Skip to content

Commit 9bb457a

Browse files
AboruhenRoman Kniazevych
andauthored
Fix reference link ($ref:) of defining examples for issue for B014 (#963)
* Fix reference link (`$ref:`) of defining examples for issue for `B014` --------- Co-authored-by: Roman Kniazevych <romank@backbase.com>
1 parent 016f762 commit 9bb457a

File tree

4 files changed

+311
-274
lines changed

4 files changed

+311
-274
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ It currently consists of
1414

1515
# Release Notes
1616
BOAT is still under development and subject to change.
17+
## 0.17.52
18+
* Lint rule `B014` fix reference link to component examples
1719
## 0.17.46
1820
* boat-scaffold
1921
* Enhanced ISO8601 Date Formatting with Fractional Seconds Support for Swift template.

boat-quay/boat-quay-rules/src/main/kotlin/com/backbase/oss/boat/quay/ruleset/RequestResponseExampleRule.kt

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package com.backbase.oss.boat.quay.ruleset
33
import com.fasterxml.jackson.databind.JsonNode
44
import com.fasterxml.jackson.databind.ObjectMapper
55
import com.fasterxml.jackson.databind.node.JsonNodeFactory
6+
import io.swagger.v3.oas.models.Components
67
import io.swagger.v3.oas.models.Operation
78
import io.swagger.v3.oas.models.PathItem
89
import io.swagger.v3.oas.models.examples.Example
910
import io.swagger.v3.oas.models.media.MediaType
1011
import io.swagger.v3.oas.models.media.Schema
12+
import org.apache.commons.lang3.StringUtils
1113
import org.zalando.zally.rule.api.*
1214

1315
@Rule(
@@ -19,8 +21,8 @@ import org.zalando.zally.rule.api.*
1921
class 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

Comments
 (0)