Skip to content

Commit

Permalink
aws_smithy_http_server::* should be re-exported from generated crat…
Browse files Browse the repository at this point in the history
…es (#3839)

Many customers have reported the need to explicitly depend on the
`aws-smithy-http-server` crate in their service handler code. When they
re-generate the crate, they often encounter version mismatches between
the `aws-smithy-http-server` crate used in the generated code and the
one they use in their service handler. This version discrepancy leads to
compilation errors, requiring them to manually adjust the crate
versions, which adds friction to their development workflow.

To resolve this issue, we now re-export all relevant types from
`aws-smithy-http-server` within the generated crates. By doing so,
customers can use these re-exported types directly, eliminating the need
to depend on `aws-smithy-http-server` in their handler code.

Additionally, the generated crates no longer have the `aws-lambda`
feature flag enabled by default. This prevents the `aws-lambda` feature
from being automatically enabled in `aws-smithy-http-server` when the
SDK is not intended for AWS Lambda.

---------

Co-authored-by: Fahad Zubair <fahadzub@amazon.com>
  • Loading branch information
drganjoo and Fahad Zubair authored Sep 30, 2024
1 parent 37c1cc0 commit d8fbf47
Show file tree
Hide file tree
Showing 22 changed files with 187 additions and 99 deletions.
9 changes: 9 additions & 0 deletions .changelog/4106176.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
applies_to: ["server"]
authors: ["drganjoo"]
references: []
breaking: true
new_feature: false
bug_fix: false
---
The generated crates no longer have the `aws-lambda` feature flag enabled by default. This prevents the [aws-lambda](https://docs.rs/crate/aws-smithy-http-server/0.63.3/features#aws-lambda) feature from being automatically enabled in [aws-smithy-http-server](https://docs.rs/aws-smithy-http-server/0.63.3/aws_smithy_http_server/) when the SDK is not intended for AWS Lambda.
10 changes: 10 additions & 0 deletions .changelog/9278363.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
applies_to: ["server"]
authors: ["drganjoo"]
references: []
breaking: false
new_feature: true
bug_fix: false
---
All relevant types from [aws-smithy-http-server](https://docs.rs/aws-smithy-http-server/0.63.3/aws_smithy_http_server/) are now re-exported within the generated crates. This removes the need to explicitly depend on [aws-smithy-http-server](https://docs.rs/aws-smithy-http-server/0.63.3/aws_smithy_http_server/) in service handler code and prevents compilation errors caused by version mismatches.

Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@ sealed class RustModule {
parent: RustModule = LibRs,
documentationOverride: String? = null,
additionalAttributes: List<Attribute> = emptyList(),
inline: Boolean = false,
): LeafModule =
new(
name,
visibility = Visibility.PUBLIC,
inline = false,
inline = inline,
parent = parent,
documentationOverride = documentationOverride,
additionalAttributes = additionalAttributes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ object ServerRustModule {
val Input = RustModule.public("input")
val Output = RustModule.public("output")
val Types = RustModule.public("types")
val Server = RustModule.public("server")
val Service = RustModule.private("service")
val Server = RustModule.public("server", inline = true)

val UnconstrainedModule =
software.amazon.smithy.rust.codegen.core.smithy.UnconstrainedModule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,19 @@ class ServerRequiredCustomizations : ServerCodegenDecorator {
rustCrate.mergeFeature(
Feature(
"aws-lambda",
true,
false,
listOf("aws-smithy-http-server/aws-lambda"),
),
)

rustCrate.mergeFeature(
Feature(
"request-id",
true,
listOf("aws-smithy-http-server/request-id"),
),
)

rustCrate.withModule(ServerRustModule.Types) {
pubUseSmithyPrimitives(codegenContext, codegenContext.model, rustCrate)(this)
rustTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,11 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency
import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext

class ScopeMacroGenerator(
private val codegenContext: ServerCodegenContext,
) {
private val runtimeConfig = codegenContext.runtimeConfig
private val codegenScope =
arrayOf(
"SmithyHttpServer" to ServerCargoDependency.smithyHttpServer(runtimeConfig).toType(),
)

/** Calculate all `operationShape`s contained within the `ServiceShape`. */
private val index = TopDownIndex.of(codegenContext.model)
private val operations = index.getContainedOperations(codegenContext.serviceShape).toSortedSet(compareBy { it.id })
Expand All @@ -37,7 +30,7 @@ class ScopeMacroGenerator(

// When writing `macro_rules!` we add whitespace between `$` and the arguments to avoid Kotlin templating.

// To acheive the desired API we need to calculate the set theoretic complement `B \ A`.
// To achieve the desired API we need to calculate the set theoretic complement `B \ A`.
// The macro below, for rules prefixed with `@`, encodes a state machine which performs this.
// The initial state is `(A) () (B)`, where `A` and `B` are lists of elements of `A` and `B`.
// The rules, in order:
Expand Down Expand Up @@ -87,9 +80,9 @@ class ScopeMacroGenerator(

rustTemplate(
"""
/// A macro to help with scoping [plugins](#{SmithyHttpServer}::plugin) to a subset of all operations.
/// A macro to help with scoping [plugins](crate::server::plugin) to a subset of all operations.
///
/// In contrast to [`aws_smithy_http_server::scope`](#{SmithyHttpServer}::scope), this macro has knowledge
/// In contrast to [`crate::server::scope`](crate::server::scope), this macro has knowledge
/// of the service and any operations _not_ specified will be placed in the opposing group.
///
/// ## Example
Expand All @@ -109,7 +102,7 @@ class ScopeMacroGenerator(
/// }
/// }
///
/// ## use #{SmithyHttpServer}::plugin::{Plugin, Scoped};
/// ## use $crateName::server::plugin::{Plugin, Scoped};
/// ## use $crateName::scope;
/// ## struct MockPlugin;
/// ## impl<S, Op, T> Plugin<S, Op, T> for MockPlugin { type Output = u32; fn apply(&self, input: T) -> u32 { 3 } }
Expand All @@ -125,13 +118,13 @@ class ScopeMacroGenerator(
// Completed, render impls
(@ $ name: ident, $ contains: ident () ($($ temp: ident)*) ($($ not_member: ident)*)) => {
$(
impl #{SmithyHttpServer}::plugin::scoped::Membership<$ temp> for $ name {
type Contains = #{SmithyHttpServer}::plugin::scoped::$ contains;
impl $ crate::server::plugin::scoped::Membership<$ temp> for $ name {
type Contains = $ crate::server::plugin::scoped::$ contains;
}
)*
$(
impl #{SmithyHttpServer}::plugin::scoped::Membership<$ not_member> for $ name {
type Contains = #{SmithyHttpServer}::plugin::scoped::$ contains;
impl $ crate::server::plugin::scoped::Membership<$ not_member> for $ name {
type Contains = $ crate::server::plugin::scoped::$ contains;
}
)*
};
Expand All @@ -147,7 +140,7 @@ class ScopeMacroGenerator(
}
) => {
use $ crate::operation_shape::*;
#{SmithyHttpServer}::scope! {
$ crate::server::scope! {
$(##[$ attrs])*
$ vis struct $ name {
includes: [$($ include),*],
Expand All @@ -164,7 +157,7 @@ class ScopeMacroGenerator(
) => {
use $ crate::operation_shape::*;
#{SmithyHttpServer}::scope! {
$ crate::server::scope! {
$(##[$ attrs])*
$ vis struct $ name {
includes: [],
Expand All @@ -175,7 +168,6 @@ class ScopeMacroGenerator(
};
}
""",
*codegenScope,
"FurtherTests" to furtherTests,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,14 @@ open class ServerRootGenerator(
//! ## Using $serviceName
//!
//! The primary entrypoint is [`$serviceName`]: it satisfies the [`Service<http::Request, Response = http::Response>`](#{Tower}::Service)
//! trait and therefore can be handed to a [`hyper` server](https://github.com/hyperium/hyper) via [`$serviceName::into_make_service`] or used in Lambda via [`LambdaHandler`](#{SmithyHttpServer}::routing::LambdaHandler).
//! trait and therefore can be handed to a [`hyper` server](https://github.com/hyperium/hyper) via [`$serviceName::into_make_service`]
//! or used in AWS Lambda
##![cfg_attr(
feature = "aws-lambda",
doc = " via [`LambdaHandler`](crate::server::routing::LambdaHandler).")]
##![cfg_attr(
not(feature = "aws-lambda"),
doc = " by enabling the `aws-lambda` feature flag and utilizing the `LambdaHandler`.")]
//! The [`crate::${InputModule.name}`], ${if (!hasErrors) "and " else ""}[`crate::${OutputModule.name}`], ${if (hasErrors) "and [`crate::${ErrorModule.name}`]" else "" }
//! modules provide the types used in each operation.
//!
Expand All @@ -93,10 +100,8 @@ open class ServerRootGenerator(
//!
//! ###### Running on Lambda
//!
//! This requires the `aws-lambda` feature flag to be passed to the [`#{SmithyHttpServer}`] crate.
//!
//! ```rust,ignore
//! use #{SmithyHttpServer}::routing::LambdaHandler;
//! use $crateName::server::routing::LambdaHandler;
//! use $crateName::$serviceName;
//!
//! ## async fn dummy() {
Expand All @@ -120,10 +125,10 @@ open class ServerRootGenerator(
//! Plugins allow you to build middleware which is aware of the operation it is being applied to.
//!
//! ```rust,no_run
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as LoggingPlugin;
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as MetricsPlugin;
//! ## use $crateName::server::plugin::IdentityPlugin as LoggingPlugin;
//! ## use $crateName::server::plugin::IdentityPlugin as MetricsPlugin;
//! ## use #{Hyper}::Body;
//! use #{SmithyHttpServer}::plugin::HttpPlugins;
//! use $crateName::server::plugin::HttpPlugins;
//! use $crateName::{$serviceName, ${serviceName}Config, $builderName};
//!
//! let http_plugins = HttpPlugins::new()
Expand All @@ -133,14 +138,14 @@ open class ServerRootGenerator(
//! let builder: $builderName<Body, _, _, _> = $serviceName::builder(config);
//! ```
//!
//! Check out [`#{SmithyHttpServer}::plugin`] to learn more about plugins.
//! Check out [`crate::server::plugin`] to learn more about plugins.
//!
//! #### Handlers
//!
//! [`$builderName`] provides a setter method for each operation in your Smithy model. The setter methods expect an async function as input, matching the signature for the corresponding operation in your Smithy model.
//! We call these async functions **handlers**. This is where your application business logic lives.
//!
//! Every handler must take an `Input`, and optional [`extractor arguments`](#{SmithyHttpServer}::request), while returning:
//! Every handler must take an `Input`, and optional [`extractor arguments`](crate::server::request), while returning:
//!
//! * A `Result<Output, Error>` if your operation has modeled errors, or
//! * An `Output` otherwise.
Expand All @@ -162,7 +167,7 @@ open class ServerRootGenerator(
//! ## struct Error;
//! ## struct State;
//! ## use std::net::SocketAddr;
//! use #{SmithyHttpServer}::request::{extension::Extension, connect_info::ConnectInfo};
//! use $crateName::server::request::{extension::Extension, connect_info::ConnectInfo};
//!
//! async fn handler_with_no_extensions(input: Input) -> Output {
//! todo!()
Expand All @@ -181,7 +186,7 @@ open class ServerRootGenerator(
//! }
//! ```
//!
//! See the [`operation module`](#{SmithyHttpServer}::operation) for information on precisely what constitutes a handler.
//! See the [`operation module`](crate::operation) for information on precisely what constitutes a handler.
//!
//! #### Build
//!
Expand Down Expand Up @@ -233,7 +238,6 @@ open class ServerRootGenerator(
"HandlerImports" to handlerImports(crateName, operations, commentToken = "//!"),
"Handlers" to handlers,
"ExampleHandler" to operations.take(1).map { operation -> DocHandlerGenerator(codegenContext, operation, builderFieldNames[operation]!!, "//!").docSignature() },
"SmithyHttpServer" to ServerCargoDependency.smithyHttpServer(codegenContext.runtimeConfig).toType(),
"Hyper" to ServerCargoDependency.HyperDev.toType(),
"Tokio" to ServerCargoDependency.TokioDev.toType(),
"Tower" to ServerCargoDependency.Tower.toType(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,8 @@ class ServerRuntimeTypesReExportsGenerator(
fun render(writer: RustWriter) {
writer.rustTemplate(
"""
pub mod body {
pub use #{SmithyHttpServer}::body::BoxBody;
}
pub mod operation {
pub use #{SmithyHttpServer}::operation::OperationShape;
}
pub mod plugin {
pub use #{SmithyHttpServer}::plugin::HttpPlugins;
pub use #{SmithyHttpServer}::plugin::ModelPlugins;
pub use #{SmithyHttpServer}::plugin::HttpMarker;
pub use #{SmithyHttpServer}::plugin::ModelMarker;
pub use #{SmithyHttpServer}::plugin::Plugin;
pub use #{SmithyHttpServer}::plugin::PluginStack;
}
pub mod request {
pub use #{SmithyHttpServer}::request::FromParts;
##[cfg(feature = "aws-lambda")]
pub mod lambda {
pub use #{SmithyHttpServer}::request::lambda::Context;
}
}
pub mod response {
pub use #{SmithyHttpServer}::response::IntoResponse;
}
pub mod routing {
pub use #{SmithyHttpServer}::routing::IntoMakeService;
pub use #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo;
pub use #{SmithyHttpServer}::routing::Router;
##[cfg(feature = "aws-lambda")]
pub use #{SmithyHttpServer}::routing::LambdaHandler;
}
pub use #{SmithyHttpServer}::instrumentation;
pub use #{SmithyHttpServer}::protocol;
pub use #{SmithyHttpServer}::Extension;
// Re-export all types from the `aws-smithy-http-server` crate.
pub use #{SmithyHttpServer}::*;
""",
*codegenScope,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.server.smithy

import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.testModule
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverIntegrationTest

class ServerTypesReExportTest {
private val sampleModel =
"""
namespace amazon
use aws.protocols#restJson1
@restJson1
service SampleService {
operations: [SampleOperation]
}
@http(uri: "/sample", method: "GET")
operation SampleOperation {
output := {}
}
""".asSmithyModel(smithyVersion = "2")

@Test
fun `ensure types are exported from aws-smithy-http-server`() {
serverIntegrationTest(sampleModel, IntegrationTestParams(service = "amazon#SampleService")) { _, rustCrate ->
rustCrate.testModule {
fun Set<String>.generateUseStatements(prefix: String) =
this.joinToString(separator = "\n") {
"#[allow(unused_imports)] use $prefix::$it;"
}

// Ensure all types that were exported before version 0.64 and used
// under the `{generated_sdk_crate_name}::server` namespace remain available.
// Additionally, include all types requested by customers.
unitTest(
"types_exists_in_server_module",
setOf(
"extension::{OperationExtensionExt, OperationExtension}",
"plugin::Scoped",
"routing::{Route, RoutingService}",
"body::boxed",
"shape_id::ShapeId",
"body::BoxBody",
"operation::OperationShape",
"plugin::HttpPlugins",
"plugin::ModelPlugins",
"plugin::HttpMarker",
"plugin::ModelMarker",
"plugin::Plugin",
"plugin::PluginStack",
"request::{self, FromParts}",
"response::IntoResponse",
"routing::IntoMakeService",
"routing::IntoMakeServiceWithConnectInfo",
"routing::Router",
"instrumentation",
"protocol",
"Extension",
"scope",
).generateUseStatements("crate::server"),
)

unitTest(
"request_id_reexports",
additionalAttributes = listOf(Attribute.featureGate("request-id")),
) {
rustTemplate(
"""
##[allow(unused_imports)] use crate::server::request::request_id::ServerRequestId;
""",
)
}

unitTest(
"aws_lambda_reexports",
additionalAttributes = listOf(Attribute.featureGate("aws-lambda")),
) {
rustTemplate(
"""
##[allow(unused_imports)] use crate::server::{request::lambda::Context, routing::LambdaHandler};
""",
)
}
}
}
}
}
5 changes: 3 additions & 2 deletions examples/pokemon-service-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ tower = "0.4"
# Local paths
aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x"] }
aws-smithy-runtime-api = { path = "../../rust-runtime/aws-smithy-runtime-api", features = ["client"] }
aws-smithy-http-server = { path = "../../rust-runtime/aws-smithy-http-server" }
pokemon-service-client = { path = "../pokemon-service-client" }
pokemon-service-client = { path = "../pokemon-service-client/", features = [
"behavior-version-latest",
] }
pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk" }

[dev-dependencies]
Expand Down
Loading

0 comments on commit d8fbf47

Please sign in to comment.