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.
Original file line number Diff line number Diff line change
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)
}
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,
)
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading

0 comments on commit 876c19e

Please sign in to comment.