mirror of https://github.com/smithy-lang/smithy-rs
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:
parent
12b4943e03
commit
e751ed6965
|
@ -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
|
||||
|
||||
|
|
|
@ -143,8 +143,8 @@ class PythonServerCodegenVisitor(
|
|||
rustCrate,
|
||||
protocolGenerator,
|
||||
protocolGeneratorFactory.support(),
|
||||
protocolGeneratorFactory.protocol(codegenContext).httpBindingResolver,
|
||||
codegenContext,
|
||||
protocolGeneratorFactory.protocol(codegenContext),
|
||||
codegenContext
|
||||
)
|
||||
.render()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -223,8 +223,8 @@ open class ServerCodegenVisitor(
|
|||
rustCrate,
|
||||
protocolGenerator,
|
||||
protocolGeneratorFactory.support(),
|
||||
protocolGeneratorFactory.protocol(codegenContext).httpBindingResolver,
|
||||
codegenContext,
|
||||
protocolGeneratorFactory.protocol(codegenContext),
|
||||
codegenContext
|
||||
)
|
||||
.render()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue