Skip to content

Commit

Permalink
Merge pull request #152 from disneystreaming/openapi-error-examples
Browse files Browse the repository at this point in the history
fix issue with openapi error response examples
  • Loading branch information
lewisjkl authored Mar 27, 2024
2 parents ddf361b + fe09c61 commit 1ea08ae
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import software.amazon.smithy.openapi.model._
import java.util
import java.util.function.Function
import scala.jdk.CollectionConverters._
import software.amazon.smithy.model.traits.ExamplesTrait.Example

/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Expand Down Expand Up @@ -426,16 +427,49 @@ abstract class AlloyAbstractRestProtocol[T <: Trait]
result.asScala
}

private def reorganizeExampleTraits(
operation: OperationShape,
shape: StructureShape
): Shape = {
val isErrorShape = shape.hasTrait(classOf[ErrorTrait])
val operationOrError =
if (isErrorShape) shape
else operation

val allExamples: List[Example] =
operation
.getTrait(classOf[ExamplesTrait])
.asScala
.toList
.flatMap(_.getExamples.asScala)
val allRelevantExamples: List[Example] =
// error response so only include examples that are matching to this error shape
if (isErrorShape)
allExamples
.filter(
_.getError().asScala.map(_.getShapeId).contains(shape.toShapeId)
)
// not an error response so no error examples should be included
else allExamples.filter(_.getError().isEmpty())
val newExamplesTraitBuilder = ExamplesTrait.builder()
allRelevantExamples.foreach { ex => newExamplesTraitBuilder.addExample(ex) }
val exTrait: Trait = newExamplesTraitBuilder.build()
val newShape: Shape =
(Shape.shapeToBuilder(operationOrError): AbstractShapeBuilder[_, _])
.addTrait(exTrait)
.build()

newShape
}

private def updateResponsesMapWithResponseStatusAndObject(
context: Context[T],
bindingIndex: HttpBindingIndex,
operation: OperationShape,
shape: StructureShape,
responses: util.Map[String, ResponseObject]
) = {
val operationOrError =
if (shape.hasTrait(classOf[ErrorTrait])) shape
else operation
val operationOrError = reorganizeExampleTraits(operation, shape)
val statusCode = context.getOpenApiProtocol.getOperationResponseStatusCode(
context,
operationOrError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ object ExampleNode {
memberNames: List[String]
): ExampleNode = {
val members: List[(String, Node)] = memberNames.flatMap(name =>
example.getOutput.asScala.flatMap(
_.getMember(name).asScala.map(name -> _)
)
example.getOutput.asScala
.orElse(example.getError.asScala.map(_.getContent))
.flatMap(
_.getMember(name).asScala.map(name -> _)
)
)
ExampleNode(example, members)
}
Expand Down
106 changes: 106 additions & 0 deletions modules/openapi/test/resources/foo.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,79 @@
}
}
},
"/test_errors": {
"post": {
"operationId": "TestErrorsInExamples",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestErrorsInExamplesRequestContent"
},
"examples": {
"ONE": {
"value": {
"in": "testinput"
}
},
"TWO": {
"value": {
"in": "testinputtwo"
}
}
}
}
},
"required": true
},
"responses": {
"200": {
"description": "TestErrorsInExamples200response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestErrorsInExamplesResponseContent"
},
"examples": {
"ONE": {
"value": {
"out": "testoutput"
}
}
}
}
}
},
"404": {
"description": "NotFound404response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResponseContent"
},
"examples": {
"TWO": {
"value": {
"message": "Notfoundmessage"
}
}
}
}
}
},
"500": {
"description": "GeneralServerError500response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GeneralServerErrorResponseContent"
}
}
}
}
}
}
},
"/values": {
"get": {
"operationId": "GetValues",
Expand Down Expand Up @@ -340,6 +413,17 @@
}
]
},
"NotFoundResponseContent": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
]
},
"SomeValue": {
"oneOf": [
{
Expand All @@ -352,6 +436,28 @@
"format": "int32"
}
]
},
"TestErrorsInExamplesRequestContent": {
"type": "object",
"properties": {
"in": {
"type": "string"
}
},
"required": [
"in"
]
},
"TestErrorsInExamplesResponseContent": {
"type": "object",
"properties": {
"out": {
"type": "string"
}
},
"required": [
"out"
]
}
}
},
Expand Down
47 changes: 46 additions & 1 deletion modules/openapi/test/resources/foo.smithy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$version: "2"

namespace foo

use alloy#simpleRestJson
Expand All @@ -14,7 +16,7 @@ use alloy#dataExamples
service HelloWorldService {
version: "0.0.1",
errors: [GeneralServerError],
operations: [Greet, GetUnion, GetValues]
operations: [Greet, GetUnion, GetValues, TestErrorsInExamples]
}

@externalDocumentation(
Expand All @@ -28,6 +30,49 @@ operation Greet {
output: Greeting
}

@error("client")
@httpError(404)
structure NotFound {
@required
message: String
}

@examples([
{
title: "ONE"
input: {
in: "test input"
}
output: {
out: "test output"
}
}
{
title: "TWO"
input: {
in: "test input two"
}
error: {
shapeId: NotFound
content: {
message: "Not found message"
}
}
}
])
@http(method: "POST", uri: "/test_errors")
operation TestErrorsInExamples {
input := {
@required
in: String
}
output := {
@required
out: String
}
errors: [NotFound]
}

@readonly
@http(method: "GET", uri: "/default")
operation GetUnion {
Expand Down

0 comments on commit 1ea08ae

Please sign in to comment.