Unverified Commit 34f43d96 authored by Matteo Bigoi's avatar Matteo Bigoi Committed by GitHub
Browse files

Migrate from axum to axum-core (#945)

Migrate to axum-core. 
Remove pin-project in favor of pin-project-lite. 
Remove CloneBoxedService in favor of tower util upstream type.
Update codegen to use axum-core and pin-project-lite.
Use inlinable for server handler definition.
parent 953c774c
Loading
Loading
Loading
Loading
+29 −2
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@
package software.amazon.smithy.rust.codegen.server.smithy
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.CratesIo
import software.amazon.smithy.rust.codegen.rustlang.InlineDependency
import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig

/**
 * Object used *exclusively* in the runtime of the server, for separation concerns.
@@ -13,8 +15,33 @@ import software.amazon.smithy.rust.codegen.rustlang.CratesIo
 * For a dependency that is used in the client, or in both the client and the server, use [CargoDependency] directly.
 */
object ServerCargoDependency {
    val Axum: CargoDependency = CargoDependency("axum", CratesIo("0.3"))
    val AsyncTrait: CargoDependency = CargoDependency("async-trait", CratesIo("0.1"))
    val AxumCore: CargoDependency = CargoDependency("axum-core", CratesIo("0.1"))
    val FuturesUtil: CargoDependency = CargoDependency("futures-util", CratesIo("0.3"))
    val PinProject: CargoDependency = CargoDependency("pin-project", CratesIo("1"))
    val PinProjectLite: CargoDependency = CargoDependency("pin-project-lite", CratesIo("0.2"))
    val Tower: CargoDependency = CargoDependency("tower", CratesIo("0.4"))
}

/**
 * A dependency on a snippet of code
 *
 * ServerInlineDependency should not be instantiated directly, rather, it should be constructed with
 * [software.amazon.smithy.rust.codegen.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 deduplicates inline dependencies by (module, name) during code generation.
 */
object ServerInlineDependency {
    fun serverOperationHandler(runtimeConfig: RuntimeConfig): InlineDependency =
        InlineDependency.forRustFile(
            "server_operation_handler_trait",
            CargoDependency.SmithyHttpServer(runtimeConfig),
            CargoDependency.Http,
            ServerCargoDependency.PinProjectLite,
            ServerCargoDependency.Tower,
            ServerCargoDependency.FuturesUtil,
            ServerCargoDependency.AsyncTrait,
        )
}
+8 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
package software.amazon.smithy.rust.codegen.server.smithy

import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.InlineDependency
import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.smithy.RuntimeType

@@ -15,10 +16,17 @@ import software.amazon.smithy.rust.codegen.smithy.RuntimeType
 * For a runtime type that is used in the client, or in both the client and the server, use [RuntimeType] directly.
 */
object ServerRuntimeType {
    fun forInlineDependency(inlineDependency: InlineDependency) =
        RuntimeType(inlineDependency.name, inlineDependency, namespace = "crate")

    val Phantom = RuntimeType("PhantomData", dependency = null, namespace = "std::marker")

    fun Router(runtimeConfig: RuntimeConfig) =
        RuntimeType("Router", CargoDependency.SmithyHttpServer(runtimeConfig), "${runtimeConfig.crateSrcPrefix}_http_server::routing")

    fun RequestSpecModule(runtimeConfig: RuntimeConfig) =
        RuntimeType("request_spec", CargoDependency.SmithyHttpServer(runtimeConfig), "${runtimeConfig.crateSrcPrefix}_http_server::routing")

    fun serverOperationHandler(runtimeConfig: RuntimeConfig) =
        forInlineDependency(ServerInlineDependency.serverOperationHandler(runtimeConfig))
}
+14 −97
Original line number Diff line number Diff line
@@ -34,18 +34,19 @@ class ServerOperationHandlerGenerator(
    private val operationNames = operations.map { symbolProvider.toSymbol(it).name }
    private val runtimeConfig = codegenContext.runtimeConfig
    private val codegenScope = arrayOf(
        "Axum" to ServerCargoDependency.Axum.asType(),
        "PinProject" to ServerCargoDependency.PinProject.asType(),
        "AsyncTrait" to ServerCargoDependency.AsyncTrait.asType(),
        "AxumCore" to ServerCargoDependency.AxumCore.asType(),
        "PinProjectLite" to ServerCargoDependency.PinProjectLite.asType(),
        "Tower" to ServerCargoDependency.Tower.asType(),
        "FuturesUtil" to ServerCargoDependency.FuturesUtil.asType(),
        "SmithyHttpServer" to CargoDependency.SmithyHttpServer(runtimeConfig).asType(),
        "SmithyRejection" to ServerHttpProtocolGenerator.smithyRejection(runtimeConfig),
        "Phantom" to ServerRuntimeType.Phantom,
        "ServerOperationHandler" to ServerRuntimeType.serverOperationHandler(runtimeConfig),
        "http" to RuntimeType.http,
    )

    fun render(writer: RustWriter) {
        renderStaticRust(writer)
        renderHandlerImplementations(writer, false)
        renderHandlerImplementations(writer, true)
    }
@@ -61,13 +62,13 @@ class ServerOperationHandlerGenerator(
            val inputWrapperName = "crate::operation::$operationName${ServerHttpProtocolGenerator.OPERATION_INPUT_WRAPPER_SUFFIX}"
            val outputWrapperName = "crate::operation::$operationName${ServerHttpProtocolGenerator.OPERATION_OUTPUT_WRAPPER_SUFFIX}"
            val fnSignature = if (state) {
                "impl<B, Fun, Fut, S> Handler<B, $serverCrate::Extension<S>, $inputName> for Fun"
                "impl<B, Fun, Fut, S> #{ServerOperationHandler}::Handler<B, $serverCrate::Extension<S>, $inputName> for Fun"
            } else {
                "impl<B, Fun, Fut> Handler<B, (), $inputName> for Fun"
                "impl<B, Fun, Fut> #{ServerOperationHandler}::Handler<B, (), $inputName> for Fun"
            }
            writer.rustBlockTemplate(
                """
                ##[axum::async_trait]
                ##[#{AsyncTrait}::async_trait]
                $fnSignature
                where
                    ${operationTraitBounds(operation, inputName, state)}
@@ -77,7 +78,7 @@ class ServerOperationHandlerGenerator(
                val callImpl = if (state) {
                    """let state = match $serverCrate::Extension::<S>::from_request(&mut req).await {
                    Ok(v) => v,
                    Err(r) => return r.into_response().map($serverCrate::body::box_body)
                    Err(r) => return r.into_response().map($serverCrate::boxed)
                    };
                    let input_inner = input_wrapper.into();
                    let output_inner = self(input_inner, state).await;"""
@@ -87,18 +88,18 @@ class ServerOperationHandlerGenerator(
                }
                rustTemplate(
                    """
                    type Sealed = sealed::Hidden;
                    type Sealed = #{ServerOperationHandler}::sealed::Hidden;
                    async fn call(self, req: #{http}::Request<B>) -> #{http}::Response<#{SmithyHttpServer}::BoxBody> {
                        let mut req = #{Axum}::extract::RequestParts::new(req);
                        use #{Axum}::extract::FromRequest;
                        use #{Axum}::response::IntoResponse;
                        let mut req = #{AxumCore}::extract::RequestParts::new(req);
                        use #{AxumCore}::extract::FromRequest;
                        use #{AxumCore}::response::IntoResponse;
                        let input_wrapper = match $inputWrapperName::from_request(&mut req).await {
                            Ok(v) => v,
                            Err(r) => return r.into_response().map(#{SmithyHttpServer}::body::box_body)
                            Err(r) => return r.into_response().map(#{SmithyHttpServer}::boxed)
                        };
                        $callImpl
                        let output_wrapper: $outputWrapperName = output_inner.into();
                        output_wrapper.into_response().map(#{SmithyHttpServer}::body::box_body)
                        output_wrapper.into_response().map(#{SmithyHttpServer}::boxed)
                    }
                    """,
                    *codegenScope
@@ -133,88 +134,4 @@ class ServerOperationHandlerGenerator(
            $serverCrate::rejection::SmithyRejection: From<<B as $serverCrate::HttpBody>::Error>
        """
    }

    /*
     * This method is used to "generate" the `OperationHandler` struct, the `Handler` trait and all the
     * code needed to support the indirection used to call the user defined functions, which implement the service's operations.
     * implementations.
     *
     * TODO: remove this hacky function and move this piece of code into an inlinable crate. The crate should
     *       be server specific, so we do not step on the client codegen feet. We choose to just add a static
     *       string since there are changes needed in the Inlinable class to be able to support server specific
     *       functionalities.
     */
    private fun renderStaticRust(writer: RustWriter) {
        writer.rustTemplate(
            """
            /// Struct that holds a handler, that is, a function provided by the user that implements the
            /// Smithy operation.
            pub struct OperationHandler<H, B, R, I> {
                handler: H,
                ##[allow(clippy::type_complexity)]
                _marker: #{Phantom}<fn() -> (B, R, I)>,
            }
            impl<H, B, R, I> Clone for OperationHandler<H, B, R, I>
            where
                H: Clone,
            {
                fn clone(&self) -> Self {
                    Self {
                        handler: self.handler.clone(),
                        _marker: #{Phantom},
                    }
                }
            }
            /// Construct an [`OperationHandler`] out of a function implementing the operation.
            pub fn operation<H, B, R, I>(handler: H) -> OperationHandler<H, B, R, I> {
                OperationHandler {
                    handler,
                    _marker: #{Phantom},
                }
            }
            impl<H, B, R, I> #{Tower}::Service<#{http}::Request<B>> for OperationHandler<H, B, R, I>
            where
                H: Handler<B, R, I>,
                B: Send + 'static,
            {
                type Response = #{http}::Response<#{SmithyHttpServer}::BoxBody>;
                type Error = std::convert::Infallible;
                type Future = OperationHandlerFuture;

                ##[inline]
                fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
                    std::task::Poll::Ready(Ok(()))
                }

                fn call(&mut self, req: #{http}::Request<B>) -> Self::Future {
                    use #{FuturesUtil}::FutureExt;
                    let future = Handler::call(self.handler.clone(), req).map(Ok::<_, std::convert::Infallible> as _);
                    OperationHandlerFuture::new(future)
                }
            }
            type WrapResultInResponseFn = fn(#{http}::Response<#{SmithyHttpServer}::BoxBody>) -> Result<#{http}::Response<#{SmithyHttpServer}::BoxBody>, std::convert::Infallible>;
            use #{PinProject};
            use #{SmithyHttpServer}::opaque_future;
            opaque_future! {
                /// Response future for [`OperationHandler`].
                pub type OperationHandlerFuture =
                    #{FuturesUtil}::future::Map<#{FuturesUtil}::future::BoxFuture<'static, #{http}::Response<#{SmithyHttpServer}::BoxBody>>, WrapResultInResponseFn>;
            }
            pub(crate) mod sealed {
                ##![allow(unreachable_pub, missing_docs, missing_debug_implementations)]
                pub trait HiddenTrait {}
                pub struct Hidden;
                impl HiddenTrait for Hidden {}
            }
            ##[axum::async_trait]
            pub trait Handler<B, T, Fut>: Clone + Send + Sized + 'static {
                ##[doc(hidden)]
                type Sealed: sealed::HiddenTrait;

                async fn call(self, req: #{http}::Request<B>) -> #{http}::Response<#{SmithyHttpServer}::BoxBody>;
            }
            """,
            *codegenScope
        )
    }
}
+4 −3
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ class ServerOperationRegistryGenerator(
    private val codegenScope = arrayOf(
        "Router" to ServerRuntimeType.Router(runtimeConfig),
        "SmithyHttpServer" to CargoDependency.SmithyHttpServer(runtimeConfig).asType(),
        "ServerOperationHandler" to ServerRuntimeType.serverOperationHandler(runtimeConfig),
        "Phantom" to ServerRuntimeType.Phantom,
        "StdError" to RuntimeType.StdError
    )
@@ -200,8 +201,8 @@ class ServerOperationRegistryGenerator(
        val operationsTraitBounds = operations
            .mapIndexed { i, operation ->
                val operationName = symbolProvider.toSymbol(operation).name
                """Op$i: crate::operation_handler::Handler<B, In$i, crate::input::${operationName}Input>,
                In$i: 'static"""
                """Op$i: #{ServerOperationHandler}::Handler<B, In$i, crate::input::${operationName}Input>,
                In$i: 'static + Send"""
            }.joinToString(separator = ",\n")
        Attribute.Custom("allow(clippy::all)").render(writer)
        writer.rustBlockTemplate(
@@ -216,7 +217,7 @@ class ServerOperationRegistryGenerator(
            rustBlock("fn from(registry: $operationRegistryNameWithArguments) -> Self") {
                val requestSpecsVarNames = operationNames.map { "${it}_request_spec" }
                val routes = requestSpecsVarNames.zip(operationNames) { requestSpecVarName, operationName ->
                    ".route($requestSpecVarName, crate::operation_handler::operation(registry.$operationName))"
                    ".route($requestSpecVarName, #{ServerOperationHandler}::operation(registry.$operationName))"
                }.joinToString(separator = "\n")

                val requestSpecs = requestSpecsVarNames.zip(operations) { requestSpecVarName, operation ->
+4 −4
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ class ServerProtocolTestGenerator(
        "SmithyHttp" to CargoDependency.SmithyHttp(codegenContext.runtimeConfig).asType(),
        "Http" to CargoDependency.Http.asType(),
        "Hyper" to CargoDependency.Hyper.asType(),
        "Axum" to ServerCargoDependency.Axum.asType(),
        "AxumCore" to ServerCargoDependency.AxumCore.asType(),
        "SmithyHttpServer" to CargoDependency.SmithyHttpServer(codegenContext.runtimeConfig).asType(),
    )

@@ -272,7 +272,7 @@ class ServerProtocolTestGenerator(
        rustTemplate(
            """
            let output = super::$operationImpl;
            use #{Axum}::response::IntoResponse;
            use #{AxumCore}::response::IntoResponse;
            let http_response = output.into_response();
            """,
            *codegenScope,
@@ -308,8 +308,8 @@ class ServerProtocolTestGenerator(
        val operationName = "${operationSymbol.name}${ServerHttpProtocolGenerator.OPERATION_INPUT_WRAPPER_SUFFIX}"
        rustWriter.rustTemplate(
            """
            use #{Axum}::extract::FromRequest;
            let mut http_request = #{Axum}::extract::RequestParts::new(http_request);
            use #{AxumCore}::extract::FromRequest;
            let mut http_request = #{AxumCore}::extract::RequestParts::new(http_request);
            let input_wrapper = super::$operationName::from_request(&mut http_request).await.expect("failed to parse request");
            let input = input_wrapper.0;
            """,
Loading