Skip to content

Commit 0843607

Browse files
Added the option to add OpenAPIDocsOptions to verifyClient/verifyServer (#5093)
Added the ability to include `OpenAPIDocsOptions` for `verifyClient` and `verifyServer` At $work we compare our service endpoint to the last production deployed yaml to find inconsistencies. We recently wanted to try `markOptionsAsNullable` but encountered issues - the resulting yaml fails when compared to the endpoints. The error we experienced is similar to what happens in `verifyServer - respect OpenAPIDocsOptions` if you don't include custom `OpenAPIDocsOptions`. ```scala List(incompatible path /users: - incompatible operation get: - incompatible response: - incompatible content: - incompatible media type application/json: - incompatible schema: List(incompatible anyOf/oneOf variant at index 1: - target schema is restricted to type object, type null is incompatible with it)) was not empty ScalaTestFailureLocation: sttp.tapir.docs.openapi.OpenApiVerifierTest at (OpenApiVerifierTest.scala:138) ``` This PR solves this by allowing users to provide your `OpenApiDocsOptions` to `OpenApiVerifier` so that they are more similar.
1 parent 929903b commit 0843607

2 files changed

Lines changed: 94 additions & 4 deletions

File tree

docs/openapi-verifier/src/main/scala/sttp/tapir/docs/openapi/OpenAPIVerifier.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ object OpenAPIVerifier {
2525
* a list of `OpenAPICompatibilityIssue` instances detailing the compatibility issues found during verification, or `Nil` if no issues
2626
* were found.
2727
*/
28-
def verifyClient(clientEndpoints: List[AnyEndpoint], serverSpecificationYaml: String): List[OpenAPICompatibilityIssue] = {
29-
val clientOpenAPI = OpenAPIDocsInterpreter().toOpenAPI(clientEndpoints, "OpenAPIVerifier", "1.0")
28+
def verifyClient(
29+
clientEndpoints: List[AnyEndpoint],
30+
serverSpecificationYaml: String,
31+
docsOptions: OpenAPIDocsOptions = OpenAPIDocsOptions.default
32+
): List[OpenAPICompatibilityIssue] = {
33+
val clientOpenAPI = OpenAPIDocsInterpreter(docsOptions).toOpenAPI(clientEndpoints, "OpenAPIVerifier", "1.0")
3034
val serverOpenAPI = readOpenAPIFromString(serverSpecificationYaml)
3135

3236
OpenAPIComparator(clientOpenAPI, serverOpenAPI).compare()
@@ -42,8 +46,12 @@ object OpenAPIVerifier {
4246
* a list of `OpenAPICompatibilityIssue` instances detailing the compatibility issues found during verification, or `Nil` if no issues
4347
* were found.
4448
*/
45-
def verifyServer(serverEndpoints: List[AnyEndpoint], clientSpecificationYaml: String): List[OpenAPICompatibilityIssue] = {
46-
val serverOpenAPI = OpenAPIDocsInterpreter().toOpenAPI(serverEndpoints, "OpenAPIVerifier", "1.0")
49+
def verifyServer(
50+
serverEndpoints: List[AnyEndpoint],
51+
clientSpecificationYaml: String,
52+
docsOptions: OpenAPIDocsOptions = OpenAPIDocsOptions.default
53+
): List[OpenAPICompatibilityIssue] = {
54+
val serverOpenAPI = OpenAPIDocsInterpreter(docsOptions).toOpenAPI(serverEndpoints, "OpenAPIVerifier", "1.0")
4755
val clientOpenAPI = readOpenAPIFromString(clientSpecificationYaml)
4856

4957
OpenAPIComparator(clientOpenAPI, serverOpenAPI).compare()

docs/openapi-verifier/src/test/scala/sttp/tapir/docs/openapi/OpenApiVerifierTest.scala

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package sttp.tapir.docs.openapi
33
import org.scalatest.funsuite.AnyFunSuite
44
import sttp.tapir._
55
import sttp.tapir.json.circe.jsonBody
6+
import io.circe.generic.auto._
7+
import sttp.tapir.generic.auto._
68

79
class OpenApiVerifierTest extends AnyFunSuite {
810
val openAPISpecification: String =
@@ -45,6 +47,50 @@ class OpenApiVerifierTest extends AnyFunSuite {
4547
| type: string
4648
""".stripMargin
4749

50+
val openAPISpecificationMarkOptionAsNullable: String =
51+
"""openapi: 3.0.0
52+
|info:
53+
| title: Sample API
54+
| description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
55+
| version: 0.1.9
56+
|
57+
|servers:
58+
| - url: http://api.example.com/v1
59+
| description: Optional server description, e.g. Main (production) server
60+
| - url: http://staging-api.example.com
61+
| description: Optional server description, e.g. Internal staging server for testing
62+
|
63+
|paths:
64+
| /users:
65+
| get:
66+
| summary: Returns a list of users.
67+
| description: Optional extended description in CommonMark or HTML.
68+
| responses:
69+
| "200": # status code
70+
| description: A JSON array of user names
71+
| content:
72+
| application/json:
73+
| schema:
74+
| anyOf:
75+
| - $ref: '#/components/schemas/FullName'
76+
| - type: 'null'
77+
|components:
78+
| schemas:
79+
| FullName:
80+
| title: FullName
81+
| type: object
82+
| required:
83+
| - firstName
84+
| - lastName
85+
| properties:
86+
| firstName:
87+
| type: string
88+
| lastName:
89+
| type: string
90+
| """.stripMargin
91+
92+
case class FullName(firstName: String, lastName: String)
93+
4894
test("verifyServer - all client openapi endpoints have corresponding server endpoints") {
4995
val serverEndpoints = List(
5096
endpoint.get
@@ -84,6 +130,24 @@ class OpenApiVerifierTest extends AnyFunSuite {
84130
assert(OpenAPIVerifier.verifyServer(serverEndpoints, openAPISpecification).nonEmpty)
85131
}
86132

133+
test("verifyServer - respect OpenAPIDocsOptions") {
134+
val serverEndpoints = List(
135+
endpoint.get
136+
.in("users")
137+
.out(jsonBody[Option[FullName]])
138+
)
139+
140+
assert(
141+
OpenAPIVerifier
142+
.verifyServer(
143+
serverEndpoints,
144+
openAPISpecificationMarkOptionAsNullable,
145+
OpenAPIDocsOptions.default.copy(markOptionsAsNullable = true)
146+
)
147+
.isEmpty
148+
)
149+
}
150+
87151
test("verifyClient - all server openapi endpoints have corresponding client endpoints") {
88152
val clientEndpoints = List(
89153
endpoint.get
@@ -122,4 +186,22 @@ class OpenApiVerifierTest extends AnyFunSuite {
122186

123187
assert(OpenAPIVerifier.verifyClient(clientEndpoints, openAPISpecification).isEmpty)
124188
}
189+
190+
test("verifyClient - respect OpenAPIDocsOptions") {
191+
val clientEndpoints = List(
192+
endpoint.get
193+
.in("users")
194+
.out(jsonBody[Option[FullName]])
195+
)
196+
197+
assert(
198+
OpenAPIVerifier
199+
.verifyClient(
200+
clientEndpoints,
201+
openAPISpecificationMarkOptionAsNullable,
202+
OpenAPIDocsOptions.default.copy(markOptionsAsNullable = true)
203+
)
204+
.isEmpty
205+
)
206+
}
125207
}

0 commit comments

Comments
 (0)