Skip to content

Commit

Permalink
Supported OpenApi 3.1.0 nullable changes. (#263)
Browse files Browse the repository at this point in the history
* Supported OpenApi 3.1.0 nullable changes.
Fabrikt will now detect of the newer 3.1.0 specification is declared and automatically downgrade any of the json schema nullable types to the previous format.
EG, this:
```
    NewNullableFormat:
      type: object
      required:
        - version
      properties:
        version:
          type:
            - string
            - 'null'
```
Will get converted to this:
```
    NewNullableFormat:
      type: object
      required:
        - version
      properties:
        version:
          type: string
          nullable: true
```

* improve code
  • Loading branch information
cjbooms authored Jan 29, 2024
1 parent d1a5e4f commit 467eb6c
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 12 deletions.
59 changes: 47 additions & 12 deletions src/main/kotlin/com/cjbooms/fabrikt/util/YamlUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,62 @@ object YamlUtils {
ObjectMapper(
YAMLFactory()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES),
)
.registerKotlinModule()
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
private val internalMapper: ObjectMapper =
ObjectMapper(YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER))

private val NULL_TYPE: JsonNode = objectMapper.valueToTree("null")

fun mergeYamlTrees(mainTree: String, updateTree: String) =
internalMapper.writeValueAsString(
mergeNodes(
internalMapper.readTree(mainTree),
internalMapper.readTree(updateTree)
)
internalMapper.readTree(updateTree),
),
)!!

fun parseOpenApi(input: String, inputDir: Path = Paths.get("").toAbsolutePath()): OpenApi3 =
try {
OpenApi3Parser().parse(input, inputDir.toUri().toURL())
val root: JsonNode = objectMapper.readTree(input)
if (root["openapi"].asText() == "3.1.0") {
downgradeNullableSyntax(root)
}
OpenApi3Parser().parse(root, inputDir.toUri().toURL())
} catch (ex: NullPointerException) {
throw IllegalArgumentException(
"The Kaizen openapi-parser library threw a NPE exception when parsing this API. " +
"This is commonly due to an external schema reference that is unresolvable, " +
"possibly due to a lack of internet connection",
ex
ex,
)
}

private fun downgradeNullableSyntax(node: JsonNode) {
when {
node.isObject -> {
var requiresNullable = false
node.fields().forEach { (key, maybeTypeArray) ->
if (key == "type" && maybeTypeArray.isArray && maybeTypeArray.contains(NULL_TYPE)) {
val nonNullType = maybeTypeArray.first { it != NULL_TYPE }
(node as ObjectNode).replace("type", nonNullType)
requiresNullable = true
}
downgradeNullableSyntax(maybeTypeArray)
}
if (requiresNullable) {
(node as ObjectNode).put("nullable", true)
}
}

node.isArray -> {
node.forEach { downgradeNullableSyntax(it) }
}
}
}

/**
* The below merge function has been shamelessly stolen from Stackoverflow: https://stackoverflow.com/a/32447591/1026785
* and converted to much nicer Kotlin implementation
Expand All @@ -56,14 +85,20 @@ object YamlUtils {
val incomingNode = incomingTree.get(fieldName)
if (currentNode is ArrayNode && incomingNode is ArrayNode) {
incomingNode.forEach {
if (currentNode.contains(it)) mergeNodes(
currentNode.get(currentNode.indexOf(it)),
it
)
else currentNode.add(it)
if (currentNode.contains(it)) {
mergeNodes(
currentNode.get(currentNode.indexOf(it)),
it,
)
} else {
currentNode.add(it)
}
}
} else if (currentNode is ObjectNode) mergeNodes(currentNode, incomingNode)
else (currentTree as? ObjectNode)?.replace(fieldName, incomingNode)
} else if (currentNode is ObjectNode) {
mergeNodes(currentNode, incomingNode)
} else {
(currentTree as? ObjectNode)?.replace(fieldName, incomingNode)
}
}
return currentTree
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class ModelGeneratorTest {
"webhook",
"instantDateTime",
"discriminatedOneOf",
"openapi310",
)

@BeforeEach
Expand Down
14 changes: 14 additions & 0 deletions src/test/resources/examples/openapi310/api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
openapi: 3.1.0
components:
schemas:
NewNullableFormat:
type: object
required:
- version
properties:
version:
type:
- string
- 'null'
description: The resolved version or `null` if there is no matching version.

12 changes: 12 additions & 0 deletions src/test/resources/examples/openapi310/models/Models.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package examples.openapi310.models

import com.fasterxml.jackson.`annotation`.JsonProperty
import javax.validation.constraints.NotNull
import kotlin.String

public data class NewNullableFormat(
@param:JsonProperty("version")
@get:JsonProperty("version")
@get:NotNull
public val version: String,
)

0 comments on commit 467eb6c

Please sign in to comment.