HTTP Service with Axum
The HttpService
provides
support for serving an HTTP API using axum. The HttpService
automatically applies
all the configured middleware and initializers automatically, so all that's needed in most cases to serve a production
ready API service is to define your routes, provide them to the HttpService
, and register the HttpService
with
the ServiceRegistry
.
use roadster::service::http::builder::HttpServiceBuilder;
const BASE: &str = "/api";
/// Set up the [`HttpServiceBuilder`]. This will then be registered with the
/// [`roadster::service::registry::ServiceRegistry`].
pub fn http_service(state: &AppContext) -> HttpServiceBuilder<AppContext> {
HttpServiceBuilder::new(Some(BASE), state)
// Multiple routers can be registered and they will all be merged together using the
// `axum::Router::merge` method.
.router(Router::new().route(&build_path(BASE, "/example_a"), get(example_a)))
// Create your routes as an `ApiRouter` in order to include it in the OpenAPI schema.
.api_router(ApiRouter::new().api_route(
&build_path(BASE, "/example_b"),
get_with(example_b::example_b_get, example_b::example_b_get_docs),
))
.api_router(ApiRouter::new().api_route(
&build_path(BASE, "/example_c"),
get_with(example_c::example_c_get, example_c::example_c_get_docs),
))
}
async fn example_a() -> impl IntoResponse {
()
}
example_b module
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ExampleBResponse {}
#[instrument(skip_all)]
pub async fn example_b_get(
State(_state): State<AppContext>,
) -> RoadsterResult<Json<ExampleBResponse>> {
Ok(Json(ExampleBResponse {}))
}
pub fn example_b_get_docs(op: TransformOperation) -> TransformOperation {
op.description("Example B API.")
.tag("Example B")
.response_with::<200, Json<ExampleBResponse>, _>(|res| res.example(ExampleBResponse {}))
}
example_c module
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ExampleCResponse {}
#[instrument(skip_all)]
pub async fn example_c_get(
State(_state): State<AppContext>,
) -> RoadsterResult<Json<ExampleCResponse>> {
Ok(Json(ExampleCResponse {}))
}
pub fn example_c_get_docs(op: TransformOperation) -> TransformOperation {
op.description("Example C API.")
.tag("Example C")
.response_with::<200, Json<ExampleCResponse>, _>(|res| res.example(ExampleCResponse {}))
}
OpenAPI Schema
If the open-api
feature is enabled, the service also supports generating an OpenAPI schema. The OpenAPI schema can be
accessed in various ways.
Via HTTP API
It's served by default at /<base>/_docs/api.json
# First, run your app
cargo run
# In a separate shell or browser, navigate to the API, e.g.
curl localhost:3000/api/_docs/api.json
Via CLI command
It can be generated via a CLI command
cargo run -- roadster open-api -o $HOME/open-api.json
Via the HttpService
directly
It can also be generated programmatically using the HttpService
directly.
type App = RoadsterApp<AppContext>;
async fn open_api() -> RoadsterResult<()> {
// Build the app
let app: App = RoadsterApp::builder()
.state_provider(|context| Ok(context))
.add_service_provider(move |registry, state| {
Box::pin(async move {
registry
.register_builder(crate::http::http_service(state))
.await?;
Ok(())
})
})
.build();
// Prepare the app
let prepared = prepare(app, PrepareOptions::builder().build()).await?;
// Get the `HttpService`
let http_service = prepared.service_registry.get::<HttpService>()?;
// Get the OpenAPI schema
let schema = http_service.open_api_schema(&OpenApiArgs::builder().build())?;
println!("{schema}");
Ok(())
}