-
Notifications
You must be signed in to change notification settings - Fork 422
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: adamw <[email protected]>
- Loading branch information
1 parent
0fc93a0
commit b494b1b
Showing
4 changed files
with
293 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
docs/openapi-verifier/src/main/scala/sttp/tapir/docs/openapi/OpenAPIVerifier.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package sttp.tapir.docs.openapi | ||
|
||
import sttp.apispec.openapi.OpenAPI | ||
import sttp.apispec.openapi.validation._ | ||
import sttp.tapir._ | ||
import io.circe._ | ||
import io.circe.yaml.parser | ||
import sttp.apispec.openapi.circe.openAPIDecoder | ||
|
||
/** A utility for verifying the compatibility of Tapir endpoints with an OpenAPI specification. | ||
* | ||
* The `OpenAPIVerifier` object provides methods to verify compatibility between endpoints and OpenAPI specifications, or client endpoints | ||
* and server OpenAPI specifications. The compatibility check detects issues such as missing endpoints, parameter mismatches, and schema | ||
* inconsistencies. | ||
*/ | ||
object OpenAPIVerifier { | ||
|
||
/** Verifies that the provided client endpoints are compatible with the given server OpenAPI specification. | ||
* | ||
* @param clientEndpoints | ||
* the list of client Tapir endpoints to verify. | ||
* @param serverSpecificationYaml | ||
* the OpenAPI specification provided by the server, in YAML format. | ||
* @return | ||
* a list of `OpenAPICompatibilityIssue` instances detailing the compatibility issues found during verification, or `Nil` if no issues | ||
* were found. | ||
*/ | ||
def verifyClient(clientEndpoints: List[AnyEndpoint], serverSpecificationYaml: String): List[OpenAPICompatibilityIssue] = { | ||
val clientOpenAPI = OpenAPIDocsInterpreter().toOpenAPI(clientEndpoints, "OpenAPIVerifier", "1.0") | ||
val serverOpenAPI = readOpenAPIFromString(serverSpecificationYaml) | ||
|
||
OpenAPIComparator(clientOpenAPI, serverOpenAPI).compare() | ||
} | ||
|
||
/** Verifies that the client OpenAPI specification is compatible with the provided server endpoints. | ||
* | ||
* @param serverEndpoints | ||
* the list of server Tapir endpoints to verify. | ||
* @param clientSpecificationYaml | ||
* the OpenAPI specification provided by the client, in YAML format. | ||
* @return | ||
* a list of `OpenAPICompatibilityIssue` instances detailing the compatibility issues found during verification, or `Nil` if no issues | ||
* were found. | ||
*/ | ||
def verifyServer(serverEndpoints: List[AnyEndpoint], clientSpecificationYaml: String): List[OpenAPICompatibilityIssue] = { | ||
val serverOpenAPI = OpenAPIDocsInterpreter().toOpenAPI(serverEndpoints, "OpenAPIVerifier", "1.0") | ||
val clientOpenAPI = readOpenAPIFromString(clientSpecificationYaml) | ||
|
||
OpenAPIComparator(clientOpenAPI, serverOpenAPI).compare() | ||
} | ||
|
||
private def readOpenAPIFromString(yamlOpenApiSpec: String): OpenAPI = { | ||
parser.parse(yamlOpenApiSpec).flatMap(_.as[OpenAPI]) match { | ||
case Right(openapi) => openapi | ||
case Left(error) => throw new IllegalArgumentException("Failed to parse OpenAPI YAML specification", error) | ||
} | ||
} | ||
} |
125 changes: 125 additions & 0 deletions
125
docs/openapi-verifier/src/test/scala/sttp/tapir/docs/openapi/OpenApiVerifierTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package sttp.tapir.docs.openapi | ||
|
||
import org.scalatest.funsuite.AnyFunSuite | ||
import sttp.tapir._ | ||
import sttp.tapir.json.circe.jsonBody | ||
|
||
class OpenApiVerifierTest extends AnyFunSuite { | ||
val openAPISpecification: String = | ||
"""openapi: 3.0.0 | ||
|info: | ||
| title: Sample API | ||
| description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. | ||
| version: 0.1.9 | ||
| | ||
|servers: | ||
| - url: http://api.example.com/v1 | ||
| description: Optional server description, e.g. Main (production) server | ||
| - url: http://staging-api.example.com | ||
| description: Optional server description, e.g. Internal staging server for testing | ||
| | ||
|paths: | ||
| /users: | ||
| get: | ||
| summary: Returns a list of users. | ||
| description: Optional extended description in CommonMark or HTML. | ||
| responses: | ||
| "200": # status code | ||
| description: A JSON array of user names | ||
| content: | ||
| application/json: | ||
| schema: | ||
| type: array | ||
| items: | ||
| type: string | ||
| /users/name: | ||
| get: | ||
| summary: Returns a user name. | ||
| description: Retrieves the name of a specific user. | ||
| responses: | ||
| "200": # status code | ||
| description: A plain text user name | ||
| content: | ||
| text/plain: | ||
| schema: | ||
| type: string | ||
""".stripMargin | ||
|
||
test("verifyServer - all client openapi endpoints have corresponding server endpoints") { | ||
val serverEndpoints = List( | ||
endpoint.get | ||
.in("users") | ||
.out(jsonBody[List[String]]), | ||
endpoint.get | ||
.in("users" / "name") | ||
.out(stringBody) | ||
) | ||
|
||
assert(OpenAPIVerifier.verifyServer(serverEndpoints, openAPISpecification).isEmpty) | ||
} | ||
|
||
test("verifyServer - additional endpoints in server") { | ||
val serverEndpoints = List( | ||
endpoint.get | ||
.in("users") | ||
.out(jsonBody[List[String]]), | ||
endpoint.get | ||
.in("users" / "name") | ||
.out(stringBody), | ||
endpoint.get | ||
.in("extra") | ||
.out(stringBody) | ||
) | ||
|
||
assert(OpenAPIVerifier.verifyServer(serverEndpoints, openAPISpecification).isEmpty) | ||
} | ||
|
||
test("verifyServer - missing endpoints in server") { | ||
val serverEndpoints = List( | ||
endpoint.get | ||
.in("users") | ||
.out(jsonBody[List[String]]) | ||
) | ||
|
||
assert(OpenAPIVerifier.verifyServer(serverEndpoints, openAPISpecification).nonEmpty) | ||
} | ||
|
||
test("verifyClient - all server openapi endpoints have corresponding client endpoints") { | ||
val clientEndpoints = List( | ||
endpoint.get | ||
.in("users") | ||
.out(jsonBody[List[String]]), | ||
endpoint.get | ||
.in("users" / "name") | ||
.out(stringBody) | ||
) | ||
|
||
assert(OpenAPIVerifier.verifyClient(clientEndpoints, openAPISpecification).isEmpty) | ||
} | ||
|
||
test("verifyClient - additional endpoints exist in client") { | ||
val clientEndpoints = List( | ||
endpoint.get | ||
.in("users") | ||
.out(jsonBody[List[String]]), | ||
endpoint.get | ||
.in("users" / "name") | ||
.out(stringBody), | ||
endpoint.get | ||
.in("extra") | ||
.out(stringBody) | ||
) | ||
|
||
assert(OpenAPIVerifier.verifyClient(clientEndpoints, openAPISpecification).nonEmpty) | ||
} | ||
|
||
test("verifyClient - missing endpoints in client") { | ||
val clientEndpoints = List( | ||
endpoint.get | ||
.in("users") | ||
.out(jsonBody[List[String]]) | ||
) | ||
|
||
assert(OpenAPIVerifier.verifyClient(clientEndpoints, openAPISpecification).isEmpty) | ||
} | ||
} |