Skip to content

Commit

Permalink
fix: Support custom error format for Route53 ChangeResourceRecordSets…
Browse files Browse the repository at this point in the history
… operation (#791)

* Updates CRT to 0.5.0

* Updates CRT to 0.5.0

* Bumps CRT to 0.5.2

* Updates tests

* Cleans up and adds tests

* Adds tests

* Addresses PR feedback

* Logs when we fail to resolve a region

* Updates codegen tests

* Updates codegen and error models

* Updates codegen

* Adds customization for Route53 ChangeResourceRecordSets to handle InvalidBatchError

* ktlintformat
  • Loading branch information
epau authored Jan 5, 2023
1 parent 6dd1d5a commit 73358bb
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 3 deletions.
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 {", "}") {
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
Expand Up @@ -3,6 +3,7 @@ package software.amazon.smithy.aws.swift.codegen.customization.route53
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.HttpLabelTrait
Expand All @@ -12,13 +13,12 @@ import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
import software.amazon.smithy.swift.codegen.integration.SwiftIntegration
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.MiddlewareShapeUtils
import software.amazon.smithy.swift.codegen.middleware.OperationMiddleware
import software.amazon.smithy.swift.codegen.model.expectShape
import software.amazon.smithy.swift.codegen.model.hasTrait

private val Route53ShapeId: ShapeId = ShapeId.from("com.amazonaws.route53#AWSDnsV20130401")

class Route53TrimHostedZone : SwiftIntegration {
override fun enabledForService(model: Model, settings: SwiftSettings): Boolean {
return settings.service == Route53ShapeId
return model.expectShape<ServiceShape>(settings.service).isRoute53
}
override fun preprocessModel(model: Model, settings: SwiftSettings): Model {
return ModelTransformer.create().mapShapes(model) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ software.amazon.smithy.aws.swift.codegen.AddProtocols
software.amazon.smithy.aws.swift.codegen.customization.s3.S3ErrorIntegration
software.amazon.smithy.aws.swift.codegen.customization.s3.S3Expires
software.amazon.smithy.aws.swift.codegen.customization.route53.Route53TrimHostedZone
software.amazon.smithy.aws.swift.codegen.customization.route53.Route53InvalidBatchErrorIntegration
software.amazon.smithy.aws.swift.codegen.customization.apigateway.ApiGatewayAddAcceptHeader
software.amazon.smithy.aws.swift.codegen.customization.glacier.GlacierAddVersionHeader
software.amazon.smithy.aws.swift.codegen.customization.glacier.GlacierAccountIdDefault
Expand Down
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
}

0 comments on commit 73358bb

Please sign in to comment.