Skip to content

Commit

Permalink
Merge pull request softwaremill#2717 from softwaremill/default-decode…
Browse files Browse the repository at this point in the history
…-failure-output-docs

Properly generate docs for default error responses defined in options
  • Loading branch information
adamw committed Feb 7, 2023
2 parents 927bd27 + 31e23ce commit c222bc2
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ class SchemasForEndpoints(
es: Iterable[AnyEndpoint],
schemaName: SName => String,
toKeyedSchemas: ToKeyedSchemas,
markOptionsAsNullable: Boolean
markOptionsAsNullable: Boolean,
additionalOutputs: List[EndpointOutput[_]]
) {

def apply(): (ListMap[SchemaId, ReferenceOr[ASchema]], Schemas) = {
val keyedSchemas = ToKeyedSchemas.unique(
es.flatMap(e => forInput(e.securityInput) ++ forInput(e.input) ++ forOutput(e.errorOutput) ++ forOutput(e.output))
es.flatMap(e =>
forInput(e.securityInput) ++ forInput(e.input) ++ forOutput(e.errorOutput) ++ forOutput(e.output)
) ++ additionalOutputs.flatMap(forOutput(_))
)
val keysToIds = calculateUniqueIds(keyedSchemas.map(_._1), (key: SchemaKey) => schemaName(key.name))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ private[asyncapi] object EndpointToAsyncAPIDocs {
val wsEndpoints = wsEndpointsWithWrapper.map(_._1).map(nameAllPathCapturesInEndpoint)
val toKeyedSchemas = new ToKeyedSchemas
val (keyToSchema, schemas) =
new SchemasForEndpoints(wsEndpoints, options.schemaName, toKeyedSchemas, markOptionsAsNullable = false).apply()
new SchemasForEndpoints(wsEndpoints, options.schemaName, toKeyedSchemas, markOptionsAsNullable = false, additionalOutputs = Nil)
.apply()
val (codecToMessageKey, keyToMessage) = new MessagesForEndpoints(schemas, options.schemaName, toKeyedSchemas)(
wsEndpointsWithWrapper.map(_._2)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ private[openapi] object EndpointToOpenAPIDocs {
): OpenAPI = {
val es2 = es.filter(e => findWebSocket(e).isEmpty).map(nameAllPathCapturesInEndpoint)
val toKeyedSchemas = new ToKeyedSchemas
val (idToSchema, schemas) = new SchemasForEndpoints(es2, options.schemaName, toKeyedSchemas, options.markOptionsAsNullable).apply()
val additionalOutputs = es2.flatMap(e => options.defaultDecodeFailureOutput(e.input)).toSet.toList
val (idToSchema, schemas) =
new SchemasForEndpoints(es2, options.schemaName, toKeyedSchemas, options.markOptionsAsNullable, additionalOutputs).apply()
val securitySchemes = SecuritySchemesForEndpoints(es2, apiKeyAuthTypeName = "apiKey")
val pathCreator = new EndpointToOpenAPIPaths(schemas, securitySchemes, options)
val componentsCreator = new EndpointToOpenAPIComponents(idToSchema, securitySchemes)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
openapi: 3.0.3
info:
title: Entities
version: '1.0'
paths:
/:
get:
operationId: getRoot
parameters:
- name: amount
in: query
required: true
schema:
type: integer
format: int32
minimum: 0
responses:
'200':
description: ''
'400':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Fail'
components:
schemas:
Fail:
required:
- msg
type: object
properties:
msg:
type: string
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ import sttp.model.StatusCode
import sttp.apispec.openapi.Info
import sttp.apispec.openapi.circe.yaml._
import sttp.tapir
import sttp.tapir._
import sttp.tapir.tests.Validation
import sttp.tapir.{Endpoint, oneOfVariant, stringBody}
import sttp.tapir.json.circe._
import sttp.tapir.generic.auto._

import io.circe.generic.auto._

class VerifyYamlDecodeFailureOutputTest extends AnyFunSuite with Matchers {

private val fallibleEndpoint: Endpoint[Unit, Int, Unit, Unit, Any] = Validation.in_query
private case class Fail(msg: String)

test("should include default 400 response if input decoding may fail") {
val expectedYaml = load("decode_failure_output/expected_default_400_response.yml")
Expand All @@ -32,6 +38,18 @@ class VerifyYamlDecodeFailureOutputTest extends AnyFunSuite with Matchers {
noIndentation(actualYaml) shouldBe expectedYaml
}

test("should include json response defined in options if input decoding may fail") {
val options: OpenAPIDocsOptions = OpenAPIDocsOptions.default.copy(
defaultDecodeFailureOutput =
_ => Some(statusCode(StatusCode.BadRequest).and(jsonBody[Fail]))
)

val expectedYaml = load("decode_failure_output/expected_json_response_defined_in_options.yml")

val actualYaml = OpenAPIDocsInterpreter(options).toOpenAPI(fallibleEndpoint, Info("Entities", "1.0")).toYaml
noIndentation(actualYaml) shouldBe expectedYaml
}

test("should exclude response if default response is set to None") {
val options: OpenAPIDocsOptions = OpenAPIDocsOptions.default.copy(defaultDecodeFailureOutput = _ => None)

Expand Down

0 comments on commit c222bc2

Please sign in to comment.