Skip to content

Commit

Permalink
Revert "Remove old service builder machinery (smithy-lang#2161)"
Browse files Browse the repository at this point in the history
This reverts commit 4436d9a.
  • Loading branch information
thomas-k-cameron committed Jan 24, 2023
1 parent d75eafd commit 876c19e
Show file tree
Hide file tree
Showing 33 changed files with 2,188 additions and 478 deletions.
Expand Up @@ -10,19 +10,16 @@ import software.amazon.smithy.build.PluginContext
import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.NullableIndex
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.shapes.UnionShape
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig
import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerEnumGenerator
import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerOperationHandlerGenerator
import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerServiceGenerator
import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerStructureGenerator
import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext
Expand Down Expand Up @@ -167,11 +164,4 @@ class PythonServerCodegenVisitor(
)
.render()
}

override fun operationShape(shape: OperationShape) {
super.operationShape(shape)
rustCrate.withModule(RustModule.public("python_operation_adaptor")) {
PythonServerOperationHandlerGenerator(codegenContext, shape).render(this)
}
}
}
Expand Up @@ -199,7 +199,7 @@ class PythonApplicationGenerator(
let ${name}_locals = #{pyo3_asyncio}::TaskLocals::new(event_loop);
let handler = self.handlers.get("$name").expect("Python handler for operation `$name` not found").clone();
let builder = builder.$name(move |input, state| {
#{pyo3_asyncio}::tokio::scope(${name}_locals.clone(), crate::python_operation_adaptor::$name(input, state, handler.clone()))
#{pyo3_asyncio}::tokio::scope(${name}_locals.clone(), crate::operation_handler::$name(input, state, handler.clone()))
});
""",
*codegenScope,
Expand Down
Expand Up @@ -13,6 +13,8 @@ import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
import software.amazon.smithy.rust.codegen.server.python.smithy.PythonServerCargoDependency
import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerOperationHandlerGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol

/**
* The Rust code responsible to run the Python business logic on the Python interpreter
Expand All @@ -30,8 +32,9 @@ import software.amazon.smithy.rust.codegen.server.python.smithy.PythonServerCarg
*/
class PythonServerOperationHandlerGenerator(
codegenContext: CodegenContext,
private val operation: OperationShape,
) {
protocol: ServerProtocol,
private val operations: List<OperationShape>,
) : ServerOperationHandlerGenerator(codegenContext, protocol, operations) {
private val symbolProvider = codegenContext.symbolProvider
private val runtimeConfig = codegenContext.runtimeConfig
private val codegenScope =
Expand All @@ -44,39 +47,42 @@ class PythonServerOperationHandlerGenerator(
"tracing" to PythonServerCargoDependency.Tracing.toType(),
)

fun render(writer: RustWriter) {
override fun render(writer: RustWriter) {
super.render(writer)
renderPythonOperationHandlerImpl(writer)
}

private fun renderPythonOperationHandlerImpl(writer: RustWriter) {
val operationName = symbolProvider.toSymbol(operation).name
val input = "crate::input::${operationName}Input"
val output = "crate::output::${operationName}Output"
val error = "crate::error::${operationName}Error"
val fnName = operationName.toSnakeCase()
for (operation in operations) {
val operationName = symbolProvider.toSymbol(operation).name
val input = "crate::input::${operationName}Input"
val output = "crate::output::${operationName}Output"
val error = "crate::error::${operationName}Error"
val fnName = operationName.toSnakeCase()

writer.rustTemplate(
"""
/// Python handler for operation `$operationName`.
pub(crate) async fn $fnName(
input: $input,
state: #{SmithyServer}::Extension<#{SmithyPython}::context::PyContext>,
handler: #{SmithyPython}::PyHandler,
) -> std::result::Result<$output, $error> {
// Async block used to run the handler and catch any Python error.
let result = if handler.is_coroutine {
#{PyCoroutine:W}
} else {
#{PyFunction:W}
};
#{PyError:W}
}
""",
*codegenScope,
"PyCoroutine" to renderPyCoroutine(fnName, output),
"PyFunction" to renderPyFunction(fnName, output),
"PyError" to renderPyError(),
)
writer.rustTemplate(
"""
/// Python handler for operation `$operationName`.
pub(crate) async fn $fnName(
input: $input,
state: #{SmithyServer}::Extension<#{SmithyPython}::context::PyContext>,
handler: #{SmithyPython}::PyHandler,
) -> std::result::Result<$output, $error> {
// Async block used to run the handler and catch any Python error.
let result = if handler.is_coroutine {
#{PyCoroutine:W}
} else {
#{PyFunction:W}
};
#{PyError:W}
}
""",
*codegenScope,
"PyCoroutine" to renderPyCoroutine(fnName, output),
"PyFunction" to renderPyFunction(fnName, output),
"PyError" to renderPyError(),
)
}
}

private fun renderPyFunction(name: String, output: String): Writable =
Expand Down
Expand Up @@ -33,6 +33,10 @@ class PythonServerServiceGenerator(
PythonServerOperationErrorGenerator(context.model, context.symbolProvider, operation).render(writer)
}

override fun renderOperationHandler(writer: RustWriter, operations: List<OperationShape>) {
PythonServerOperationHandlerGenerator(context, protocol, operations).render(writer)
}

override fun renderExtras(operations: List<OperationShape>) {
rustCrate.withModule(RustModule.public("python_server_application", "Python server and application implementation.")) {
PythonApplicationGenerator(context, protocol, operations)
Expand Down
Expand Up @@ -7,6 +7,8 @@ package software.amazon.smithy.rust.codegen.server.smithy
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.CratesIo
import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope
import software.amazon.smithy.rust.codegen.core.rustlang.InlineDependency
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig

/**
Expand All @@ -25,8 +27,32 @@ object ServerCargoDependency {
val Tower: CargoDependency = CargoDependency("tower", CratesIo("0.4"))
val TokioDev: CargoDependency = CargoDependency("tokio", CratesIo("1.8.4"), scope = DependencyScope.Dev)
val Regex: CargoDependency = CargoDependency("regex", CratesIo("1.5.5"))
val HyperDev: CargoDependency = CargoDependency("hyper", CratesIo("0.14.12"), DependencyScope.Dev)

fun smithyHttpServer(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-http-server")
fun smithyTypes(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-types")
}

/**
* A dependency on a snippet of code
*
* ServerInlineDependency should not be instantiated directly, rather, it should be constructed with
* [software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.forInlineFun]
*
* ServerInlineDependencies are created as private modules within the main crate. This is useful for any code that
* doesn't need to exist in a shared crate, but must still be generated exactly once during codegen.
*
* CodegenVisitor de-duplicates inline dependencies by (module, name) during code generation.
*/
object ServerInlineDependency {
fun serverOperationHandler(runtimeConfig: RuntimeConfig): InlineDependency =
InlineDependency.forRustFile(
RustModule.private("server_operation_handler_trait"),
"/inlineable/src/server_operation_handler_trait.rs",
ServerCargoDependency.smithyHttpServer(runtimeConfig),
CargoDependency.Http,
ServerCargoDependency.PinProjectLite,
ServerCargoDependency.Tower,
ServerCargoDependency.FuturesUtil,
ServerCargoDependency.AsyncTrait,
)
}
Expand Up @@ -19,6 +19,9 @@ object ServerRuntimeType {

fun router(runtimeConfig: RuntimeConfig) = ServerCargoDependency.smithyHttpServer(runtimeConfig).toType().resolve("routing::Router")

fun operationHandler(runtimeConfig: RuntimeConfig) =
forInlineDependency(ServerInlineDependency.serverOperationHandler(runtimeConfig))

fun runtimeError(runtimeConfig: RuntimeConfig) = ServerCargoDependency.smithyHttpServer(runtimeConfig).toType().resolve("runtime_error::RuntimeError")

fun requestRejection(runtimeConfig: RuntimeConfig) = ServerCargoDependency.smithyHttpServer(runtimeConfig).toType().resolve("rejection::RequestRejection")
Expand Down
@@ -0,0 +1,154 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.rust.codegen.server.smithy.generators

import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.smithy.transformers.operationErrors
import software.amazon.smithy.rust.codegen.core.util.hasStreamingMember
import software.amazon.smithy.rust.codegen.core.util.inputShape
import software.amazon.smithy.rust.codegen.core.util.outputShape
import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency
import software.amazon.smithy.rust.codegen.server.smithy.ServerRuntimeType
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol
import software.amazon.smithy.rust.codegen.server.smithy.protocols.ServerHttpBoundProtocolGenerator

/**
* ServerOperationHandlerGenerator
*/
open class ServerOperationHandlerGenerator(
codegenContext: CodegenContext,
val protocol: ServerProtocol,
private val operations: List<OperationShape>,
) {
private val serverCrate = "aws_smithy_http_server"
private val model = codegenContext.model
private val symbolProvider = codegenContext.symbolProvider
private val runtimeConfig = codegenContext.runtimeConfig
private val codegenScope = arrayOf(
"AsyncTrait" to ServerCargoDependency.AsyncTrait.toType(),
"Tower" to ServerCargoDependency.Tower.toType(),
"FuturesUtil" to ServerCargoDependency.FuturesUtil.toType(),
"SmithyHttp" to RuntimeType.smithyHttp(runtimeConfig),
"SmithyHttpServer" to ServerCargoDependency.smithyHttpServer(runtimeConfig).toType(),
"Phantom" to RuntimeType.Phantom,
"ServerOperationHandler" to ServerRuntimeType.operationHandler(runtimeConfig),
"http" to RuntimeType.Http,
)

open fun render(writer: RustWriter) {
renderHandlerImplementations(writer, false)
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.
*/
private fun renderHandlerImplementations(writer: RustWriter, state: Boolean) {
operations.map { operation ->
val operationName = symbolProvider.toSymbol(operation).name
val inputName = symbolProvider.toSymbol(operation.inputShape(model)).fullName
val inputWrapperName = "crate::operation::$operationName${ServerHttpBoundProtocolGenerator.OPERATION_INPUT_WRAPPER_SUFFIX}"
val outputWrapperName = "crate::operation::$operationName${ServerHttpBoundProtocolGenerator.OPERATION_OUTPUT_WRAPPER_SUFFIX}"
val fnSignature = if (state) {
"impl<B, Fun, Fut, S> #{ServerOperationHandler}::Handler<B, $serverCrate::Extension<S>, $inputName> for Fun"
} else {
"impl<B, Fun, Fut> #{ServerOperationHandler}::Handler<B, (), $inputName> for Fun"
}
writer.rustBlockTemplate(
"""
##[#{AsyncTrait}::async_trait]
$fnSignature
where
${operationTraitBounds(operation, inputName, state)}
""".trimIndent(),
*codegenScope,
) {
val callImpl = if (state) {
"""
let state = match $serverCrate::extension::extract_extension(&mut req).await {
Ok(v) => v,
Err(extension_not_found_rejection) => {
let extension = $serverCrate::extension::RuntimeErrorExtension::new(extension_not_found_rejection.to_string());
let runtime_error = $serverCrate::runtime_error::RuntimeError::from(extension_not_found_rejection);
let mut response = #{SmithyHttpServer}::response::IntoResponse::<#{Protocol}>::into_response(runtime_error);
response.extensions_mut().insert(extension);
return response.map($serverCrate::body::boxed);
}
};
let input_inner = input_wrapper.into();
let output_inner = self(input_inner, state).await;
""".trimIndent()
} else {
"""
let input_inner = input_wrapper.into();
let output_inner = self(input_inner).await;
""".trimIndent()
}
rustTemplate(
"""
type Sealed = #{ServerOperationHandler}::sealed::Hidden;
async fn call(self, req: #{http}::Request<B>) -> #{http}::Response<#{SmithyHttpServer}::body::BoxBody> {
let mut req = #{SmithyHttpServer}::request::RequestParts::new(req);
let input_wrapper = match $inputWrapperName::from_request(&mut req).await {
Ok(v) => v,
Err(runtime_error) => {
let response = #{SmithyHttpServer}::response::IntoResponse::<#{Protocol}>::into_response(runtime_error);
return response.map($serverCrate::body::boxed);
}
};
$callImpl
let output_wrapper: $outputWrapperName = output_inner.into();
let mut response = output_wrapper.into_response();
let operation_ext = #{SmithyHttpServer}::extension::OperationExtension::new("${operation.id.namespace}.$operationName").expect("malformed absolute shape ID");
response.extensions_mut().insert(operation_ext);
response.map(#{SmithyHttpServer}::body::boxed)
}
""",
"Protocol" to protocol.markerStruct(),
*codegenScope,
)
}
}
}

/**
* Generates the trait bounds of the `Handler` trait implementation, depending on:
* - the presence of state; and
* - whether the operation is fallible or not.
*/
private fun operationTraitBounds(operation: OperationShape, inputName: String, state: Boolean): String {
val inputFn = if (state) {
"""S: Send + Clone + Sync + 'static,
Fun: FnOnce($inputName, $serverCrate::Extension<S>) -> Fut + Clone + Send + 'static,"""
} else {
"Fun: FnOnce($inputName) -> Fut + Clone + Send + 'static,"
}
val outputType = if (operation.operationErrors(model).isNotEmpty()) {
"Result<${symbolProvider.toSymbol(operation.outputShape(model)).fullName}, ${operation.errorSymbol(symbolProvider).fullyQualifiedName()}>"
} else {
symbolProvider.toSymbol(operation.outputShape(model)).fullName
}
val streamingBodyTraitBounds = if (operation.inputShape(model).hasStreamingMember(model)) {
"\n B: Into<#{SmithyHttp}::byte_stream::ByteStream>,"
} else {
""
}
return """
$inputFn
Fut: std::future::Future<Output = $outputType> + Send,
B: $serverCrate::body::HttpBody + Send + 'static, $streamingBodyTraitBounds
B::Data: Send,
$serverCrate::rejection::RequestRejection: From<<B as $serverCrate::body::HttpBody>::Error>
""".trimIndent()
}
}

0 comments on commit 876c19e

Please sign in to comment.