Skip to content

Commit

Permalink
Unhide new service builder and deprecate the prior (#1886)
Browse files Browse the repository at this point in the history
Co-authored-by: david-perez <d@vidp.dev>
  • Loading branch information
hlbarber and david-perez authored Dec 2, 2022
1 parent b7f1a57 commit 17cb98c
Show file tree
Hide file tree
Showing 26 changed files with 289 additions and 80 deletions.
105 changes: 105 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -502,3 +502,108 @@ in non-serverless environments (e.g. via `hyper`).
references = ["smithy-rs#2035"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "server" }
author = "LukeMathWalker"

[[smithy-rs]]
message = """
### Plugins/New Service Builder API
The `Router` struct has been replaced by a new `Service` located at the root of the generated crate. Its name coincides with the same name as the Smithy service you are generating.
```rust
use pokemon_service_server_sdk::PokemonService;
```
The new service builder infrastructure comes with a `Plugin` system which supports middleware on `smithy-rs`. See the [mididleware documentation](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/middleware.md) and the [API documentation](https://docs.rs/aws-smithy-http-server/latest/aws_smithy_http_server/plugin/index.html) for more details.
Usage of the new service builder API:
```rust
// Apply a sequence of plugins using `PluginPipeline`.
let plugins = PluginPipeline::new()
// Apply the `PrintPlugin`.
// This is a dummy plugin found in `rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/plugin.rs`
.print()
// Apply the `InstrumentPlugin` plugin, which applies `tracing` instrumentation.
.instrument();
// Construct the service builder using the `plugins` defined above.
let app = PokemonService::builder_with_plugins(plugins)
// Assign all the handlers.
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage)
.get_server_statistics(get_server_statistics)
.capture_pokemon(capture_pokemon)
.do_nothing(do_nothing)
.check_health(check_health)
// Construct the `PokemonService`.
.build()
// If handlers are missing a descriptive error will be provided.
.expect("failed to build an instance of `PokemonService`");
```
See the `rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/bin` folder for various working examples.
### Public `FromParts` trait
Previously, we only supported one [`Extension`](https://docs.rs/aws-smithy-http-server/latest/aws_smithy_http_server/request/struct.Extension.html) as an additional argument provided to the handler. This number has been increased to 8 and the argument type has been broadened to any struct which implements the [`FromParts`](https://docs.rs/aws-smithy-http-server/latest/aws_smithy_http_server/request/trait.FromParts.html) trait. The trait is publicly exported and therefore provides customers with the ability to extend the domain of the handlers.
As noted, a ubiqutious example of a struct that implements `FromParts` is the `Extension` struct, which extracts state from the `Extensions` typemap of a [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html). A new example is the `ConnectInfo` struct which allows handlers to access the connection data. See the `rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/bin/pokemon-service-connect-info.rs` example.
```rust
fn get_pokemon_species(
input: GetPokemonSpeciesInput,
state: Extension<State>,
address: ConnectInfo<SocketAddr>
) -> Result<GetPokemonSpeciesOutput, GetPokemonSpeciesError> {
todo!()
}
```
In addition to the [`ConnectInfo`](https://docs.rs/aws-smithy-http-server/latest/aws_smithy_http_server/request/connect_info/struct.ConnectInfo.html) extractor, we also have added [lambda extractors](https://docs.rs/aws-smithy-http-server/latest/aws_smithy_http_server/request/lambda/index.html) which are feature gated with `aws-lambda`.
[`FromParts` documentation](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/from_parts.md) has been added.
### New Documentation
New sections to have been added to the [server side of the book](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/overview.md).
These include:
- [Middleware](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/middleware.md)
- [Accessing Un-modelled Data](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/from_parts.md)
- [Anatomy of a Service](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/anatomy.md)
This release also introduces extensive documentation at the root of the generated crate. For best results compile documentation with `cargo +nightly doc --open`.
### Deprecations
The existing service builder infrastructure, `OperationRegistryBuilder`/`OperationRegistry`/`Router`, is now deprecated. Customers should migrate to the newer scheme described above. The deprecated types will be removed in a future release.
"""
references = [
"smithy-rs#1620",
"smithy-rs#1666",
"smithy-rs#1731",
"smithy-rs#1736",
"smithy-rs#1753",
"smithy-rs#1738",
"smithy-rs#1782",
"smithy-rs#1829",
"smithy-rs#1837",
"smithy-rs#1891",
"smithy-rs#1840",
"smithy-rs#1844",
"smithy-rs#1858",
"smithy-rs#1930",
"smithy-rs#1999",
"smithy-rs#2003",
"smithy-rs#2008",
"smithy-rs#2010",
"smithy-rs#2019",
"smithy-rs#2020",
"smithy-rs#2021",
"smithy-rs#2038",
"smithy-rs#2039",
"smithy-rs#2041",
]
meta = { "breaking" = true, "tada" = true, "bug" = false, "target" = "server" }
author = "hlbarber"
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,28 @@ sealed class Attribute {
val NonExhaustive = Custom("non_exhaustive")
}

data class Deprecated(val since: String?, val note: String?) : Attribute() {
override fun render(writer: RustWriter) {
writer.raw("#[deprecated")
if (since != null || note != null) {
writer.raw("(")
if (since != null) {
writer.raw("""since = "$since"""")

if (note != null) {
writer.raw(", ")
}
}

if (note != null) {
writer.raw("""note = "$note"""")
}
writer.raw(")")
}
writer.raw("]")
}
}

data class Derives(val derives: Set<RuntimeType>) : Attribute() {
override fun render(writer: RustWriter) {
if (derives.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ ${operationImplementationStubs(operations)}
}

private fun renderOperationRegistryStruct(writer: RustWriter) {
writer.rust("""##[deprecated(since = "0.52.0", note = "`OperationRegistry` is part of the deprecated service builder API. Use `$serviceName::builder` instead.")]""")
writer.rustBlock("pub struct $operationRegistryNameWithArguments") {
val members = operationNames
.mapIndexed { i, operationName -> "$operationName: Op$i" }
Expand All @@ -182,6 +183,7 @@ ${operationImplementationStubs(operations)}
* Renders the `OperationRegistryBuilder` structure, used to build the `OperationRegistry`.
*/
private fun renderOperationRegistryBuilderStruct(writer: RustWriter) {
writer.rust("""##[deprecated(since = "0.52.0", note = "`OperationRegistryBuilder` is part of the deprecated service builder API. Use `$serviceName::builder` instead.")]""")
writer.rustBlock("pub struct $operationRegistryBuilderNameWithArguments") {
val members = operationNames
.mapIndexed { i, operationName -> "$operationName: Option<Op$i>" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.InputsModule
import software.amazon.smithy.rust.codegen.core.smithy.OutputsModule
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol
Expand Down Expand Up @@ -69,8 +70,8 @@ open class ServerServiceGenerator(
//!
//! ## Using $serviceName
//!
//! The primary entrypoint is [`$serviceName`]: it satisfies the [`Service<http::Request, Response = http::Response>`]
//! trait and therefore can be handed to a [`hyper` server] via [`$serviceName::into_make_service`] or used in Lambda via [`LambdaHandler`](#{SmithyHttpServer}::routing::LambdaHandler).
//! 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).
//! The [`crate::${InputsModule.name}`], ${if (!hasErrors) "and " else ""}[`crate::${OutputsModule.name}`], ${if (hasErrors) "and [`crate::${ErrorsModule.name}`]" else "" }
//! modules provide the types used in each operation.
//!
Expand Down Expand Up @@ -225,6 +226,7 @@ open class ServerServiceGenerator(
"Handlers" to handlers,
"ExampleHandler" to operations.take(1).map { operation -> DocHandlerGenerator(codegenContext, operation, builderFieldNames[operation]!!, "//!").docSignature() },
"SmithyHttpServer" to ServerCargoDependency.SmithyHttpServer(codegenContext.runtimeConfig).toType(),
"Tower" to ServerCargoDependency.Tower.toType(),
)
}

Expand All @@ -236,7 +238,6 @@ open class ServerServiceGenerator(
rustCrate.lib {
documentation(this)

rust("##[doc(inline, hidden)]")
rust("pub use crate::service::{$serviceName, ${serviceName}Builder, MissingOperationsError};")
}

Expand All @@ -255,35 +256,35 @@ open class ServerServiceGenerator(
renderOperationHandler(this, operations)
}
rustCrate.withModule(
RustModule.public(
RustModule.LeafModule(
"operation_registry",
RustMetadata(
visibility = Visibility.PUBLIC,
additionalAttributes = listOf(
Attribute.Deprecated("0.52.0", "This module exports the deprecated `OperationRegistry`. Use the service builder exported from your root crate."),
),
),
"""
Contains the [`operation_registry::OperationRegistry`], a place where
you can register your service's operation implementations.
## Deprecation
This service builder is deprecated - use [`${codegenContext.serviceShape.id.name.toPascalCase()}::builder_with_plugins`] or [`${codegenContext.serviceShape.id.name.toPascalCase()}::builder_without_plugins`] instead.
""",
),
) {
renderOperationRegistry(this, operations)
}

// TODO(https://github.com/awslabs/smithy-rs/issues/1707): Remove, this is temporary.
rustCrate.withModule(
RustModule.LeafModule(
"operation_shape",
RustMetadata(
visibility = Visibility.PUBLIC,
additionalAttributes = listOf(
Attribute.DocHidden,
),
),
),
RustModule.public("operation_shape"),
) {
ServerOperationShapeGenerator(operations, codegenContext).render(this)
}

// TODO(https://github.com/awslabs/smithy-rs/issues/1707): Remove, this is temporary.
rustCrate.withModule(
RustModule.LeafModule("service", RustMetadata(visibility = Visibility.PRIVATE, additionalAttributes = listOf(Attribute.DocHidden)), null),
RustModule.private("service"),
) {
ServerServiceGeneratorV2(
codegenContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class ServerServiceGeneratorV2(
#{SmithyHttpServer}::routing::IntoMakeService::new(self)
}
/// Converts [`$serviceName`] into a [`MakeService`](tower::make::MakeService) with [`ConnectInfo`](#{SmithyHttpServer}::request::connect_info::ConnectInfo).
pub fn into_make_service_with_connect_info<C>(self) -> #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo<Self, C> {
#{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo::new(self)
Expand All @@ -396,7 +397,7 @@ class ServerServiceGeneratorV2(
/// Applies [`Route::new`](#{SmithyHttpServer}::routing::Route::new) to all routes.
///
/// This has the effect of erasing all types accumulated via [`layer`].
/// This has the effect of erasing all types accumulated via [`layer`]($serviceName::layer).
pub fn boxed<B>(self) -> $serviceName<#{SmithyHttpServer}::routing::Route<B>>
where
S: #{Tower}::Service<
Expand Down
10 changes: 6 additions & 4 deletions design/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
- [Backwards Compatibility](smithy/backwards-compat.md)

- [Server](./server/overview.md)
- [Generating Common Service Code](./server/code_generation.md)
- [Generating the Pokémon Service](./server/pokemon_service.md)
- [Instrumentation](./server/instrumentation.md)
<!-- - [The Anatomy of a Service](./server/anatomy.md) -->
- [Middleware](./middleware.md)
- [Instrumentation](./instrumentation.md)
- [Accessing Un-modelled Data](./from_parts.md)
- [The Anatomy of a Service](./anatomy.md)
- [Generating Common Service Code](./code_generation.md)
- [Generating the Pokémon Service](./pokemon_service.md)

- [RFCs](./rfcs/overview.md)
- [RFC-0001: Sharing configuration between multiple clients](./rfcs/rfc0001_shared_config.md)
Expand Down
14 changes: 11 additions & 3 deletions design/src/server/instrumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,19 @@ Smithy provides an out-the-box middleware which:
- Opens a DEBUG level span, prior to request handling, including the operation name and request URI and headers.
- Emits a DEBUG level event, after to request handling, including the response headers and status code.

This is applied by default and can be enabled and disabled by filtering on `aws_smithy_http_server::instrumentation`.
This is enabled via the `instrument` method provided by the `aws_smithy_http_server::instrumentation::InstrumentExt` trait.

<!-- TODO: Link to it when the logging module is no longer `#[doc(hidden)]` -->
```rust
use aws_smithy_http_server::instrumentation::InstrumentExt;

<!-- TODO: Document use of the `InstrumentExt` after the new service builder is released. -->
let plugins = PluginPipeline::new().instrument();
let app = PokemonService::builder_with_plugins(plugins)
.get_pokemon_species(/* handler */)
/* ... */
.build();
```

<!-- TODO: Link to it when the logging module is no longer `#[doc(hidden)]` -->

### Example

Expand Down
8 changes: 4 additions & 4 deletions design/src/server/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Smithy Rust provides the ability to generate a server whose operations are provided by the customer.

- [Middleware](./middleware.md)
- [Instrumentation](./instrumentation.md)
- [Accessing Un-modelled Data](./from_parts.md)
- [The Anatomy of a Service](./anatomy.md)
- [Generating Common Service Code](./code_generation.md)
- [Generating the Pokémon Service](./pokemon_service.md)
<!-- - [Middleware](./middleware.md) -->
- [Instrumentation](./instrumentation.md)
<!-- - [The Anatomy of a Service](./anatomy.md) -->
<!-- - [Accessing Un-modelled Data](./from_parts.md) -->
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ async fn main() {
.expect("failed to build an instance of PokemonService");

// Start the [`hyper::Server`].
let bind: std::net::SocketAddr = format!("{}:{}", args.address, args.port)
let bind: SocketAddr = format!("{}:{}", args.address, args.port)
.parse()
.expect("unable to parse the server bind address and port");
let server = hyper::Server::bind(&bind).serve(app.into_make_service_with_connect_info::<std::net::SocketAddr>());
let server = hyper::Server::bind(&bind).serve(app.into_make_service_with_connect_info::<SocketAddr>());

// Run forever-ish...
if let Err(err) = server.await {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,50 @@
// This program is exported as a binary named `pokemon-service-lambda`.
use std::sync::Arc;

use aws_smithy_http_server::{routing::LambdaHandler, AddExtensionLayer, Router};
use aws_smithy_http_server::{
plugin::PluginPipeline, request::lambda::Context, routing::LambdaHandler, AddExtensionLayer, Extension,
};
use pokemon_service::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, get_storage, setup_tracing,
State,
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, plugin::PrintExt,
setup_tracing, State,
};
use pokemon_service_server_sdk::operation_registry::OperationRegistryBuilder;
use pokemon_service_server_sdk::{error, input, output, PokemonService};

/// Retrieves the user's storage and records the .
pub async fn get_storage_lambda(
input: input::GetStorageInput,
_state: Extension<Arc<State>>,
context: Context,
) -> Result<output::GetStorageOutput, error::GetStorageError> {
tracing::debug!(request_id = %context.request_id, "attempting to authenticate storage user");

// We currently only support Ash and he has nothing stored
if !(input.user == "ash" && input.passcode == "pikachu123") {
tracing::debug!("authentication failed");
return Err(error::GetStorageError::NotAuthorized(error::NotAuthorized {}));
}
Ok(output::GetStorageOutput { collection: vec![] })
}

#[tokio::main]
pub async fn main() {
setup_tracing();

let app: Router = OperationRegistryBuilder::default()
// Apply the `PrintPlugin` defined in `plugin.rs`
let plugins = PluginPipeline::new().print();
let app = PokemonService::builder_with_plugins(plugins)
// Build a registry containing implementations to all the operations in the service. These
// are async functions or async closures that take as input the operation's input and
// return the operation's output.
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage)
.get_storage(get_storage_lambda)
.get_server_statistics(get_server_statistics)
.capture_pokemon(capture_pokemon)
.do_nothing(do_nothing)
.check_health(check_health)
.build()
.expect("Unable to build operation registry")
// Convert it into a router that will route requests to the matching operation
// implementation.
.into();

// Setup shared state and middlewares.
let shared_state = Arc::new(State::default());
let app = app.layer(AddExtensionLayer::new(shared_state));
.expect("failed to build an instance of PokemonService")
// Set up shared state and middlewares.
.layer(&AddExtensionLayer::new(Arc::new(State::default())));

let handler = LambdaHandler::new(app);
let lambda = lambda_http::run(handler);
Expand Down
Loading

0 comments on commit 17cb98c

Please sign in to comment.