Make `ServerOperationRegistryGenerator` protocol-agnostic (#1525)

`ServerOperationRegistryGenerator` is the only server generator that is
currently not protocol-agnostic, in the sense that it is the only
generator that contains protocol-specific logic, as opposed to
delegating to the `Protocol` interface like other generators do.

With this change, we should be good to implement a
`RustCodegenDecorator` loaded from the classpath that implements support
for any protocol, provided the decorator provides a class implementing
the `Protocol` interface.

This commit also contains some style changes that are making `ktlint`
fail. It seems like our `ktlint` config was recently broken and some
style violations slipped through the cracks in previous commits that
touched these files.
This commit is contained in:
david-perez 2022-07-05 17:05:55 +02:00 committed by GitHub
parent 12b4943e03
commit e751ed6965
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 288 additions and 184 deletions

View File

@ -17,10 +17,10 @@ import software.amazon.smithy.rust.codegen.smithy.BaseSymbolMetadataProvider
import software.amazon.smithy.rust.codegen.smithy.EventStreamSymbolProvider
import software.amazon.smithy.rust.codegen.smithy.ServerCodegenContext
import software.amazon.smithy.rust.codegen.smithy.StreamingShapeMetadataProvider
import software.amazon.smithy.rust.codegen.smithy.StreamingShapeSymbolProvider
import software.amazon.smithy.rust.codegen.smithy.SymbolVisitor
import software.amazon.smithy.rust.codegen.smithy.SymbolVisitorConfig
import software.amazon.smithy.rust.codegen.smithy.customize.CombinedCodegenDecorator
import software.amazon.smithy.rust.codegen.smithy.StreamingShapeSymbolProvider
import java.util.logging.Level
import java.util.logging.Logger

View File

@ -143,8 +143,8 @@ class PythonServerCodegenVisitor(
rustCrate,
protocolGenerator,
protocolGeneratorFactory.support(),
protocolGeneratorFactory.protocol(codegenContext).httpBindingResolver,
codegenContext,
protocolGeneratorFactory.protocol(codegenContext),
codegenContext
)
.render()
}

View File

