mirror of https://github.com/smithy-lang/smithy-rs
Backfill message & parse without case sensitivity (#565)
* Backfill message & parse without case sensitivity * Allow empty errors for XML protocols
This commit is contained in:
parent
59f9c42ba6
commit
c10899da08
|
@ -30,21 +30,34 @@ val CodegenTests = listOf(
|
|||
CodegenTest("aws.protocoltests.json10#JsonRpc10", "json_rpc10"),
|
||||
CodegenTest("aws.protocoltests.json#JsonProtocol", "json_rpc11"),
|
||||
CodegenTest("aws.protocoltests.restjson#RestJson", "rest_json"),
|
||||
CodegenTest("aws.protocoltests.restjson#RestJsonExtras", "rest_json_extas"),
|
||||
CodegenTest("aws.protocoltests.restxml#RestXml", "rest_xml"),
|
||||
CodegenTest("aws.protocoltests.query#AwsQuery", "aws_query"),
|
||||
CodegenTest("aws.protocoltests.ec2#AwsEc2", "ec2_query"),
|
||||
CodegenTest("aws.protocoltests.restjson#RestJsonExtras", "rest_json_extras"),
|
||||
CodegenTest(
|
||||
"aws.protocoltests.restxml#RestXml", "rest_xml",
|
||||
extraConfig = """, "codegen": { "addMessageToErrors": false } """
|
||||
),
|
||||
|
||||
CodegenTest(
|
||||
"aws.protocoltests.query#AwsQuery", "aws_query",
|
||||
extraConfig = """, "codegen": { "addMessageToErrors": false } """
|
||||
),
|
||||
CodegenTest(
|
||||
"aws.protocoltests.ec2#AwsEc2", "ec2_query",
|
||||
extraConfig = """, "codegen": { "addMessageToErrors": false } """
|
||||
),
|
||||
CodegenTest(
|
||||
"aws.protocoltests.restxml.xmlns#RestXmlWithNamespace",
|
||||
"rest_xml_namespace"
|
||||
"rest_xml_namespace",
|
||||
extraConfig = """, "codegen": { "addMessageToErrors": false } """
|
||||
),
|
||||
CodegenTest(
|
||||
"aws.protocoltests.restxml#RestXmlExtras",
|
||||
"rest_xml_extras"
|
||||
"rest_xml_extras",
|
||||
extraConfig = """, "codegen": { "addMessageToErrors": false } """
|
||||
),
|
||||
CodegenTest(
|
||||
"aws.protocoltests.restxmlunwrapped#RestXmlExtrasUnwrappedErrors",
|
||||
"rest_xml_extras_unwrapped"
|
||||
"rest_xml_extras_unwrapped",
|
||||
extraConfig = """, "codegen": { "addMessageToErrors": false } """
|
||||
),
|
||||
CodegenTest(
|
||||
"crate#Config",
|
||||
|
|
|
@ -62,6 +62,7 @@ service RestJsonExtras {
|
|||
PrimitiveIntOp,
|
||||
EscapedStringValues,
|
||||
NullInNonSparse,
|
||||
CaseInsensitiveErrorOperation
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -268,3 +269,23 @@ structure NullInNonSparseOutput {
|
|||
operation NullInNonSparse {
|
||||
output: NullInNonSparseOutput,
|
||||
}
|
||||
|
||||
@http(uri: "/error-sensitive", method: "POST")
|
||||
operation CaseInsensitiveErrorOperation {
|
||||
errors: [CaseInsensitiveError]
|
||||
}
|
||||
|
||||
@httpResponseTests([
|
||||
{
|
||||
id: "Upper case error modeled lower case",
|
||||
protocol: "aws.protocols#restJson1",
|
||||
code: 500,
|
||||
body: "{\"Message\": \"hello\"}",
|
||||
headers: { "X-Amzn-Errortype": "CaseInsensitiveError" },
|
||||
params: { message: "hello" }
|
||||
}
|
||||
])
|
||||
@error("server")
|
||||
structure CaseInsensitiveError {
|
||||
message: String
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import software.amazon.smithy.rust.codegen.smithy.generators.UnionGenerator
|
|||
import software.amazon.smithy.rust.codegen.smithy.generators.implBlock
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.ProtocolLoader
|
||||
import software.amazon.smithy.rust.codegen.smithy.traits.SyntheticInputTrait
|
||||
import software.amazon.smithy.rust.codegen.smithy.transformers.AddErrorMessage
|
||||
import software.amazon.smithy.rust.codegen.smithy.transformers.RecursiveShapeBoxer
|
||||
import software.amazon.smithy.rust.codegen.util.CommandFailed
|
||||
import software.amazon.smithy.rust.codegen.util.getTrait
|
||||
|
@ -74,7 +75,9 @@ class CodegenVisitor(context: PluginContext, private val codegenDecorator: RustC
|
|||
httpGenerator = protocolGenerator.buildProtocolGenerator(protocolConfig)
|
||||
}
|
||||
|
||||
private fun baselineTransform(model: Model) = model.let(RecursiveShapeBoxer::transform)
|
||||
private fun baselineTransform(model: Model) =
|
||||
model.let(RecursiveShapeBoxer::transform)
|
||||
.letIf(settings.codegenConfig.addMessageToErrors, AddErrorMessage::transform)
|
||||
|
||||
fun execute() {
|
||||
logger.info("generating Rust client...")
|
||||
|
|
|
@ -28,14 +28,31 @@ private const val RUNTIME_CONFIG = "runtimeConfig"
|
|||
private const val CODEGEN_SETTINGS = "codegen"
|
||||
private const val LICENSE = "license"
|
||||
|
||||
data class CodegenConfig(val renameExceptions: Boolean = true, val includeFluentClient: Boolean = true, val formatTimeoutSeconds: Int = 20) {
|
||||
/**
|
||||
* Configuration of codegen settings
|
||||
*
|
||||
* [renameExceptions]: Rename `Exception` to `Error` in the generated SDK
|
||||
* [includeFluentClient]: Generate a `client` module in the generated SDK (currently the AWS SDK sets this to false
|
||||
* and generates its own client)
|
||||
*
|
||||
* [addMessageToErrors]: Adds a `message` field automatically to all error shapes
|
||||
* [formatTimeoutSeconds]: Timeout for running cargo fmt at the end of code generation
|
||||
*/
|
||||
data class CodegenConfig(
|
||||
val renameExceptions: Boolean = true,
|
||||
val includeFluentClient: Boolean = true,
|
||||
val addMessageToErrors: Boolean = true,
|
||||
val formatTimeoutSeconds: Int = 20
|
||||
) {
|
||||
companion object {
|
||||
fun fromNode(node: Optional<ObjectNode>): CodegenConfig {
|
||||
return if (node.isPresent) {
|
||||
CodegenConfig(
|
||||
node.get().getBooleanMemberOrDefault("renameErrors", true),
|
||||
node.get().getBooleanMemberOrDefault("includeFluentClient", true),
|
||||
node.get().getNumberMemberOrDefault("formatTimeoutSeconds", 20).toInt()
|
||||
node.get().getBooleanMemberOrDefault("addMessageToErrors", true),
|
||||
node.get().getNumberMemberOrDefault("formatTimeoutSeconds", 20).toInt(),
|
||||
|
||||
)
|
||||
} else {
|
||||
CodegenConfig()
|
||||
|
|
|
@ -426,6 +426,8 @@ class HttpProtocolTestGenerator(
|
|||
private val AwsJson11 = "aws.protocoltests.json#JsonProtocol"
|
||||
private val RestJson = "aws.protocoltests.restjson#RestJson"
|
||||
private val RestXml = "aws.protocoltests.restxml#RestXml"
|
||||
private val AwsQuery = "aws.protocoltests.query#AwsQuery"
|
||||
private val Ec2Query = "aws.protocoltests.ec2#AwsEc2"
|
||||
private val ExpectFail = setOf<FailingTest>()
|
||||
private val RunOnly: Set<String>? = null
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import software.amazon.smithy.rust.codegen.smithy.RuntimeType.Companion.StdError
|
|||
import software.amazon.smithy.rust.codegen.smithy.RuntimeType.Companion.stdfmt
|
||||
import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider
|
||||
import software.amazon.smithy.rust.codegen.smithy.letIf
|
||||
import software.amazon.smithy.rust.codegen.smithy.transformers.errorMessageMember
|
||||
import software.amazon.smithy.rust.codegen.util.dq
|
||||
import software.amazon.smithy.rust.codegen.util.getTrait
|
||||
|
||||
|
@ -71,9 +72,8 @@ class ErrorGenerator(
|
|||
|
||||
private fun renderError() {
|
||||
val symbol = symbolProvider.toSymbol(shape)
|
||||
val messageShape =
|
||||
shape.getMember("message").or { shape.getMember("Message") }.or { shape.getMember("errorMessage") }
|
||||
val message = messageShape.map { "self.${symbolProvider.toMemberName(it)}.as_deref()" }.orElse("None")
|
||||
val messageShape = shape.errorMessageMember()
|
||||
val message = messageShape?.let { "self.${symbolProvider.toMemberName(it)}.as_deref()" } ?: "None"
|
||||
val errorKindT = RuntimeType.errorKind(symbolProvider.config().runtimeConfig)
|
||||
writer.rustBlock("impl ${symbol.name}") {
|
||||
val retryKindWriteable = shape.modeledRetryKind(error)?.writable(symbolProvider.config().runtimeConfig)
|
||||
|
@ -97,7 +97,7 @@ class ErrorGenerator(
|
|||
"$symbolName [${shape.id.name}]"
|
||||
}
|
||||
write("write!(f, ${errorDesc.dq()})?;")
|
||||
messageShape.map {
|
||||
messageShape?.let {
|
||||
ifSet(it, symbolProvider.toSymbol(it), "&self.message") { field ->
|
||||
write("""write!(f, ": {}", $field)?;""")
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait
|
|||
import software.amazon.smithy.rust.codegen.rustlang.Attribute
|
||||
import software.amazon.smithy.rust.codegen.rustlang.RustWriter
|
||||
import software.amazon.smithy.rust.codegen.rustlang.Writable
|
||||
import software.amazon.smithy.rust.codegen.rustlang.assignment
|
||||
import software.amazon.smithy.rust.codegen.rustlang.rust
|
||||
import software.amazon.smithy.rust.codegen.rustlang.rustBlock
|
||||
import software.amazon.smithy.rust.codegen.rustlang.rustBlockTemplate
|
||||
import software.amazon.smithy.rust.codegen.rustlang.rustTemplate
|
||||
import software.amazon.smithy.rust.codegen.rustlang.withBlock
|
||||
|
@ -37,6 +39,7 @@ import software.amazon.smithy.rust.codegen.smithy.generators.setterName
|
|||
import software.amazon.smithy.rust.codegen.smithy.isOptional
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.parse.StructuredDataParserGenerator
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.serialize.StructuredDataSerializerGenerator
|
||||
import software.amazon.smithy.rust.codegen.smithy.transformers.errorMessageMember
|
||||
import software.amazon.smithy.rust.codegen.util.dq
|
||||
import software.amazon.smithy.rust.codegen.util.expectMember
|
||||
import software.amazon.smithy.rust.codegen.util.hasStreamingMember
|
||||
|
@ -288,7 +291,10 @@ class HttpBoundProtocolGenerator(
|
|||
let error_code = match generic.code() {
|
||||
Some(code) => code,
|
||||
None => return Err(#{error_symbol}::unhandled(generic))
|
||||
};""",
|
||||
};
|
||||
|
||||
let _error_message = generic.message().map(|msg|msg.to_owned());
|
||||
""",
|
||||
"error_symbol" to errorSymbol,
|
||||
)
|
||||
withBlock("Err(match error_code {", "})") {
|
||||
|
@ -296,16 +302,33 @@ class HttpBoundProtocolGenerator(
|
|||
val errorShape = model.expectShape(error, StructureShape::class.java)
|
||||
val variantName = symbolProvider.toSymbol(model.expectShape(error)).name
|
||||
withBlock(
|
||||
"${httpBindingResolver.errorCode(errorShape).dq()} => #1T { meta: generic, kind: #1TKind::$variantName({",
|
||||
"${
|
||||
httpBindingResolver.errorCode(errorShape).dq()
|
||||
} => #1T { meta: generic, kind: #1TKind::$variantName({",
|
||||
"})},",
|
||||
errorSymbol
|
||||
) {
|
||||
renderShapeParser(
|
||||
operationShape,
|
||||
errorShape,
|
||||
httpBindingResolver.errorResponseBindings(errorShape),
|
||||
errorSymbol
|
||||
)
|
||||
Attribute.AllowUnusedMut.render(this)
|
||||
assignment("mut tmp") {
|
||||
rustBlock("") {
|
||||
renderShapeParser(
|
||||
operationShape,
|
||||
errorShape,
|
||||
httpBindingResolver.errorResponseBindings(errorShape),
|
||||
errorSymbol
|
||||
)
|
||||
}
|
||||
}
|
||||
if (errorShape.errorMessageMember() != null) {
|
||||
rust(
|
||||
"""
|
||||
if (&tmp.message).is_none() {
|
||||
tmp.message = _error_message;
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
rust("tmp")
|
||||
}
|
||||
}
|
||||
rust("_ => #T::generic(generic)", errorSymbol)
|
||||
|
|
|
@ -214,6 +214,7 @@ class XmlBindingTraitParserGenerator(
|
|||
xmlError
|
||||
) {
|
||||
val members = errorShape.errorXmlMembers()
|
||||
rust("if inp.is_empty() { return Ok(builder) }")
|
||||
if (members.isNotEmpty()) {
|
||||
rustTemplate(
|
||||
"""
|
||||
|
@ -226,8 +227,6 @@ class XmlBindingTraitParserGenerator(
|
|||
"xml_errors" to xmlErrors
|
||||
)
|
||||
parseStructureInner(members, builder = "builder", Ctx(tag = "error_decoder", accum = null))
|
||||
} else {
|
||||
rust("let _ = inp;")
|
||||
}
|
||||
rust("Ok(builder)")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package software.amazon.smithy.rust.codegen.smithy.transformers
|
||||
|
||||
import software.amazon.smithy.model.Model
|
||||
import software.amazon.smithy.model.shapes.MemberShape
|
||||
import software.amazon.smithy.model.shapes.ShapeId
|
||||
import software.amazon.smithy.model.shapes.StructureShape
|
||||
import software.amazon.smithy.model.traits.ErrorTrait
|
||||
import software.amazon.smithy.model.transform.ModelTransformer
|
||||
import software.amazon.smithy.rust.codegen.util.hasTrait
|
||||
import software.amazon.smithy.rust.codegen.util.orNull
|
||||
import java.util.logging.Logger
|
||||
|
||||
fun StructureShape.errorMessageMember(): MemberShape? = this.getMember("message").or { this.getMember("Message") }.orNull()
|
||||
|
||||
object AddErrorMessage {
|
||||
private val logger = Logger.getLogger("AddErrorMessage")
|
||||
fun transform(model: Model): Model {
|
||||
return ModelTransformer.create().mapShapes(model) { shape ->
|
||||
val addMessageField = shape.hasTrait<ErrorTrait>() && shape is StructureShape && shape.errorMessageMember() == null
|
||||
if (addMessageField && shape is StructureShape) {
|
||||
logger.info("Adding message field to ${shape.id}")
|
||||
shape.toBuilder().addMember("message", ShapeId.from("smithy.api#String")).build()
|
||||
} else {
|
||||
shape
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue