@@ -3,11 +3,13 @@ package com.backbase.oss.boat.quay.ruleset
33import com.fasterxml.jackson.databind.JsonNode
44import com.fasterxml.jackson.databind.ObjectMapper
55import com.fasterxml.jackson.databind.node.JsonNodeFactory
6+ import io.swagger.v3.oas.models.Components
67import io.swagger.v3.oas.models.Operation
78import io.swagger.v3.oas.models.PathItem
89import io.swagger.v3.oas.models.examples.Example
910import io.swagger.v3.oas.models.media.MediaType
1011import io.swagger.v3.oas.models.media.Schema
12+ import org.apache.commons.lang3.StringUtils
1113import org.zalando.zally.rule.api.*
1214
1315@Rule(
@@ -19,8 +21,8 @@ import org.zalando.zally.rule.api.*
1921class RequestResponseExampleRule {
2022
2123 /* *
22- * Validate if the example contain at least one response example with all defined properties in a schema
23- * It will help to validate full response. Not just the required fields.
24+ * Validate if the response/request contains at least one example with all defined properties in a schema
25+ * It will help to validate full response/request . Not just the required fields.
2426 * Check only 2xx responses.
2527 * @param context the context to validate
2628 * @return list of identified violations
@@ -42,7 +44,7 @@ class RequestResponseExampleRule {
4244 (method == PathItem .HttpMethod .POST || method == PathItem .HttpMethod .PUT ) -> {
4345 operation?.requestBody?.content.orEmpty()
4446 .map { (type, content) ->
45- findMissPropsInExample(content).map { missProps ->
47+ findMissPropsInExample(content, context.api.components ).map { missProps ->
4648 context.violation(
4749 " Not defined value(s) (`${missProps.second} `) of example(`${missProps.first} `) for request body of ${operation?.operationId} :${method.name} of $type " ,
4850 content
@@ -67,7 +69,7 @@ class RequestResponseExampleRule {
6769 .map { (status, response) ->
6870 response.content.orEmpty()
6971 .map { (type, content) ->
70- findMissPropsInExample(content).map { missProps ->
72+ findMissPropsInExample(content, context.api.components ).map { missProps ->
7173 context.violation(
7274 " Not defined value(s) (`${missProps.second} `) of example(`${missProps.first} `) for ${operation?.operationId} :${method.name} :$status of $type " ,
7375 content
@@ -80,28 +82,15 @@ class RequestResponseExampleRule {
8082
8183 private val objectMapper = ObjectMapper ()
8284
83- private fun findMissPropsInExample (content : MediaType ): List <Pair <String , List <String >>> {
85+ private fun findMissPropsInExample (content : MediaType , components : Components ? ): List <Pair <String , List <String >>> {
8486 val properties = when {
8587 content.schema?.properties != null -> content.schema?.properties
8688 content.schema?.items?.properties != null -> content.schema?.items?.properties
8789 else -> return emptyList()
8890 }
89- var examples = content.examples.orEmpty()
90- if (examples.isEmpty()) {
91- examples = mapOf (" example" to Example ().value(content.example))
92- }
93- val missedExampleProps = examples.map { (name, exampleObject) ->
94- val jsonObject =
95- when (val value = exampleObject?.value) {
96- null -> JsonNodeFactory .instance.objectNode()
97- is String -> objectMapper.valueToTree(
98- value
99- )
100-
101- else -> value as JsonNode
102- }
91+ val missedExampleProps = prepareExamples(content, components).map { (name, exampleObject) ->
10392 val noExampleProps = properties!! .map { (propName, _) ->
104- hasExample(propName, properties[propName], jsonObject)
93+ hasExample(propName, properties[propName], jsonObject(exampleObject) )
10594 }.flatten()
10695 Pair (name, noExampleProps)
10796 }
@@ -111,6 +100,47 @@ class RequestResponseExampleRule {
111100 }
112101 }
113102
103+ private fun prepareExamples (
104+ content : MediaType ,
105+ components : Components ?
106+ ): Map <String , Example ?> {
107+ return content.examples.orEmpty()
108+ .ifEmpty {
109+ val example = Example ()
110+ if (content.example != null ) {
111+ val jsonNode = content.example as JsonNode
112+ jsonNode.update(" value" ) { value -> example.value(value) }
113+ jsonNode.update(" \$ ref" ) { ref -> example.`$ref`(ref.toString()) }
114+ }
115+ mapOf (" example" to example)
116+ }.mapValues { example ->
117+ if (StringUtils .isNotBlank(example.value.`$ref`)) {
118+ components?.examples?.get(
119+ StringUtils .substringAfterLast(example.value.`$ref`, " /" ).removeSuffix(" \" " )
120+ )
121+ } else {
122+ example.value
123+ }
124+ }
125+ }
126+
127+ private fun JsonNode.update (key : String , onFound : (JsonNode ) -> Unit ) {
128+ if (has(key) && ! get(key).isNull && get(key) is JsonNode ) {
129+ onFound(get(key))
130+ }
131+ }
132+
133+ private fun jsonObject (exampleObject : Example ? ): JsonNode {
134+ return when (val value = exampleObject?.value) {
135+ null -> JsonNodeFactory .instance.objectNode()
136+ is String -> objectMapper.valueToTree(
137+ value
138+ )
139+
140+ else -> value as JsonNode
141+ }
142+ }
143+
114144 private fun hasExample (propertyName : String , property : Schema <Any >? , jsonObject : JsonNode ): List <String > {
115145 return hasExample(null , propertyName, property, jsonObject)
116146 }
@@ -126,8 +156,10 @@ class RequestResponseExampleRule {
126156 " array" == property?.type && fieldValue.isArray -> {
127157 when {
128158 property.items.type == " object" -> {
129- property.items?.properties?.map { prop -> arrayTypeCheck(propertyName, prop, fieldValue) }!! .flatten()
159+ property.items?.properties?.map { prop -> arrayTypeCheck(propertyName, prop, fieldValue) }!!
160+ .flatten()
130161 }
162+
131163 else -> {
132164 emptyList()
133165 }
0 commit comments