diff --git a/codegen-test/dynamo-it/src/main.rs b/codegen-test/dynamo-it/src/main.rs index be1208afc67c75a9b8b4e67cf827ebe4d04f4088..b955169bcf4ef48fca6755067e20342e9ceadd7b 100644 --- a/codegen-test/dynamo-it/src/main.rs +++ b/codegen-test/dynamo-it/src/main.rs @@ -64,9 +64,6 @@ async fn main() -> Result<(), Box> { ) .build(&config); - let body = String::from_utf8(create_table.build_http_request().body().clone()).unwrap(); - println!("{}", body); - let response = io_v0::dispatch!(client, create_table); match response.parsed { Some(Ok(output)) => { @@ -83,9 +80,7 @@ async fn main() -> Result<(), Box> { let tables = io_v0::dispatch!( client, dynamo::operation::ListTables::builder().build(&config) - ) - .parsed - .unwrap(); + ).parsed.unwrap(); println!( "current tables: {:?}", &tables.as_ref().unwrap().table_names 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 c838ad67d1039a34f3714db384aca9c1b5aaae5c..531248245fd1590efe25db9c19a8205787869daa 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 @@ -173,5 +173,6 @@ data class CargoDependency( val SerdeJson: CargoDependency = CargoDependency("serde_json", CratesIo("1"), features = listOf("float_roundtrip")) val Serde = CargoDependency("serde", CratesIo("1"), features = listOf("derive")) + val Bytes: RustDependency = CargoDependency("bytes", CratesIo("1")) } } 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 77d43c8b8a08986d1471e70941ac60fc6a1ecd8f..d43419d87332133134e457845b329f088985f35d 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 @@ -145,6 +145,12 @@ data class RuntimeType(val name: String?, val dependency: RustDependency?, val n val Config = RuntimeType("config", null, "crate") + fun operation(runtimeConfig: RuntimeConfig) = RuntimeType("Operation", dependency = CargoDependency.SmithyHttp(runtimeConfig), namespace = "smithy_http::operation") + fun operationModule(runtimeConfig: RuntimeConfig) = RuntimeType(null, dependency = CargoDependency.SmithyHttp(runtimeConfig), namespace = "smithy_http::operation") + fun sdkBody(runtimeConfig: RuntimeConfig): RuntimeType = RuntimeType("SdkBody", dependency = CargoDependency.SmithyHttp(runtimeConfig), "smithy_http::body") + fun parseStrict(runtimeConfig: RuntimeConfig) = RuntimeType("ParseStrictResponse", dependency = CargoDependency.SmithyHttp(runtimeConfig), namespace = "smithy_http::response") + + val Bytes = RuntimeType("Bytes", dependency = CargoDependency.Bytes, namespace = "bytes") fun BlobSerde(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.blobSerde(runtimeConfig)) private fun forInlineDependency(inlineDependency: InlineDependency) = diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGenerator.kt index b9b56d79702fd72cde50c15f987344d2a774dc3b..8ca39aafa57dfb421abbb87062471af5bcb49ab5 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/BuilderGenerator.kt @@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.rustlang.conditionalBlock import software.amazon.smithy.rust.codegen.rustlang.docs import software.amazon.smithy.rust.codegen.rustlang.documentShape import software.amazon.smithy.rust.codegen.rustlang.render +import software.amazon.smithy.rust.codegen.rustlang.rust import software.amazon.smithy.rust.codegen.rustlang.rustBlock import software.amazon.smithy.rust.codegen.rustlang.stripOuter import software.amazon.smithy.rust.codegen.rustlang.withBlock @@ -23,6 +24,7 @@ import software.amazon.smithy.rust.codegen.smithy.RuntimeType import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.smithy.defaultValue import software.amazon.smithy.rust.codegen.smithy.isOptional +import software.amazon.smithy.rust.codegen.smithy.letIf import software.amazon.smithy.rust.codegen.smithy.makeOptional import software.amazon.smithy.rust.codegen.smithy.rustType import software.amazon.smithy.rust.codegen.util.dq @@ -64,19 +66,35 @@ class OperationInputBuilderGenerator( ) : BuilderGenerator(model, symbolProvider, shape.inputShape(model)) { override fun buildFn(implBlockWriter: RustWriter) { val fallibleBuilder = StructureGenerator.fallibleBuilder(shape.inputShape(model), symbolProvider) - val returnType = when (fallibleBuilder) { - true -> "Result<#T, String>" - false -> "#T" - } + val retryType = "()" + val returnType = "#T<#{T}, $retryType>".letIf(fallibleBuilder) { "Result<$it, String>" } val outputSymbol = symbolProvider.toSymbol(shape) + val operationT = RuntimeType.operation(symbolProvider.config().runtimeConfig) + val operationModule = RuntimeType.operationModule(symbolProvider.config().runtimeConfig) + val sdkBody = RuntimeType.sdkBody(symbolProvider.config().runtimeConfig) - implBlockWriter.docs("Consumes the builder and constructs a #D", outputSymbol) - implBlockWriter.rustBlock("pub fn build(self, _config: &#T::Config) -> $returnType", RuntimeType.Config, outputSymbol) { - conditionalBlock("Ok(", ")", conditional = fallibleBuilder) { - // If a wrapper is specified, use the `::new` associated function to construct the wrapper - withBlock("#T::new(", ")", outputSymbol) { + implBlockWriter.docs("Consumes the builder and constructs an Operation<#D>", outputSymbol) + implBlockWriter.rustBlock("pub fn build(self, _config: &#T::Config) -> $returnType", RuntimeType.Config, operationT, outputSymbol) { + conditionalBlock("Ok({", "})", conditional = fallibleBuilder) { + withBlock("let op = #T::new(", ");", outputSymbol) { coreBuilder(this) } + rust( + """ + ##[allow(unused_mut)] + let mut request = #T::Request::new(op.build_http_request().map(#T::from)); + """, + operationModule, sdkBody + ) + rust( + """ + #T::new( + request, + op + ) + """, + operationT + ) } } } 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 1d9b371bc0a97f547290d4eb8da638efb67e5554..d9aaf5d0f466b4a48932d52abdf63b27020d54d1 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 @@ -101,6 +101,7 @@ abstract class HttpProtocolGenerator( write("Self { input }") } } + traitImplementations(operationWriter, operationShape) } protected fun httpBuilderFun(implBlockWriter: RustWriter, f: RustWriter.() -> Unit) { @@ -131,6 +132,8 @@ abstract class HttpProtocolGenerator( } } + abstract fun traitImplementations(operationWriter: RustWriter, operationShape: OperationShape) + abstract fun fromResponseImpl(implBlockWriter: RustWriter, operationShape: OperationShape) /** diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGenerator.kt index 1db0a8903ec2517d0af128ae9339284c4c30cc77..21aee19022f2238e03b3d23cc4fd7fbdc6ff4746 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGenerator.kt @@ -151,11 +151,7 @@ class HttpProtocolTestGenerator( writeInline("let input =") instantiator.render(this, inputShape, httpRequestTestCase.params) write(";") - if (protocolSupport.requestBodySerialization) { - write("let http_request = input.build_http_request();") - } else { - write("let http_request = ${protocolConfig.symbolProvider.toSymbol(inputShape).name}::assemble(input.input.request_builder_base(), vec![]);") - } + write("let (http_request, _) = input.into_request_response().0.into_parts();") with(httpRequestTestCase) { write( """ @@ -246,14 +242,15 @@ class HttpProtocolTestGenerator( } private fun checkBody(rustWriter: RustWriter, body: String, mediaType: String?) { + rustWriter.write("""let body = http_request.body().bytes().expect("body should be strict");""") if (body == "") { rustWriter.write("// No body") - rustWriter.write("assert!(&http_request.body().is_empty());") + rustWriter.write("assert!(&body.is_empty());") } else { // When we generate a body instead of a stub, drop the trailing `;` and enable the assertion assertOk(rustWriter) { rustWriter.write( - "#T(&http_request.body(), ${ + "#T(&body, ${ rustWriter.escape(body).dq() }, #T::from(${(mediaType ?: "unknown").dq()}))", RuntimeType.ProtocolTestHelper(protocolConfig.runtimeConfig, "validate_body"), diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsJson10.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsJson10.kt index 65445a203279d4f4badbfd1aa984cc910a2d0288..70b7dcf5311dd27b3afd0ee549e439350d6eaf42 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsJson10.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsJson10.kt @@ -37,6 +37,7 @@ import software.amazon.smithy.rust.codegen.smithy.traits.SyntheticOutputTrait import software.amazon.smithy.rust.codegen.smithy.transformers.OperationNormalizer import software.amazon.smithy.rust.codegen.smithy.transformers.StructureModifier import software.amazon.smithy.rust.codegen.util.dq +import software.amazon.smithy.rust.codegen.util.outputShape sealed class AwsJsonVersion { abstract val value: String @@ -136,6 +137,27 @@ class BasicAwsJsonGenerator( private val awsJsonVersion: AwsJsonVersion ) : HttpProtocolGenerator(protocolConfig) { private val model = protocolConfig.model + override fun traitImplementations(operationWriter: RustWriter, operationShape: OperationShape) { + // All AWS JSON protocols do NOT support streaming shapes + val outputSymbol = symbolProvider.toSymbol(operationShape.outputShape(model)) + val operationName = symbolProvider.toSymbol(operationShape).name + operationWriter.rustTemplate( + """ + impl #{parse_strict} for $operationName { + type Output = Result<#{output}, #{error}>; + fn parse(&self, response: &#{response}<#{bytes}>) -> Self::Output { + self.parse_response(response) + } + } + """, + "parse_strict" to RuntimeType.parseStrict(symbolProvider.config().runtimeConfig), + "output" to outputSymbol, + "error" to operationShape.errorSymbol(symbolProvider), + "response" to RuntimeType.Http("Response"), + "bytes" to RuntimeType.Bytes + ) + } + private val symbolProvider = protocolConfig.symbolProvider private val operationIndex = OperationIndex.of(model) override fun toHttpRequestImpl( diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsRestJson.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsRestJson.kt index 7507936a2374a7a3acd3d89fbbdec36ead7c450f..81ff255dce00fe62900e161410024081ecfa0dcf 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsRestJson.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/AwsRestJson.kt @@ -50,6 +50,10 @@ class AwsRestJsonGenerator( // restJson1 requires all operations to use the HTTP trait private val model = protocolConfig.model + override fun traitImplementations(operationWriter: RustWriter, operationShape: OperationShape) { + // TODO: Implement parsing traits for AwsRestJson + } + override fun fromResponseImpl(implBlockWriter: RustWriter, operationShape: OperationShape) { fromResponseFun(implBlockWriter, operationShape) { // avoid non-usage warnings @@ -64,7 +68,7 @@ class AwsRestJsonGenerator( override fun toBodyImpl(implBlockWriter: RustWriter, inputShape: StructureShape, inputBody: StructureShape?) { bodyBuilderFun(implBlockWriter) { - write("todo!()") + rust(""""body not generated yet".into()""") } } diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGeneratorTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGeneratorTest.kt index 1c1fd302e0ac095eeef581ed995ed2b58e1e98f6..294812c65991fdfe125a93ecb11f978c53d8f45c 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGeneratorTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/HttpProtocolTestGeneratorTest.kt @@ -116,6 +116,10 @@ class HttpProtocolTestGeneratorTest { // A stubbed test protocol to do enable testing intentionally broken protocols class TestProtocol(protocolConfig: ProtocolConfig) : HttpProtocolGenerator(protocolConfig) { + override fun traitImplementations(operationWriter: RustWriter, operationShape: OperationShape) { + // no trait implementations for tests + } + override fun fromResponseImpl(implBlockWriter: RustWriter, operationShape: OperationShape) { fromResponseFun(implBlockWriter, operationShape) { writeWithNoFormatting(correctResponse) diff --git a/rust-runtime/io-v0/src/lib.rs b/rust-runtime/io-v0/src/lib.rs index ff3188ead2eea70567b1d4d4f9e6f9012fdd9bdc..03e60a52795ecd960d51ff5537bb54edd6ee1d43 100644 --- a/rust-runtime/io-v0/src/lib.rs +++ b/rust-runtime/io-v0/src/lib.rs @@ -25,7 +25,9 @@ macro_rules! dispatch { ($client:expr, $input:expr) => {{ use $crate::http; let inp = $input; - let request = inp.build_http_request(); + let (request, handler) = inp.into_request_response(); + let request = request.into_parts().0; + let request: http::Request> = request.map(|body| body.bytes().unwrap().into()); let request = $crate::prepare_request(&$client, request); let response = $client.http_client.request(request).await; match response { @@ -35,7 +37,7 @@ macro_rules! dispatch { parsed: None, }, Ok(resp) => { - let parsed = Some(inp.parse_response(&resp)); + let parsed = Some(handler.parse_response(&resp)); $crate::ApiResponse { raw: $crate::Raw::Response(resp).convert_to_str(), parsed, @@ -166,29 +168,3 @@ async fn read_body(body: B) -> Result, B::Error> { } Ok(output) } - -#[cfg(test)] -mod tests { - use crate::Client; - use hyper::http; - use std::error::Error; - - struct TestOperation; - - impl TestOperation { - pub fn build_http_request(&self) -> http::Request> { - http::Request::new(vec![]) - } - - pub fn parse_response(&self, _: &http::Response>) -> u32 { - 5 - } - } - - #[tokio::test] - async fn make_test_request() -> Result<(), Box> { - let client = Client::local("dynamodb"); - let _response = dispatch!(client, TestOperation); - Ok(()) - } -}