diff --git a/aws/rust-runtime/aws-inlineable/Cargo.toml b/aws/rust-runtime/aws-inlineable/Cargo.toml new file mode 100644 index 000000000..05de299c5 --- /dev/null +++ b/aws/rust-runtime/aws-inlineable/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "inlineable-aws" +version = "0.1.0" +authors = ["Russell Cohen "] +edition = "2018" +description = """ +The modules of this crate are intended to be inlined directly into the SDK as needed. The dependencies here +are to allow this crate to be compilable and testable in isolation, no client code actually takes these dependencies. +""" + +[dependencies] +smithy-xml = { path = "../../../rust-runtime/smithy-xml" } +smithy-types = { path = "../../../rust-runtime/smithy-types" } +http = "0.2.4" diff --git a/aws/rust-runtime/aws-inlineable/src/lib.rs b/aws/rust-runtime/aws-inlineable/src/lib.rs new file mode 100644 index 000000000..d19a64870 --- /dev/null +++ b/aws/rust-runtime/aws-inlineable/src/lib.rs @@ -0,0 +1,7 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#[allow(dead_code)] +mod s3_errors; diff --git a/aws/rust-runtime/aws-inlineable/src/s3_errors.rs b/aws/rust-runtime/aws-inlineable/src/s3_errors.rs new file mode 100644 index 000000000..b6a5eb1a2 --- /dev/null +++ b/aws/rust-runtime/aws-inlineable/src/s3_errors.rs @@ -0,0 +1,72 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +const EXTENDED_REQUEST_ID: &str = "s3_extended_request_id"; + +pub trait ErrorExt { + fn extended_request_id(&self) -> Option<&str>; +} + +impl ErrorExt for smithy_types::Error { + fn extended_request_id(&self) -> Option<&str> { + self.extra(EXTENDED_REQUEST_ID) + } +} + +pub fn parse_extended_error( + error: smithy_types::Error, + response: &http::Response, +) -> smithy_types::Error { + let mut builder = error.into_builder(); + let host_id = response + .headers() + .get("x-amz-id-2") + .and_then(|header_value| header_value.to_str().ok()); + if let Some(host_id) = host_id { + builder.custom(EXTENDED_REQUEST_ID, host_id); + } + builder.build() +} + +#[cfg(test)] +mod test { + use crate::s3_errors::{parse_extended_error, ErrorExt}; + + #[test] + fn add_error_fields() { + let resp = http::Response::builder() + .header( + "x-amz-id-2", + "eftixk72aD6Ap51TnqcoF8eFidJG9Z/2mkiDFu8yU9AS1ed4OpIszj7UDNEHGran", + ) + .status(400) + .body("") + .unwrap(); + let error = smithy_types::Error::builder() + .message("123") + .request_id("456") + .build(); + + let error = parse_extended_error(error, &resp); + assert_eq!( + error + .extended_request_id() + .expect("extended request id should be set"), + "eftixk72aD6Ap51TnqcoF8eFidJG9Z/2mkiDFu8yU9AS1ed4OpIszj7UDNEHGran" + ); + } + + #[test] + fn handle_missing_header() { + let resp = http::Response::builder().status(400).body("").unwrap(); + let error = smithy_types::Error::builder() + .message("123") + .request_id("456") + .build(); + + let error = parse_extended_error(error, &resp); + assert_eq!(error.extended_request_id(), None); + } +} diff --git a/aws/rust-runtime/build.gradle.kts b/aws/rust-runtime/build.gradle.kts new file mode 100644 index 000000000..b90ee1f81 --- /dev/null +++ b/aws/rust-runtime/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +description = "Rust Runtime" +plugins { + kotlin("jvm") +} + +group = "software.amazon.aws.rustruntime" + +version = "0.0.3" + +tasks.jar { + from("./") { + include("aws-inlineable/src/*.rs") + include("aws-inlineable/Cargo.toml") + } +} diff --git a/aws/sdk-codegen/build.gradle.kts b/aws/sdk-codegen/build.gradle.kts index 4c11724ba..70008732e 100644 --- a/aws/sdk-codegen/build.gradle.kts +++ b/aws/sdk-codegen/build.gradle.kts @@ -21,6 +21,7 @@ val kotestVersion: String by project dependencies { implementation(project(":codegen")) + runtimeOnly(project(":aws:rust-runtime")) implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion") implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") testImplementation("org.junit.jupiter:junit-jupiter:5.6.1") diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt index 8beeb1ba3..11036ff0f 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt @@ -6,7 +6,8 @@ package software.amazon.smithy.rustsdk import software.amazon.smithy.rust.codegen.smithy.customize.CombinedCodegenDecorator -import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayCustomizationDecorator +import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayDecorator +import software.amazon.smithy.rustsdk.customize.s3.S3Decorator val DECORATORS = listOf( CredentialsProviderDecorator(), @@ -17,8 +18,9 @@ val DECORATORS = listOf( RetryPolicyDecorator(), IntegrationTestDecorator(), FluentClientDecorator(), - ApiGatewayCustomizationDecorator(), - CrateLicenseDecorator() + ApiGatewayDecorator(), + CrateLicenseDecorator(), + S3Decorator() ) class AwsCodegenDecorator : CombinedCodegenDecorator(DECORATORS) { diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeDependency.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeDependency.kt index c0fda5127..4a2c40c22 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeDependency.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeDependency.kt @@ -6,25 +6,31 @@ package software.amazon.smithy.rustsdk import software.amazon.smithy.rust.codegen.rustlang.CargoDependency -import software.amazon.smithy.rust.codegen.rustlang.Local import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.smithy.RuntimeCrateLocation +import software.amazon.smithy.rust.codegen.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.smithy.crateLocation import java.io.File import java.nio.file.Path -fun RuntimeConfig.awsRoot(): String { - - check(runtimeCrateLocation is RuntimeCrateLocation.Path) { "cannot run tests on versioned runtime dependencies" } - val cratePath = (runtimeCrateLocation as RuntimeCrateLocation.Path).path - val asPath = Path.of(cratePath) - val path = if (asPath.isAbsolute) { - asPath.parent.resolve("aws/rust-runtime").toAbsolutePath().toString() - } else { - cratePath +fun RuntimeConfig.awsRoot(): RuntimeCrateLocation = when (runtimeCrateLocation) { + is RuntimeCrateLocation.Path -> { + val cratePath = (runtimeCrateLocation as RuntimeCrateLocation.Path).path + val asPath = Path.of(cratePath) + val path = if (asPath.isAbsolute) { + asPath.parent.resolve("aws/rust-runtime").toAbsolutePath().toString() + } else { + cratePath + } + check(File(path).exists()) { "$path must exist to generate a working SDK" } + RuntimeCrateLocation.Path(path) } - check(File(path).exists()) { "$path must exist to generate a working SDK" } - return path + is RuntimeCrateLocation.Versioned -> runtimeCrateLocation +} + +object AwsRuntimeType { + val S3Errors by lazy { RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("s3_errors")) } } fun RuntimeConfig.awsRuntimeDependency(name: String, features: List = listOf()): CargoDependency = - CargoDependency(name, Local(awsRoot()), features = features) + CargoDependency(name, awsRoot().crateLocation(), features = features) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/InlineDependency.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/InlineDependency.kt new file mode 100644 index 000000000..61a647413 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/InlineDependency.kt @@ -0,0 +1,12 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package software.amazon.smithy.rustsdk + +import software.amazon.smithy.rust.codegen.rustlang.InlineDependency + +object InlineAwsDependency { + fun forRustFile(file: String): InlineDependency = InlineDependency.Companion.forRustFile(file, "aws-inlineable") +} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayCustomizationDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt similarity index 97% rename from aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayCustomizationDecorator.kt rename to aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt index 21a953694..40d4eb671 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayCustomizationDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt @@ -17,7 +17,7 @@ import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator import software.amazon.smithy.rust.codegen.smithy.generators.ProtocolConfig import software.amazon.smithy.rust.codegen.smithy.letIf -class ApiGatewayCustomizationDecorator : RustCodegenDecorator { +class ApiGatewayDecorator : RustCodegenDecorator { override val name: String = "ApiGateway" override val order: Byte = 0 diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt new file mode 100644 index 000000000..689e62a33 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package software.amazon.smithy.rustsdk.customize.s3 + +import software.amazon.smithy.aws.traits.protocols.RestXmlTrait +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.rust.codegen.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.rustlang.Writable +import software.amazon.smithy.rust.codegen.rustlang.asType +import software.amazon.smithy.rust.codegen.rustlang.rust +import software.amazon.smithy.rust.codegen.rustlang.rustBlockTemplate +import software.amazon.smithy.rust.codegen.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.rustlang.writable +import software.amazon.smithy.rust.codegen.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator +import software.amazon.smithy.rust.codegen.smithy.generators.LibRsCustomization +import software.amazon.smithy.rust.codegen.smithy.generators.LibRsSection +import software.amazon.smithy.rust.codegen.smithy.generators.ProtocolConfig +import software.amazon.smithy.rust.codegen.smithy.letIf +import software.amazon.smithy.rust.codegen.smithy.protocols.ProtocolMap +import software.amazon.smithy.rust.codegen.smithy.protocols.RestXml +import software.amazon.smithy.rust.codegen.smithy.protocols.RestXmlFactory +import software.amazon.smithy.rustsdk.AwsRuntimeType + +/** + * Top level decorator for S3 + * */ +class S3Decorator : RustCodegenDecorator { + override val name: String = "S3ExtendedError" + override val order: Byte = 0 + private fun applies(serviceId: ShapeId) = + serviceId == ShapeId.from("com.amazonaws.s3#AmazonS3") + + override fun protocols(serviceId: ShapeId, currentProtocols: ProtocolMap): ProtocolMap { + return currentProtocols.letIf(applies(serviceId)) { + it + mapOf( + RestXmlTrait.ID to RestXmlFactory { protocolConfig -> + S3(protocolConfig) + } + ) + } + } + + override fun libRsCustomizations( + protocolConfig: ProtocolConfig, + baseCustomizations: List + ): List { + return baseCustomizations.letIf(applies(protocolConfig.serviceShape.id)) { + it + S3PubUse() + } + } +} + +class S3(protocolConfig: ProtocolConfig) : RestXml(protocolConfig) { + private val runtimeConfig = protocolConfig.runtimeConfig + override fun parseGenericError(operationShape: OperationShape): RuntimeType { + return RuntimeType.forInlineFun("parse_generic_error", "xml_deser") { + it.rustBlockTemplate( + "pub fn parse_generic_error(response: &#{Response}<#{Bytes}>) -> Result<#{Error}, #{XmlError}>", + "Response" to RuntimeType.http.member("Response"), + "Bytes" to RuntimeType.Bytes, + "Error" to RuntimeType.GenericError(runtimeConfig), + "XmlError" to CargoDependency.smithyXml(runtimeConfig).asType().member("decode::XmlError") + ) { + rustTemplate( + """ + let base_err = #{base_errors}::parse_generic_error(response.body().as_ref())?; + Ok(#{s3_errors}::parse_extended_error(base_err, &response)) + """, + "base_errors" to restXmlErrors, "s3_errors" to AwsRuntimeType.S3Errors + ) + } + } + } +} + +class S3PubUse : LibRsCustomization() { + override fun section(section: LibRsSection): Writable = when (section) { + is LibRsSection.Body -> writable { rust("pub use #T::ErrorExt;", AwsRuntimeType.S3Errors) } + else -> emptySection + } +} diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml new file mode 100644 index 000000000..96b1b120d --- /dev/null +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -0,0 +1,14 @@ +# This Cargo.toml is unused in generated code. It exists solely to enable these tests to compile in-situ +[package] +name = "s3-tests" +version = "0.1.0" +authors = ["Russell Cohen "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aws-sdk-s3 = { path = "../../build/aws-sdk/s3" } +smithy-http = { path = "../../build/aws-sdk/smithy-http" } +http = "0.2.3" +bytes = "1" diff --git a/aws/sdk/integration-tests/s3/src/lib.rs b/aws/sdk/integration-tests/s3/src/lib.rs new file mode 100644 index 000000000..c6d888f02 --- /dev/null +++ b/aws/sdk/integration-tests/s3/src/lib.rs @@ -0,0 +1,4 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ diff --git a/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs b/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs new file mode 100644 index 000000000..e4eb8e4ad --- /dev/null +++ b/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +use aws_sdk_s3::operation::GetObject; +use aws_sdk_s3::ErrorExt; +use bytes::Bytes; +use smithy_http::response::ParseHttpResponse; + +#[test] +fn deserialize_extended_errors() { + let resp = http::Response::builder() + .header( + "x-amz-id-2", + "gyB+3jRPnrkN98ZajxHXr3u7EFM67bNgSAxexeEHndCX/7GRnfTXxReKUQF28IfP", + ) + .header("x-amz-request-id", "3B3C7C725673C630") + .status(404) + .body( + r#" + + NoSuchKey + The resource you requested does not exist + /mybucket/myfoto.jpg + 4442587FB7D0A2F9 +"#, + ) + .unwrap(); + let err = GetObject::new() + .parse_loaded(&resp.map(Bytes::from)) + .expect_err("status was 404, this is an error"); + assert_eq!( + err.meta().extended_request_id(), + Some("gyB+3jRPnrkN98ZajxHXr3u7EFM67bNgSAxexeEHndCX/7GRnfTXxReKUQF28IfP") + ); + assert_eq!(err.meta().request_id(), Some("4442587FB7D0A2F9")); +} diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt index 5f65c967b..59d52bebf 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt @@ -72,18 +72,22 @@ class InlineDependency( companion object { fun forRustFile( name: String, + baseDir: String, vararg additionalDependencies: RustDependency ): InlineDependency { val module = name val filename = "$name.rs" // The inline crate is loaded as a dependency on the runtime classpath - val rustFile = this::class.java.getResource("/inlineable/src/$filename") - check(rustFile != null) { "Rust file $filename was missing from the resource bundle!" } + val rustFile = this::class.java.getResource("/$baseDir/src/$filename") + check(rustFile != null) { "Rust file /$baseDir/src/$filename was missing from the resource bundle!" } return InlineDependency(name, module, additionalDependencies.toList()) { writer -> writer.raw(rustFile.readText()) } } + private fun forRustFile(name: String, vararg additionalDependencies: RustDependency) = + forRustFile(name, "inlineable", *additionalDependencies) + fun awsJsonErrors(runtimeConfig: RuntimeConfig) = forRustFile("aws_json_errors", CargoDependency.Http, CargoDependency.SmithyTypes(runtimeConfig)) diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt index 3ae8fe908..5e9590a4e 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt @@ -70,7 +70,7 @@ fun T.rust( @Language("Rust", prefix = "macro_rules! foo { () => {{ ", suffix = "}}}") contents: String, vararg args: Any ) { - this.write(contents, *args) + this.write(contents.trim(), *args) } /** diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt index 6ce681da7..2eba3ecbb 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt @@ -174,12 +174,12 @@ data class RuntimeType(val name: String?, val dependency: RustDependency?, val n fun awsJsonErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.awsJsonErrors(runtimeConfig)) - val DocJson = forInlineDependency(InlineDependency.docJson()) + val DocJson by lazy { forInlineDependency(InlineDependency.docJson()) } - val InstantEpoch = forInlineDependency(InlineDependency.instantEpoch()) - val InstantHttpDate = forInlineDependency(InlineDependency.instantHttpDate()) - val Instant8601 = forInlineDependency(InlineDependency.instant8601()) - val IdempotencyToken = forInlineDependency(InlineDependency.idempotencyToken()) + val InstantEpoch by lazy { forInlineDependency(InlineDependency.instantEpoch()) } + val InstantHttpDate by lazy { forInlineDependency(InlineDependency.instantHttpDate()) } + val Instant8601 by lazy { forInlineDependency(InlineDependency.instant8601()) } + val IdempotencyToken by lazy { forInlineDependency(InlineDependency.idempotencyToken()) } val Config = RuntimeType("config", null, "crate") @@ -207,7 +207,7 @@ data class RuntimeType(val name: String?, val dependency: RustDependency?, val n val Bytes = RuntimeType("Bytes", dependency = CargoDependency.Bytes, namespace = "bytes") fun BlobSerde(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.blobSerde(runtimeConfig)) - private fun forInlineDependency(inlineDependency: InlineDependency) = + fun forInlineDependency(inlineDependency: InlineDependency) = RuntimeType(inlineDependency.name, inlineDependency, namespace = "crate") fun forInlineFun(name: String, module: String, func: (RustWriter) -> Unit) = RuntimeType( diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolGenerator.kt index 2c54a5fb3..55548b3f7 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolGenerator.kt @@ -64,9 +64,6 @@ abstract class HttpProtocolGenerator( operationShape: OperationShape, customizations: List ) { - /* if (operationShape.hasTrait()) { - TODO("https://github.com/awslabs/smithy-rs/issues/197") - } */ val inputShape = operationShape.inputShape(model) val sdkId = protocolConfig.serviceShape.getTrait()?.sdkId?.toLowerCase()?.replace(" ", "") @@ -162,7 +159,7 @@ abstract class HttpProtocolGenerator( withBlock("Ok({", "})") { features.forEach { it.section(OperationSection.MutateInput("self", "_config"))(this) } rust("let request = self.request_builder_base()?;") - withBlock("let body = ", ";") { + withBlock("let body =", ";") { body("self", shape) } rust("let request = Self::assemble(request, body);") diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/error/CombinedErrorGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/error/CombinedErrorGenerator.kt index f06c5a987..4d1a2fdb6 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/error/CombinedErrorGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/error/CombinedErrorGenerator.kt @@ -121,24 +121,25 @@ class CombinedErrorGenerator( writer.rustTemplate( """ - impl ${symbol.name} { - pub fn new(kind: ${symbol.name}Kind, meta: #{generic_error}) -> Self { - Self { kind, meta } - } + impl ${symbol.name} { + pub fn new(kind: ${symbol.name}Kind, meta: #{generic_error}) -> Self { + Self { kind, meta } + } - pub fn unhandled(err: impl Into>) -> Self { - Self { - kind: ${symbol.name}Kind::Unhandled(err.into()), - meta: Default::default() - } - } - pub fn generic(err: #{generic_error}) -> Self { - Self { - meta: err.clone(), - kind: ${symbol.name}Kind::Unhandled(err.into()), - } + pub fn unhandled(err: impl Into>) -> Self { + Self { + kind: ${symbol.name}Kind::Unhandled(err.into()), + meta: Default::default() } + } + + pub fn generic(err: #{generic_error}) -> Self { + Self { + meta: err.clone(), + kind: ${symbol.name}Kind::Unhandled(err.into()), + } + } // Consider if this should actually be `Option>`. This would enable us to use display as implemented // by std::Error to generate a message in that case. @@ -146,6 +147,10 @@ class CombinedErrorGenerator( self.meta.message() } + pub fn meta(&self) -> &#{generic_error} { + &self.meta + } + pub fn request_id(&self) -> Option<&str> { self.meta.request_id() } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/HttpTraitProtocolGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/HttpTraitProtocolGenerator.kt index df63cb953..fbff5ff89 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/HttpTraitProtocolGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/HttpTraitProtocolGenerator.kt @@ -88,7 +88,7 @@ class HttpTraitProtocolGenerator( val bindings = httpIndex.getRequestBindings(operationShape).toList() val payloadMemberName: String? = bindings.firstOrNull { (_, binding) -> binding.location == HttpBinding.Location.PAYLOAD }?.first - if (payloadMemberName == null) { + return if (payloadMemberName == null) { serializerGenerator.operationSerializer(operationShape)?.let { serializer -> rust( "#T(&self).map_err(|err|#T::SerializationError(err.into()))?", @@ -96,10 +96,10 @@ class HttpTraitProtocolGenerator( runtimeConfig.operationBuildError() ) } ?: rustTemplate("#{SdkBody}::from(\"\")", *codegenScope) - return BodyMetadata(takesOwnership = false) + BodyMetadata(takesOwnership = false) } else { val member = inputShape.expectMember(payloadMemberName) - return serializeViaPayload(member, serializerGenerator) + serializeViaPayload(member, serializerGenerator) } } @@ -470,8 +470,7 @@ class HttpTraitProtocolGenerator( rust( """ #T(response.headers()) - .map_err(|_|#T::unhandled("Failed to parse ${member.memberName} from header `${binding.locationName}"))? - """, + .map_err(|_|#T::unhandled("Failed to parse ${member.memberName} from header `${binding.locationName}"))?""", fnName, errorSymbol ) } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/RestXml.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/RestXml.kt index 319c3b43f..c91be78e2 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/RestXml.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/RestXml.kt @@ -24,9 +24,12 @@ import software.amazon.smithy.rust.codegen.smithy.transformers.OperationNormaliz import software.amazon.smithy.rust.codegen.smithy.transformers.RemoveEventStreamOperations import software.amazon.smithy.rust.codegen.util.expectTrait -class RestXmlFactory : ProtocolGeneratorFactory { - override fun buildProtocolGenerator(protocolConfig: ProtocolConfig): HttpTraitProtocolGenerator { - return HttpTraitProtocolGenerator(protocolConfig, RestXml(protocolConfig)) +class RestXmlFactory(private val generator: (ProtocolConfig) -> Protocol = { RestXml(it) }) : + ProtocolGeneratorFactory { + override fun buildProtocolGenerator( + protocolConfig: ProtocolConfig + ): HttpTraitProtocolGenerator { + return HttpTraitProtocolGenerator(protocolConfig, generator(protocolConfig)) } override fun transformModel(model: Model): Model { @@ -46,10 +49,10 @@ class RestXmlFactory : ProtocolGeneratorFactory { } } -class RestXml(private val protocolConfig: ProtocolConfig) : Protocol { +open class RestXml(private val protocolConfig: ProtocolConfig) : Protocol { private val restXml = protocolConfig.serviceShape.expectTrait() private val runtimeConfig = protocolConfig.runtimeConfig - private val restXmlErrors: RuntimeType = when (restXml.isNoErrorWrapping) { + protected val restXmlErrors: RuntimeType = when (restXml.isNoErrorWrapping) { true -> RuntimeType.unwrappedXmlErrors(runtimeConfig) false -> RuntimeType.wrappedXmlErrors(runtimeConfig) } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt index e42a45c7a..56ef03868 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/parsers/XmlBindingTraitParserGenerator.kt @@ -227,7 +227,7 @@ class XmlBindingTraitParserGenerator(protocolConfig: ProtocolConfig, private val private fun RustWriter.parseStructureInner(members: XmlMemberIndex, builder: String, outerCtx: Ctx) { members.attributeMembers.forEach { member -> val temp = safeName("attrib") - withBlock("let $temp = ", ";") { + withBlock("let $temp =", ";") { parseAttributeMember(member, outerCtx) } rust("$builder.${symbolProvider.toMemberName(member)} = $temp;") @@ -240,7 +240,7 @@ class XmlBindingTraitParserGenerator(protocolConfig: ProtocolConfig, private val members.dataMembers.forEach { member -> case(member) { val temp = safeName() - withBlock("let $temp = ", ";") { + withBlock("let $temp =", ";") { parseMember( member, ctx.copy(accum = "$builder.${symbolProvider.toMemberName(member)}.take()") @@ -346,7 +346,7 @@ class XmlBindingTraitParserGenerator(protocolConfig: ProtocolConfig, private val Some(_) => return Err(#{XmlError}::custom("mixed variants")) }) """ - withBlock("let tmp = ", ";") { + withBlock("let tmp =", ";") { parseMember(member, ctx.copy(accum = current)) } rust("base = Some(#T::$variantName(tmp));", symbol) diff --git a/rust-runtime/smithy-types/src/lib.rs b/rust-runtime/smithy-types/src/lib.rs index a02982ada..2e84366d2 100644 --- a/rust-runtime/smithy-types/src/lib.rs +++ b/rust-runtime/smithy-types/src/lib.rs @@ -153,6 +153,10 @@ pub mod error { pub fn builder() -> Builder { Builder::default() } + + pub fn into_builder(self) -> Builder { + Builder { inner: self } + } } impl ProvideErrorKind for Error { diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f07b1fd1..65a084618 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,3 +23,4 @@ include(":rust-runtime") include(":aws:sdk-codegen") include(":aws:sdk-codegen-test") include(":aws:sdk") +include(":aws:rust-runtime")