@ -13,21 +13,21 @@ import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext
import software.amazon.smithy.rust.codegen.smithy.RustCrate
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpBindingResolver
import software.amazon.smithy.rust.codegen.smithy.protocols.Protocol
/**
* PythonServerServiceGenerator
*
* Service generator is the main codegeneration entry point for Smithy services. Individual structures and unions are
* Service generator is the main code generation entry point for Smithy services. Individual structures and unions are
* generated in codegen visitor, but this class handles all protocol-specific code generation (i.e. operations).
*/
class PythonServerServiceGenerator(
private val rustCrate: RustCrate,
protocolGenerator: ProtocolGenerator,
protocolSupport: ProtocolSupport,
httpBindingResolver: HttpBindingResolver,
protocol: Protocol,
private val context: CoreCodegenContext,
) : ServerServiceGenerator(rustCrate, protocolGenerator, protocolSupport, httpBindingResolver, context) {
) : ServerServiceGenerator(rustCrate, protocolGenerator, protocolSupport, protocol, context) {
override fun renderCombinedErrors(writer: RustWriter, operation: OperationShape) {
PythonServerCombinedErrorGenerator(context.model, context.symbolProvider, operation).render(writer)

View File

@ -45,7 +45,7 @@ class RustCodegenServerPlugin : SmithyBuildPlugin {
CombinedCodegenDecorator.fromClasspath(context, ServerRequiredCustomizations())
// ServerCodegenVisitor is the main driver of code generation that traverses the model and generates code
logger.info("Loaded plugin to generate pure Rust bindings for the server SSDK")
logger.info("Loaded plugin to generate pure Rust bindings for the server SDK")
ServerCodegenVisitor(context, codegenDecorator).execute()
}

View File

@ -223,8 +223,8 @@ open class ServerCodegenVisitor(
rustCrate,
protocolGenerator,
protocolGeneratorFactory.support(),
protocolGeneratorFactory.protocol(codegenContext).httpBindingResolver,
codegenContext,
protocolGeneratorFactory.protocol(codegenContext),
codegenContext
)
.render()
}

View File

@ -34,7 +34,6 @@ open class ServerOperationHandlerGenerator(
private val model = coreCodegenContext.model
private val protocol = coreCodegenContext.protocol
private val symbolProvider = coreCodegenContext.symbolProvider
private val operationNames = operations.map { symbolProvider.toSymbol(it).name }
private val runtimeConfig = coreCodegenContext.runtimeConfig
private val codegenScope = arrayOf(
"AsyncTrait" to ServerCargoDependency.AsyncTrait.asType(),
@ -52,7 +51,7 @@ open class ServerOperationHandlerGenerator(
renderHandlerImplementations(writer, true)
}
/*
/**
* Renders the implementation of the `Handler` trait for all operations.
* Handlers are implemented for `FnOnce` function types whose signatures take in state or not.
*/
@ -126,7 +125,7 @@ open class ServerOperationHandlerGenerator(
}
}
/*
/**
* Generates the trait bounds of the `Handler` trait implementation, depending on:
* - the presence of state; and
* - whether the operation is fallible or not.

View File

@ -5,10 +5,6 @@
package software.amazon.smithy.rust.codegen.server.smithy.generators
import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait
import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.traits.DocumentationTrait
import software.amazon.smithy.rust.codegen.rustlang.Attribute
@ -32,7 +28,7 @@ import software.amazon.smithy.rust.codegen.smithy.Inputs
import software.amazon.smithy.rust.codegen.smithy.Outputs
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpBindingResolver
import software.amazon.smithy.rust.codegen.smithy.protocols.Protocol
import software.amazon.smithy.rust.codegen.util.getTrait
import software.amazon.smithy.rust.codegen.util.inputShape
import software.amazon.smithy.rust.codegen.util.outputShape
@ -51,12 +47,11 @@ import software.amazon.smithy.rust.codegen.util.toSnakeCase
*/
class ServerOperationRegistryGenerator(
coreCodegenContext: CoreCodegenContext,
private val httpBindingResolver: HttpBindingResolver,
private val protocol: Protocol,
private val operations: List<OperationShape>,
) {
private val crateName = coreCodegenContext.settings.moduleName
private val model = coreCodegenContext.model
private val protocol = coreCodegenContext.protocol
private val symbolProvider = coreCodegenContext.symbolProvider
private val serviceName = coreCodegenContext.serviceShape.toShapeId().name
private val operationNames = operations.map { symbolProvider.toSymbol(it).name.toSnakeCase() }
@ -89,14 +84,20 @@ class ServerOperationRegistryGenerator(
}
private fun renderOperationRegistryRustDocs(writer: RustWriter) {
val inputOutputErrorsImport = if (operations.any { it.errors.isNotEmpty() }) {
"/// use $crateName::{${Inputs.namespace}, ${Outputs.namespace}, ${Errors.namespace}};"
} else {
"/// use $crateName::{${Inputs.namespace}, ${Outputs.namespace}};"
}
writer.rustTemplate(
"""
##[allow(clippy::tabs_in_doc_comments)]
/// The `${operationRegistryName}` is the place where you can register
/// The `$operationRegistryName` is the place where you can register
/// your service's operation implementations.
///
/// Use [`${operationRegistryBuilderName}`] to construct the
/// `${operationRegistryName}`. For each of the [operations] modeled in
/// Use [`$operationRegistryBuilderName`] to construct the
/// `$operationRegistryName`. For each of the [operations] modeled in
/// your Smithy service, you need to provide an implementation in the
/// form of a Rust async function or closure that takes in the
/// operation's input as their first parameter, and returns the
@ -120,17 +121,13 @@ class ServerOperationRegistryGenerator(
///
/// ```rust
/// use std::net::SocketAddr;
${ if (operations.any { it.errors.isNotEmpty() }) {
"/// use ${crateName}::{${Inputs.namespace}, ${Outputs.namespace}, ${Errors.namespace}};"
} else {
"/// use ${crateName}::{${Inputs.namespace}, ${Outputs.namespace}};"
} }
/// use ${crateName}::operation_registry::${operationRegistryBuilderName};
$inputOutputErrorsImport
/// use $crateName::operation_registry::$operationRegistryBuilderName;
/// use #{Router};
///
/// ##[#{Tokio}::main]
/// pub async fn main() {
/// let app: Router = ${operationRegistryBuilderName}::default()
/// let app: Router = $operationRegistryBuilderName::default()
${operationNames.map { ".$it($it)" }.joinToString("\n") { it.prependIndent("/// ") }}
/// .build()
/// .expect("unable to build operation registry")
@ -206,10 +203,10 @@ ${operationImplementationStubs(operations)}
Attribute.Derives(setOf(RuntimeType.Debug)).render(writer)
writer.rustTemplate(
"""
pub enum ${operationRegistryErrorName}{
pub enum $operationRegistryErrorName {
UninitializedField(&'static str)
}
impl #{Display} for ${operationRegistryErrorName}{
impl #{Display} for $operationRegistryErrorName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UninitializedField(v) => write!(f, "{}", v),
@ -263,14 +260,14 @@ ${operationImplementationStubs(operations)}
)
}
rustBlock("pub fn build(self) -> Result<$operationRegistryNameWithArguments, ${operationRegistryErrorName}>") {
rustBlock("pub fn build(self) -> Result<$operationRegistryNameWithArguments, $operationRegistryErrorName>") {
withBlock("Ok( $operationRegistryName {", "})") {
for (operationName in operationNames) {
rust(
"""
$operationName: match self.$operationName {
Some(v) => v,
None => return Err(${operationRegistryErrorName}::UninitializedField("$operationName")),
None => return Err($operationRegistryErrorName::UninitializedField("$operationName")),
},
"""
)
@ -320,7 +317,11 @@ ${operationImplementationStubs(operations)}
)
}
withBlockTemplate("#{Router}::${runtimeRouterConstructor()}(vec![", "])", *codegenScope) {
withBlockTemplate(
"#{Router}::${protocol.serverRouterRuntimeConstructor()}(vec![",
"])",
*codegenScope
) {
requestSpecsVarNames.zip(operationNames).forEach { (requestSpecVarName, operationName) ->
rustTemplate(
"(#{Tower}::util::BoxCloneService::new(#{ServerOperationHandler}::operation(registry.$operationName)), $requestSpecVarName),",
@ -337,100 +338,6 @@ ${operationImplementationStubs(operations)}
*/
private fun phantomMembers() = operationNames.mapIndexed { i, _ -> "In$i" }.joinToString(separator = ",\n")
/**
* Finds the runtime function to construct a new `Router` based on the Protocol.
*/
private fun runtimeRouterConstructor(): String =
when (protocol) {
RestJson1Trait.ID -> "new_rest_json_router"
RestXmlTrait.ID -> "new_rest_xml_router"
AwsJson1_0Trait.ID -> "new_aws_json_10_router"
AwsJson1_1Trait.ID -> "new_aws_json_11_router"
else -> TODO("Protocol $protocol not supported yet")
}
/**
* Returns a writable for the `RequestSpec` for an operation based on the service's protocol.
*/
private fun OperationShape.requestSpec(): Writable =
when (protocol) {
RestJson1Trait.ID, RestXmlTrait.ID -> restRequestSpec()
AwsJson1_0Trait.ID, AwsJson1_1Trait.ID -> awsJsonOperationName()
else -> TODO("Protocol $protocol not supported yet")
}
/**
* Returns the operation name as required by the awsJson1.x protocols.
*/
private fun OperationShape.awsJsonOperationName(): Writable {
val operationName = symbolProvider.toSymbol(this).name
return writable {
rust("""String::from("$serviceName.$operationName")""")
}
}
/**
* Generates a restJson1 or restXml specific `RequestSpec`.
*/
private fun OperationShape.restRequestSpec(): Writable {
val httpTrait = httpBindingResolver.httpTrait(this)
val extraCodegenScope =
arrayOf("RequestSpec", "UriSpec", "PathAndQuerySpec", "PathSpec", "QuerySpec", "PathSegment", "QuerySegment").map {
it to ServerCargoDependency.SmithyHttpServer(runtimeConfig).asType().member("routing::request_spec::$it")
}.toTypedArray()
// TODO(https://github.com/awslabs/smithy-rs/issues/950): Support the `endpoint` trait.
val pathSegmentsVec = writable {
withBlock("vec![", "]") {
for (segment in httpTrait.uri.segments) {
val variant = when {
segment.isGreedyLabel -> "Greedy"
segment.isLabel -> "Label"
else -> """Literal(String::from("${segment.content}"))"""
}
rustTemplate(
"#{PathSegment}::$variant,",
*extraCodegenScope
)
}
}
}
val querySegmentsVec = writable {
withBlock("vec![", "]") {
for (queryLiteral in httpTrait.uri.queryLiterals) {
val variant = if (queryLiteral.value == "") {
"""Key(String::from("${queryLiteral.key}"))"""
} else {
"""KeyValue(String::from("${queryLiteral.key}"), String::from("${queryLiteral.value}"))"""
}
rustTemplate("#{QuerySegment}::$variant,", *extraCodegenScope)
}
}
}
return writable {
rustTemplate(
"""
#{RequestSpec}::new(
#{Method}::${httpTrait.method},
#{UriSpec}::new(
#{PathAndQuerySpec}::new(
#{PathSpec}::from_vector_unchecked(#{PathSegmentsVec:W}),
#{QuerySpec}::from_vector_unchecked(#{QuerySegmentsVec:W})
)
),
)
""",
*codegenScope,
*extraCodegenScope,
"PathSegmentsVec" to pathSegmentsVec,
"QuerySegmentsVec" to querySegmentsVec,
"Method" to CargoDependency.Http.asType().member("Method"),
)
}
}
private fun operationImplementationStubs(operations: List<OperationShape>): String =
operations.joinToString("\n///\n") {
val operationDocumentation = it.getTrait<DocumentationTrait>()?.value
@ -438,11 +345,11 @@ ${operationImplementationStubs(operations)}
operationDocumentation.replace("#", "##").prependIndent("/// /// ") + "\n"
} else ""
ret +
"""
"""
/// ${it.signature()} {
/// todo!()
/// }
""".trimIndent()
""".trimIndent()
}
/**
@ -465,4 +372,14 @@ ${operationImplementationStubs(operations)}
val operationName = symbolProvider.toSymbol(this).name.toSnakeCase()
return "async fn $operationName(input: $inputT) -> $outputT"
}
/**
* Returns a writable for the `RequestSpec` for an operation based on the service's protocol.
*/
private fun OperationShape.requestSpec(): Writable = protocol.serverRouterRequestSpec(
this,
symbolProvider.toSymbol(this).name,
serviceName,
ServerCargoDependency.SmithyHttpServer(runtimeConfig).asType().member("routing::request_spec")
)
}

View File

@ -14,19 +14,19 @@ import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext
import software.amazon.smithy.rust.codegen.smithy.RustCrate
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpBindingResolver
import software.amazon.smithy.rust.codegen.smithy.protocols.Protocol
/**
* ServerServiceGenerator
*
* Service generator is the main codegeneration entry point for Smithy services. Individual structures and unions are
* Service generator is the main code generation entry point for Smithy services. Individual structures and unions are
* generated in codegen visitor, but this class handles all protocol-specific code generation (i.e. operations).
*/
open class ServerServiceGenerator(
private val rustCrate: RustCrate,
private val protocolGenerator: ProtocolGenerator,
private val protocolSupport: ProtocolSupport,
private val httpBindingResolver: HttpBindingResolver,
private val protocol: Protocol,
private val coreCodegenContext: CoreCodegenContext,
) {
private val index = TopDownIndex.of(coreCodegenContext.model)
@ -84,6 +84,6 @@ open class ServerServiceGenerator(
// Render operations registry.
private fun renderOperationRegistry(writer: RustWriter, operations: List<OperationShape>) {
ServerOperationRegistryGenerator(coreCodegenContext, httpBindingResolver, operations).render(writer)
ServerOperationRegistryGenerator(coreCodegenContext, protocol, operations).render(writer)
}
}

View File

@ -27,7 +27,7 @@ import software.amazon.smithy.rust.codegen.smithy.protocols.serialize.JsonSerial
import software.amazon.smithy.rust.codegen.smithy.protocols.serialize.StructuredDataSerializerGenerator
import software.amazon.smithy.rust.codegen.util.hasTrait
/*
/**
* AwsJson 1.0 and 1.1 server-side protocol factory. This factory creates the [ServerHttpBoundProtocolGenerator]
* with AwsJson specific configurations.
*/
@ -57,7 +57,7 @@ class ServerAwsJsonFactory(private val version: AwsJsonVersion) :
}
/**
* AwsJson requires errors to be serialized with an additional "__type" field. This
* AwsJson requires errors to be serialized in server responses with an additional `__type` field. This
* customization writes the right field depending on the version of the AwsJson protocol.
*/
class ServerAwsJsonError(private val awsJsonVersion: AwsJsonVersion) : JsonCustomization() {
@ -79,15 +79,22 @@ class ServerAwsJsonError(private val awsJsonVersion: AwsJsonVersion) : JsonCusto
}
/**
* AwsJson requires errors to be serialized with an additional "__type" field. This class
* AwsJson requires operation errors to be serialized in server response with an additional `__type` field. This class
* customizes [JsonSerializerGenerator] to add this functionality.
*
* https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_0-protocol.html#operation-error-serialization
*/
class ServerAwsJsonSerializerGenerator(
private val coreCodegenContext: CoreCodegenContext,
private val httpBindingResolver: HttpBindingResolver,
private val awsJsonVersion: AwsJsonVersion,
private val jsonSerializerGenerator: JsonSerializerGenerator =
JsonSerializerGenerator(coreCodegenContext, httpBindingResolver, ::awsJsonFieldName, customizations = listOf(ServerAwsJsonError(awsJsonVersion)))
JsonSerializerGenerator(
coreCodegenContext,
httpBindingResolver,
::awsJsonFieldName,
customizations = listOf(ServerAwsJsonError(awsJsonVersion))
)
) : StructuredDataSerializerGenerator by jsonSerializerGenerator
class ServerAwsJson(

View File

@ -12,7 +12,7 @@ import software.amazon.smithy.rust.codegen.smithy.protocols.Protocol
import software.amazon.smithy.rust.codegen.smithy.protocols.ProtocolGeneratorFactory
import software.amazon.smithy.rust.codegen.smithy.protocols.RestJson
/*
/**
* RestJson1 server-side protocol factory. This factory creates the [ServerHttpProtocolGenerator]
* with RestJson1 specific configurations.
*/

View File

@ -69,52 +69,53 @@ class ServerOperationRegistryGeneratorTest {
val index = TopDownIndex.of(serverCodegenContext.model)
val operations = index.getContainedOperations(serverCodegenContext.serviceShape).sortedBy { it.id }
val httpBindingResolver = protocolGeneratorFactory.protocol(serverCodegenContext).httpBindingResolver
val protocol = protocolGeneratorFactory.protocol(serverCodegenContext)
val generator = ServerOperationRegistryGenerator(serverCodegenContext, httpBindingResolver, operations)
val generator = ServerOperationRegistryGenerator(serverCodegenContext, protocol, operations)
val writer = RustWriter.forModule("operation_registry")
generator.render(writer)
writer.toString() shouldContain
"""
/// ```rust
/// use std::net::SocketAddr;
/// use service::{input, output, error};
/// use service::operation_registry::OperationRegistryBuilder;
/// use aws_smithy_http_server::routing::Router;
///
/// #[tokio::main]
/// pub async fn main() {
/// let app: Router = OperationRegistryBuilder::default()
/// .frobnify(frobnify)
/// .say_hello(say_hello)
/// .build()
/// .expect("unable to build operation registry")
/// .into();
///
/// let bind: SocketAddr = format!("{}:{}", "127.0.0.1", "6969")
/// .parse()
/// .expect("unable to parse the server bind address and port");
///
/// let server = hyper::Server::bind(&bind).serve(app.into_make_service());
///
/// // Run your service!
/// // if let Err(err) = server.await {
/// // eprintln!("server error: {}", err);
/// // }
/// }
///
/// /// Only the Frobnify operation is documented,
/// /// over multiple lines.
/// /// And here are #hash #tags!
/// async fn frobnify(input: input::FrobnifyInputOutput) -> Result<output::FrobnifyInputOutput, error::FrobnifyError> {
/// todo!()
/// }
///
/// async fn say_hello(input: input::SayHelloInputOutput) -> output::SayHelloInputOutput {
/// todo!()
/// }
/// ```
///""".trimIndent()
"""
/// ```rust
/// use std::net::SocketAddr;
/// use service::{input, output, error};
/// use service::operation_registry::OperationRegistryBuilder;
/// use aws_smithy_http_server::routing::Router;
///
/// #[tokio::main]
/// pub async fn main() {
/// let app: Router = OperationRegistryBuilder::default()
/// .frobnify(frobnify)
/// .say_hello(say_hello)
/// .build()
/// .expect("unable to build operation registry")
/// .into();
///
/// let bind: SocketAddr = format!("{}:{}", "127.0.0.1", "6969")
/// .parse()
/// .expect("unable to parse the server bind address and port");
///
/// let server = hyper::Server::bind(&bind).serve(app.into_make_service());
///
/// // Run your service!
/// // if let Err(err) = server.await {
/// // eprintln!("server error: {}", err);
/// // }
/// }
///
/// /// Only the Frobnify operation is documented,
/// /// over multiple lines.
/// /// And here are #hash #tags!
/// async fn frobnify(input: input::FrobnifyInputOutput) -> Result<output::FrobnifyInputOutput, error::FrobnifyError> {
/// todo!()
/// }
///
/// async fn say_hello(input: input::SayHelloInputOutput) -> output::SayHelloInputOutput {
/// todo!()
/// }
/// ```
///
""".trimIndent()
}
}

View File

@ -18,7 +18,7 @@ import software.amazon.smithy.rust.codegen.smithy.generators.ManifestCustomizati
import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.smithy.protocols.ProtocolMap
import software.amazon.smithy.rust.codegen.util.deepMergeWith
import java.util.*
import java.util.ServiceLoader
import java.util.logging.Logger
/**

View File

@ -0,0 +1,94 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.smithy.generators.http
import software.amazon.smithy.model.shapes.OperationShape
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.rustTemplate
import software.amazon.smithy.rust.codegen.rustlang.withBlock
import software.amazon.smithy.rust.codegen.rustlang.writable
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpBindingResolver
/**
* [RestRequestSpecGenerator] generates a restJson1 or restXml specific `RequestSpec`. Both protocols are routed the same.
*
* This class has to live in the `codegen` subproject instead of in the `codegen-server` subproject because it is used
* by the implementations of the `serverRouterRequestSpec` of the [Protocol] interface, which is used by both subprojects
* (even though only the `codegen-server` subproject calls `serverRouterRequestSpec`).
*/
class RestRequestSpecGenerator(
private val httpBindingResolver: HttpBindingResolver,
private val requestSpecModule: RuntimeType
) {
fun generate(operationShape: OperationShape): Writable {
val httpTrait = httpBindingResolver.httpTrait(operationShape)
val extraCodegenScope =
arrayOf(
"RequestSpec",
"UriSpec",
"PathAndQuerySpec",
"PathSpec",
"QuerySpec",
"PathSegment",
"QuerySegment"
).map {
it to requestSpecModule.member(it)
}.toTypedArray()
// TODO(https://github.com/awslabs/smithy-rs/issues/950): Support the `endpoint` trait.
val pathSegmentsVec = writable {
withBlock("vec![", "]") {
for (segment in httpTrait.uri.segments) {
val variant = when {
segment.isGreedyLabel -> "Greedy"
segment.isLabel -> "Label"
else -> """Literal(String::from("${segment.content}"))"""
}
rustTemplate(
"#{PathSegment}::$variant,",
*extraCodegenScope
)
}
}
}
val querySegmentsVec = writable {
withBlock("vec![", "]") {
for (queryLiteral in httpTrait.uri.queryLiterals) {
val variant = if (queryLiteral.value == "") {
"""Key(String::from("${queryLiteral.key}"))"""
} else {
"""KeyValue(String::from("${queryLiteral.key}"), String::from("${queryLiteral.value}"))"""
}
rustTemplate("#{QuerySegment}::$variant,", *extraCodegenScope)
}
}
}
return writable {
rustTemplate(
"""
#{RequestSpec}::new(
#{Method}::${httpTrait.method},
#{UriSpec}::new(
#{PathAndQuerySpec}::new(
#{PathSpec}::from_vector_unchecked(#{PathSegmentsVec:W}),
#{QuerySpec}::from_vector_unchecked(#{QuerySegmentsVec:W})
)
),
)
""",
*extraCodegenScope,
"PathSegmentsVec" to pathSegmentsVec,
"QuerySegmentsVec" to querySegmentsVec,
"Method" to CargoDependency.Http.asType().member("Method"),
)
}
}
}

View File

@ -15,8 +15,10 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.RustModule
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.ClientCodegenContext
import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
@ -130,7 +132,7 @@ class AwsJsonSerializerGenerator(
open class AwsJson(
private val coreCodegenContext: CoreCodegenContext,
awsJsonVersion: AwsJsonVersion
private val awsJsonVersion: AwsJsonVersion
) : Protocol {
private val runtimeConfig = coreCodegenContext.runtimeConfig
private val errorScope = arrayOf(
@ -181,6 +183,23 @@ open class AwsJson(
*errorScope
)
}
/**
* Returns the operation name as required by the awsJson1.x protocols.
*/
override fun serverRouterRequestSpec(
operationShape: OperationShape,
operationName: String,
serviceName: String,
requestSpecModule: RuntimeType
) = writable {
rust("""String::from("$serviceName.$operationName")""")
}
override fun serverRouterRuntimeConstructor() = when (awsJsonVersion) {
AwsJsonVersion.Json10 -> "new_aws_json_10_router"
AwsJsonVersion.Json11 -> "new_aws_json_11_router"
}
}
fun awsJsonFieldName(member: MemberShape): String = member.memberName

View File

@ -14,6 +14,7 @@ import software.amazon.smithy.model.traits.HttpTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.RustModule
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
@ -106,4 +107,17 @@ class AwsQueryProtocol(private val coreCodegenContext: CoreCodegenContext) : Pro
rust("#T::parse_generic_error(payload.as_ref())", awsQueryErrors)
}
}
override fun serverRouterRequestSpec(
operationShape: OperationShape,
operationName: String,
serviceName: String,
requestSpecModule: RuntimeType
): Writable {
TODO("Not yet implemented")
}
override fun serverRouterRuntimeConstructor(): String {
TODO("Not yet implemented")
}
}

View File

@ -12,6 +12,7 @@ import software.amazon.smithy.model.traits.HttpTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.RustModule
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
@ -98,4 +99,17 @@ class Ec2QueryProtocol(private val coreCodegenContext: CoreCodegenContext) : Pro
rust("#T::parse_generic_error(payload.as_ref())", ec2QueryErrors)
}
}
override fun serverRouterRequestSpec(
operationShape: OperationShape,
operationName: String,
serviceName: String,
requestSpecModule: RuntimeType
): Writable {
TODO("Not yet implemented")
}
override fun serverRouterRuntimeConstructor(): String {
TODO("Not yet implemented")
}
}

View File

@ -20,6 +20,7 @@ import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.model.traits.Trait
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider
@ -74,6 +75,22 @@ interface Protocol {
* there are no response headers or statuses available to further inform the error parsing.
*/
fun parseEventStreamGenericError(operationShape: OperationShape): RuntimeType
/**
* Returns a writable for the `RequestSpec` for an operation.
*/
fun serverRouterRequestSpec(
operationShape: OperationShape,
operationName: String,
serviceName: String,
requestSpecModule: RuntimeType
): Writable
/**
* Returns the name of the constructor to be used on the `Router` type, to instantiate a `Router` using this
* protocol.
*/
fun serverRouterRuntimeConstructor(): String
}
typealias ProtocolMap<C> = Map<ShapeId, ProtocolGeneratorFactory<ProtocolGenerator, C>>

View File

@ -16,11 +16,13 @@ import software.amazon.smithy.model.traits.StreamingTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.RustModule
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.asType
import software.amazon.smithy.rust.codegen.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.generators.http.RestRequestSpecGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.smithy.protocols.parse.JsonParserGenerator
import software.amazon.smithy.rust.codegen.smithy.protocols.parse.StructuredDataParserGenerator
@ -141,6 +143,15 @@ class RestJson(private val coreCodegenContext: CoreCodegenContext) : Protocol {
*errorScope
)
}
override fun serverRouterRequestSpec(
operationShape: OperationShape,
operationName: String,
serviceName: String,
requestSpecModule: RuntimeType
): Writable = RestRequestSpecGenerator(httpBindingResolver, requestSpecModule).generate(operationShape)
override fun serverRouterRuntimeConstructor() = "new_rest_json_router"
}
fun restJsonFieldName(member: MemberShape): String {

View File

@ -11,12 +11,14 @@ import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.RustModule
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.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.smithy.CoreCodegenContext
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.generators.http.RestRequestSpecGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.smithy.protocols.parse.RestXmlParserGenerator
import software.amazon.smithy.rust.codegen.smithy.protocols.parse.StructuredDataParserGenerator
@ -101,4 +103,13 @@ open class RestXml(private val coreCodegenContext: CoreCodegenContext) : Protoco
rust("#T::parse_generic_error(payload.as_ref())", restXmlErrors)
}
}
override fun serverRouterRequestSpec(
operationShape: OperationShape,
operationName: String,
serviceName: String,
requestSpecModule: RuntimeType
): Writable = RestRequestSpecGenerator(httpBindingResolver, requestSpecModule).generate(operationShape)
override fun serverRouterRuntimeConstructor() = "new_rest_xml_router"
}