diff --git a/server/zally-core/build.gradle.kts b/server/zally-core/build.gradle.kts index 4372f8f0a..f2a4de7df 100644 --- a/server/zally-core/build.gradle.kts +++ b/server/zally-core/build.gradle.kts @@ -2,10 +2,13 @@ dependencies { kapt("com.google.auto.service:auto-service:1.0.1") api(project(":zally-rule-api")) - api("io.swagger.parser.v3:swagger-parser:2.1.12") + api("io.swagger.parser.v3:swagger-parser:2.1.26") api("io.github.config4k:config4k:0.5.0") implementation("com.google.auto.service:auto-service:1.0.1") + implementation("javax.mail:javax.mail-api:1.6.2") + runtimeOnly("com.sun.mail:javax.mail:1.6.2") + testImplementation(project(":zally-test")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/server/zally-core/src/main/kotlin/org/zalando/zally/core/ast/ReverseAstBuilder.kt b/server/zally-core/src/main/kotlin/org/zalando/zally/core/ast/ReverseAstBuilder.kt index ea7137e63..428ff4b70 100644 --- a/server/zally-core/src/main/kotlin/org/zalando/zally/core/ast/ReverseAstBuilder.kt +++ b/server/zally-core/src/main/kotlin/org/zalando/zally/core/ast/ReverseAstBuilder.kt @@ -97,20 +97,25 @@ class ReverseAstBuilder internal constructor(root: T) { val name = m.name try { m.invoke(obj)?.let { value -> + val nextNodePointer: JsonPointer + var skipNode = false + if (m.isAnnotationPresent(JsonAnyGetter::class.java)) { // A `JsonAnyGetter` method is simply a wrapper for nested properties. // We must not use the method name but re-use the current pointer. - nodes.push(Node(value, pointer, marker, /* skip */true)) + nextNodePointer = pointer + skipNode = true + } else if (name in this.extensionMethodNames) { + // Extension methods return a Map of OpenAPI extensions. + // Assigning the parent's pointer here ensures the Map itself doesn't add a path segment, + // to avoid issues like '/parent//extension-key' + nextNodePointer = pointer } else { - // Do not add extension names to the JsonNode path - val nextPath = m.name - .takeIf { it !in this.extensionMethodNames } - ?.removePrefix("get") - ?.replaceFirstChar({ it.lowercase() }) - ?: "" - - nodes.push(Node(value, pointer + nextPath.toEscapedJsonPointer(), marker)) + // Regular bean property: a new path segment is created from the property name. + val propertyName = name.removePrefix("get").replaceFirstChar { it.lowercase() } + nextNodePointer = pointer + propertyName.toEscapedJsonPointer() } + nodes.push(Node(value, nextNodePointer, marker, skipNode)) } } catch (e: ReflectiveOperationException) { throw ReverseAstException("Error invoking $name on ${obj.javaClass.name} at path $pointer", e)