-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Support custom error format for Route53 ChangeResourceRecordSets operation #791
Changes from all commits
6c2bb49
052a43a
546165c
c832bf5
0f3ea21
59157b9
4af2df2
f2a6809
242674d
d504197
7e356ce
249540c
6b619cb
3b3d34c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package software.amazon.smithy.aws.swift.codegen.customization.route53 | ||
|
||
import software.amazon.smithy.aws.swift.codegen.restxml.AWSRestXMLHttpResponseBindingErrorGenerator | ||
import software.amazon.smithy.model.Model | ||
import software.amazon.smithy.model.shapes.ServiceShape | ||
import software.amazon.smithy.swift.codegen.SwiftDelegator | ||
import software.amazon.smithy.swift.codegen.SwiftDependency | ||
import software.amazon.smithy.swift.codegen.SwiftSettings | ||
import software.amazon.smithy.swift.codegen.SwiftWriter | ||
import software.amazon.smithy.swift.codegen.core.CodegenContext | ||
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator | ||
import software.amazon.smithy.swift.codegen.integration.SectionWriter | ||
import software.amazon.smithy.swift.codegen.integration.SectionWriterBinding | ||
import software.amazon.smithy.swift.codegen.integration.SwiftIntegration | ||
import software.amazon.smithy.swift.codegen.model.expectShape | ||
|
||
class Route53InvalidBatchErrorIntegration : SwiftIntegration { | ||
override fun enabledForService(model: Model, settings: SwiftSettings): Boolean { | ||
return model.expectShape<ServiceShape>(settings.service).isRoute53 | ||
} | ||
|
||
override val sectionWriters: List<SectionWriterBinding> | ||
get() = listOf( | ||
SectionWriterBinding(AWSRestXMLHttpResponseBindingErrorGenerator.RestXMLResponseBindingSectionId, httpResponseBindingErrorGenerator) | ||
) | ||
|
||
private val httpResponseBindingErrorGenerator = SectionWriter { writer, previousCode -> | ||
val operationErrorName = writer.getContext("operationErrorName") as String | ||
if (operationErrorName == "ChangeResourceRecordSetsOutputError") { | ||
writer.openBlock("if let customBatchError = CustomInvalidBatchError.makeFromHttpResponse(httpResponse) {", "}") { | ||
writer.openBlock("let invalidChangeBatchError = InvalidChangeBatch(", ")") { | ||
writer.write("customError: customBatchError,") | ||
writer.write("headers: httpResponse.headers,") | ||
writer.write("statusCode: httpResponse.statusCode") | ||
} | ||
writer.write("self = .invalidChangeBatch(invalidChangeBatchError)") | ||
writer.write("return") | ||
} | ||
} | ||
writer.write(previousCode) | ||
} | ||
|
||
override fun writeAdditionalFiles(ctx: CodegenContext, protocolGenerationContext: ProtocolGenerator.GenerationContext, delegator: SwiftDelegator) { | ||
delegator.useFileWriter("${ctx.settings.moduleName}/models/ChangeResourceRecordSetsOutputError+Customization.swift") { writer -> | ||
writer.addImport(SwiftDependency.CLIENT_RUNTIME.target) | ||
renderCustomInvalidBatchError(writer) | ||
renderInvalidChangeBatch(writer) | ||
} | ||
} | ||
|
||
private fun renderCustomInvalidBatchError(writer: SwiftWriter) { | ||
writer.openBlock("struct CustomInvalidBatchError: Decodable {", "}") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. public APIs/structs must have docs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't public. This is just internal as it's just used for decoding and then it's transformed into a |
||
writer.openBlock("struct Message: Decodable {", "}") { | ||
writer.write("let message: String") | ||
writer.openBlock("enum CodingKeys: String, CodingKey {", "}") { | ||
writer.write("case message = \"Message\"") | ||
} | ||
} | ||
writer.write("let requestId: String") | ||
writer.write("let messages: [String]?") | ||
writer.openBlock("enum CodingKeys: String, CodingKey {", "}") { | ||
writer.write("case messages = \"Messages\"") | ||
writer.write("case requestId = \"RequestId\"") | ||
} | ||
writer.openBlock("init(from decoder: Decoder) throws {", "}") { | ||
writer.write("let container = try decoder.container(keyedBy: CodingKeys.self)") | ||
writer.write("self.requestId = try container.decode(String.self, forKey: .requestId)") | ||
writer.write("let messages = try container.decodeIfPresent([Message].self, forKey: .messages)") | ||
writer.write("self.messages = messages?.map(\\.message)") | ||
} | ||
writer.openBlock("static func makeFromHttpResponse(_ httpResponse: ClientRuntime.HttpResponse) -> CustomInvalidBatchError? {", "}") { | ||
writer.openBlock("guard let data = httpResponse.body.toBytes()?.getData() else {", "}") { | ||
writer.write("return nil") | ||
} | ||
writer.write("return try? XMLDecoder().decode(CustomInvalidBatchError.self, from: data)") | ||
} | ||
} | ||
} | ||
|
||
private fun renderInvalidChangeBatch(writer: SwiftWriter) { | ||
writer.openBlock("extension InvalidChangeBatch {", "}") { | ||
writer.openBlock("init(customError: CustomInvalidBatchError, headers: Headers?, statusCode: HttpStatusCode?) {", "}") { | ||
writer.write("self.init(messages: customError.messages)") | ||
writer.write("self._requestID = customError.requestId") | ||
writer.write("self._headers = headers") | ||
writer.write("self._statusCode = statusCode") | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package software.amazon.smithy.aws.swift.codegen.customization.route53 | ||
|
||
import software.amazon.smithy.aws.swift.codegen.sdkId | ||
import software.amazon.smithy.model.shapes.ServiceShape | ||
|
||
val ServiceShape.isRoute53: Boolean | ||
get() = sdkId.lowercase() == "route53" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package software.amazon.smithy.aws.swift.codegen.customizations | ||
|
||
import io.kotest.matchers.string.shouldContainOnlyOnce | ||
import org.junit.jupiter.api.Test | ||
import software.amazon.smithy.aws.swift.codegen.TestContext | ||
import software.amazon.smithy.aws.swift.codegen.TestContextGenerator | ||
import software.amazon.smithy.aws.swift.codegen.shouldSyntacticSanityCheck | ||
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait | ||
|
||
class Route53InvalidBatchErrorIntegrationTests { | ||
|
||
@Test | ||
fun `001 test additional structs and extensions are generated`() { | ||
val context = setupTests("route53-invalidbatch.smithy", "com.amazonaws.route53#Route53") | ||
val contents = TestContextGenerator.getFileContents(context.manifest, "/Example/models/ChangeResourceRecordSetsOutputError+Customization.swift") | ||
contents.shouldSyntacticSanityCheck() | ||
val expectedContents = | ||
""" | ||
struct CustomInvalidBatchError: Decodable { | ||
struct Message: Decodable { | ||
let message: String | ||
enum CodingKeys: String, CodingKey { | ||
case message = "Message" | ||
} | ||
} | ||
let requestId: String | ||
let messages: [String]? | ||
enum CodingKeys: String, CodingKey { | ||
case messages = "Messages" | ||
case requestId = "RequestId" | ||
} | ||
init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
self.requestId = try container.decode(String.self, forKey: .requestId) | ||
let messages = try container.decodeIfPresent([Message].self, forKey: .messages) | ||
self.messages = messages?.map(\.message) | ||
} | ||
static func makeFromHttpResponse(_ httpResponse: ClientRuntime.HttpResponse) -> CustomInvalidBatchError? { | ||
guard let data = httpResponse.body.toBytes()?.getData() else { | ||
return nil | ||
} | ||
return try? XMLDecoder().decode(CustomInvalidBatchError.self, from: data) | ||
} | ||
} | ||
extension InvalidChangeBatch { | ||
init(customError: CustomInvalidBatchError, headers: Headers?, statusCode: HttpStatusCode?) { | ||
self.init(messages: customError.messages) | ||
self._requestID = customError.requestId | ||
self._headers = headers | ||
self._statusCode = statusCode | ||
} | ||
} | ||
""".trimIndent() | ||
contents.shouldContainOnlyOnce(expectedContents) | ||
} | ||
|
||
@Test | ||
fun `002 test ChangeResourceRecordSetsOutputError+HttpResponseBinding is customized`() { | ||
val context = setupTests("route53-invalidbatch.smithy", "com.amazonaws.route53#Route53") | ||
val contents = TestContextGenerator.getFileContents(context.manifest, "/Example/models/ChangeResourceRecordSetsOutputError+HttpResponseBinding.swift") | ||
contents.shouldSyntacticSanityCheck() | ||
val expectedContents = | ||
""" | ||
extension ChangeResourceRecordSetsOutputError: ClientRuntime.HttpResponseBinding { | ||
public init(httpResponse: ClientRuntime.HttpResponse, decoder: ClientRuntime.ResponseDecoder? = nil) throws { | ||
if let customBatchError = CustomInvalidBatchError.makeFromHttpResponse(httpResponse) { | ||
let invalidChangeBatchError = InvalidChangeBatch( | ||
customError: customBatchError, | ||
headers: httpResponse.headers, | ||
statusCode: httpResponse.statusCode | ||
) | ||
self = .invalidChangeBatch(invalidChangeBatchError) | ||
return | ||
} | ||
let errorDetails = try AWSClientRuntime.RestXMLError(httpResponse: httpResponse) | ||
try self.init(errorType: errorDetails.errorCode, httpResponse: httpResponse, decoder: decoder, message: errorDetails.message, requestID: errorDetails.requestId) | ||
} | ||
} | ||
""".trimIndent() | ||
contents.shouldContainOnlyOnce(expectedContents) | ||
} | ||
|
||
private fun setupTests(smithyFile: String, serviceShapeId: String): TestContext { | ||
val context = TestContextGenerator.initContextFrom(smithyFile, serviceShapeId, RestXmlTrait.ID) | ||
return context | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
$version: "1.0" | ||
namespace com.amazonaws.route53 | ||
|
||
use aws.api#service | ||
use aws.protocols#restXml | ||
|
||
@service(sdkId: "Route53") | ||
@restXml | ||
service Route53 { | ||
version: "2019-12-16", | ||
operations: [ChangeResourceRecordSets] | ||
} | ||
|
||
@http(uri: "/ChangeResourceRecordSets", method: "POST") | ||
operation ChangeResourceRecordSets { | ||
input: InputOutput | ||
output: InputOutput | ||
errors: [InvalidChangeBatch] | ||
} | ||
|
||
structure InputOutput { | ||
foo: String | ||
} | ||
|
||
@error("client") | ||
structure InvalidChangeBatch { | ||
message: String | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be awesome to have a way to just write this in swift, since it isn't dependent on the smithy model.
I filed this issue to investigate some mechanism that would allow us to write service specific swift code. I imagine the solution here will be related (if not the same) as solving the service specific integration test problem.