Skip to content

Commit

Permalink
fix: Make operations correctly model service errors (#1114)
Browse files Browse the repository at this point in the history
* Add helper method for handling service errors for AWS protocols.

---------

Co-authored-by: Sichan Yoo <chanyoo@amazon.com>
Co-authored-by: Josh Elkins <jbelkins@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 13, 2023
1 parent 93696f9 commit 806ca22
Show file tree
Hide file tree
Showing 16 changed files with 667 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ object AWSClientRuntimeTypes {

object EC2Query {
val Ec2NarrowedResponse = runtimeSymbol("Ec2NarrowedResponse")
val Ec2QueryError = runtimeSymbol("Ec2QueryError")
}
object AWSJSON {
val XAmzTargetMiddleware = runtimeSymbol("XAmzTargetMiddleware")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,50 @@ import software.amazon.smithy.swift.codegen.model.toUpperCamelCase
import software.amazon.smithy.swift.codegen.utils.errorShapeName

class AWSJsonHttpResponseBindingErrorGenerator : HttpResponseBindingErrorGeneratable {
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, unknownServiceErrorSymbol: Symbol) {
override fun renderServiceError(ctx: ProtocolGenerator.GenerationContext) {
val serviceShape = ctx.service
val serviceName = ctx.service.id.name
val rootNamespace = ctx.settings.moduleName
val fileName = "./$rootNamespace/models/$serviceName+ServiceErrorHelperMethod.swift"

ctx.delegator.useFileWriter(fileName) { writer ->
with(writer) {
addImport(AWSSwiftDependency.AWS_CLIENT_RUNTIME.target)
addImport(SwiftDependency.CLIENT_RUNTIME.target)

openBlock("extension ${ctx.symbolProvider.toSymbol(ctx.service).name}Types {", "}") {
openBlock(
"static func makeServiceError(_ httpResponse: \$N, _ decoder: \$D, _ error: \$N, _ id: String?) async throws -> \$N? {",
"}",
ClientRuntimeTypes.Http.HttpResponse,
ClientRuntimeTypes.Serde.ResponseDecoder,
AWSClientRuntimeTypes.RestJSON.RestJSONError,
SwiftTypes.Error
) {
openBlock("switch error.errorType {", "}") {
val serviceErrorShapes =
serviceShape.errors
.map { ctx.model.expectShape(it) as StructureShape }
.toSet()
.sorted()
serviceErrorShapes.forEach { errorShape ->
val errorShapeName = errorShape.errorShapeName(ctx.symbolProvider)
val errorShapeType = ctx.symbolProvider.toSymbol(errorShape).name
write(
"case \$S: return try await \$L(httpResponse: httpResponse, decoder: decoder, message: error.errorMessage, requestID: id)",
errorShapeName,
errorShapeType
)
}
write("default: return nil")
}
}
}
}
}
}

override fun renderOperationError(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, unknownServiceErrorSymbol: Symbol) {
val operationErrorName = "${op.toUpperCamelCase()}OutputError"
val rootNamespace = ctx.settings.moduleName
val httpBindingSymbol = Symbol.builder()
Expand All @@ -28,33 +71,52 @@ class AWSJsonHttpResponseBindingErrorGenerator : HttpResponseBindingErrorGenerat
.build()

ctx.delegator.useShapeWriter(httpBindingSymbol) { writer ->
writer.addImport(AWSSwiftDependency.AWS_CLIENT_RUNTIME.target)
writer.addImport(SwiftDependency.CLIENT_RUNTIME.target)

writer.openBlock("public enum \$L: \$N {", "}", operationErrorName, ClientRuntimeTypes.Http.HttpResponseErrorBinding) {
writer.openBlock(
"public static func makeError(httpResponse: \$N, decoder: \$D) async throws -> \$N {", "}",
ClientRuntimeTypes.Http.HttpResponse,
ClientRuntimeTypes.Serde.ResponseDecoder,
SwiftTypes.Error
with(writer) {
addImport(AWSSwiftDependency.AWS_CLIENT_RUNTIME.target)
addImport(SwiftDependency.CLIENT_RUNTIME.target)

openBlock(
"public enum \$L: \$N {",
"}",
operationErrorName,
ClientRuntimeTypes.Http.HttpResponseErrorBinding
) {
writer.write(
"let restJSONError = try await \$N(httpResponse: httpResponse)",
AWSClientRuntimeTypes.RestJSON.RestJSONError
)
writer.write("let requestID = httpResponse.requestId")
writer.openBlock("switch restJSONError.errorType {", "}") {
val errorShapes = op.errors.map { ctx.model.expectShape(it) as StructureShape }.toSet().sorted()
for (errorShape in errorShapes) {
var errorShapeName = errorShape.errorShapeName(ctx.symbolProvider)
var errorShapeType = ctx.symbolProvider.toSymbol(errorShape).name
writer.write(
"case \$S: return try await \$L(httpResponse: httpResponse, decoder: decoder, message: restJSONError.errorMessage, requestID: requestID)",
errorShapeName,
errorShapeType
openBlock(
"public static func makeError(httpResponse: \$N, decoder: \$D) async throws -> \$N {", "}",
ClientRuntimeTypes.Http.HttpResponse,
ClientRuntimeTypes.Serde.ResponseDecoder,
SwiftTypes.Error
) {
write(
"let restJSONError = try await \$N(httpResponse: httpResponse)",
AWSClientRuntimeTypes.RestJSON.RestJSONError
)
write("let requestID = httpResponse.requestId")

if (ctx.service.errors.isNotEmpty()) {
write("let serviceError = try await ${ctx.symbolProvider.toSymbol(ctx.service).name}Types.makeServiceError(httpResponse, decoder, restJSONError, requestID)")
write("if let error = serviceError { return error }")
}

openBlock("switch restJSONError.errorType {", "}") {
val errorShapes = op.errors
.map { ctx.model.expectShape(it) as StructureShape }
.toSet()
.sorted()
errorShapes.forEach { errorShape ->
var errorShapeName = errorShape.errorShapeName(ctx.symbolProvider)
var errorShapeType = ctx.symbolProvider.toSymbol(errorShape).name
write(
"case \$S: return try await \$L(httpResponse: httpResponse, decoder: decoder, message: restJSONError.errorMessage, requestID: requestID)",
errorShapeName,
errorShapeType
)
}
write(
"default: return try await \$N.makeError(httpResponse: httpResponse, message: restJSONError.errorMessage, requestID: requestID, typeName: restJSONError.errorType)",
unknownServiceErrorSymbol
)
}
writer.write("default: return try await \$N.makeError(httpResponse: httpResponse, message: restJSONError.errorMessage, requestID: requestID, typeName: restJSONError.errorType)", unknownServiceErrorSymbol)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package software.amazon.smithy.aws.swift.codegen.ec2query.httpResponse

import software.amazon.smithy.aws.swift.codegen.AWSClientRuntimeTypes
import software.amazon.smithy.aws.swift.codegen.AWSSwiftDependency
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.OperationShape
Expand All @@ -18,7 +19,49 @@ import software.amazon.smithy.swift.codegen.model.toUpperCamelCase
import software.amazon.smithy.swift.codegen.utils.errorShapeName

class AWSEc2QueryHttpResponseBindingErrorGenerator : HttpResponseBindingErrorGeneratable {
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, unknownServiceErrorSymbol: Symbol) {
override fun renderServiceError(ctx: ProtocolGenerator.GenerationContext) {
val serviceShape = ctx.service
val serviceName = ctx.service.id.name
val rootNamespace = ctx.settings.moduleName
val fileName = "./$rootNamespace/models/$serviceName+ServiceErrorHelperMethod.swift"

ctx.delegator.useFileWriter(fileName) { writer ->
with(writer) {
addImport(AWSSwiftDependency.AWS_CLIENT_RUNTIME.target)
addImport(SwiftDependency.CLIENT_RUNTIME.target)
openBlock("extension ${ctx.symbolProvider.toSymbol(ctx.service).name}Types {", "}") {
openBlock(
"static func makeServiceError(_ httpResponse: \$N, _ decoder: \$D, _ error: \$N) async throws -> \$N? {",
"}",
ClientRuntimeTypes.Http.HttpResponse,
ClientRuntimeTypes.Serde.ResponseDecoder,
AWSClientRuntimeTypes.EC2Query.Ec2QueryError,
SwiftTypes.Error
) {
openBlock("switch error.errorCode {", "}") {
val serviceErrorShapes =
serviceShape.errors
.map { ctx.model.expectShape(it) as StructureShape }
.toSet()
.sorted()
serviceErrorShapes.forEach { errorShape ->
val errorShapeName = errorShape.errorShapeName(ctx.symbolProvider)
val errorShapeType = ctx.symbolProvider.toSymbol(errorShape).name
write(
"case \$S: return try await \$L(httpResponse: httpResponse, decoder: decoder, message: error.message, requestID: error.requestId)",
errorShapeName,
errorShapeType
)
}
write("default: return nil")
}
}
}
}
}
}

override fun renderOperationError(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, unknownServiceErrorSymbol: Symbol) {
val operationErrorName = "${op.toUpperCamelCase()}OutputError"
val rootNamespace = ctx.settings.moduleName
val httpBindingSymbol = Symbol.builder()
Expand All @@ -27,25 +70,48 @@ class AWSEc2QueryHttpResponseBindingErrorGenerator : HttpResponseBindingErrorGen
.build()

ctx.delegator.useShapeWriter(httpBindingSymbol) { writer ->
writer.addImport(AWSSwiftDependency.AWS_CLIENT_RUNTIME.target)
writer.addImport(SwiftDependency.CLIENT_RUNTIME.target)

writer.openBlock("public enum \$L: \$N {", "}", operationErrorName, ClientRuntimeTypes.Http.HttpResponseErrorBinding) {
writer.openBlock(
"public static func makeError(httpResponse: \$N, decoder: \$D) async throws -> \$N {", "}",
ClientRuntimeTypes.Http.HttpResponse,
ClientRuntimeTypes.Serde.ResponseDecoder,
SwiftTypes.Error
with(writer) {
addImport(AWSSwiftDependency.AWS_CLIENT_RUNTIME.target)
addImport(SwiftDependency.CLIENT_RUNTIME.target)

openBlock(
"public enum \$L: \$N {",
"}",
operationErrorName,
ClientRuntimeTypes.Http.HttpResponseErrorBinding
) {
writer.write("let ec2QueryError = try await Ec2QueryError(httpResponse: httpResponse)")
writer.openBlock("switch ec2QueryError.errorCode {", "}") {
val errorShapes = op.errors.map { ctx.model.expectShape(it) as StructureShape }.toSet().sorted()
for (errorShape in errorShapes) {
var errorShapeName = errorShape.errorShapeName(ctx.symbolProvider)
var errorShapeType = ctx.symbolProvider.toSymbol(errorShape).name
writer.write("case \$S: return try await \$L(httpResponse: httpResponse, decoder: decoder, message: ec2QueryError.message, requestID: ec2QueryError.requestId)", errorShapeName, errorShapeType)
openBlock(
"public static func makeError(httpResponse: \$N, decoder: \$D) async throws -> \$N {", "}",
ClientRuntimeTypes.Http.HttpResponse,
ClientRuntimeTypes.Serde.ResponseDecoder,
SwiftTypes.Error
) {
write("let ec2QueryError = try await Ec2QueryError(httpResponse: httpResponse)")

if (ctx.service.errors.isNotEmpty()) {
write("let serviceError = try await ${ctx.symbolProvider.toSymbol(ctx.service).name}Types.makeServiceError(httpResponse, decoder, ec2QueryError)")
write("if let error = serviceError { return error }")
}

openBlock("switch ec2QueryError.errorCode {", "}") {
val errorShapes = op.errors
.map { ctx.model.expectShape(it) as StructureShape }
.toSet()
.sorted()
errorShapes.forEach { errorShape ->
var errorShapeName = errorShape.errorShapeName(ctx.symbolProvider)
var errorShapeType = ctx.symbolProvider.toSymbol(errorShape).name
write(
"case \$S: return try await \$L(httpResponse: httpResponse, decoder: decoder, message: ec2QueryError.message, requestID: ec2QueryError.requestId)",
errorShapeName,
errorShapeType
)
}
write(
"default: return try await \$N.makeError(httpResponse: httpResponse, message: ec2QueryError.message, requestID: ec2QueryError.requestId, typeName: ec2QueryError.errorCode)",
unknownServiceErrorSymbol
)
}
writer.write("default: return try await \$N.makeError(httpResponse: httpResponse, message: ec2QueryError.message, requestID: ec2QueryError.requestId, typeName: ec2QueryError.errorCode)", unknownServiceErrorSymbol)
}
}
}
Expand Down
Loading

0 comments on commit 806ca22

Please sign in to comment.