dropshot
repository·main·Indexed 22 days ago
https://github.com/oxidecomputer/dropshotAn opinionated, lightweight Rust crate for exposing REST APIs. It provides first-class support for OpenAPI specification generation and consistent pagination. The library allows developers to define API traits using the #[dropshot::api_description] macro and expose methods as endpoints via the #[endpoint] attribute.
What's inside dropshot
- Dropshot is an opinionated, lightweight Rust crate designed for exposing REST APIs. It focuses on simplicity and provides first-class support for OpenAPI by generating precise specifications directly from your code. Additionally, it includes built-in primitives for consistent pagination, including OpenAPI extensions to denote pagination in the generated spec.
Migrate RequestContext and Extractors in v0.9.0
mainVersion 0.9.0 introduced significant changes to how
RequestContextandExtractors are handled to improve type safety and performance.1. Remove Arc from RequestContext
RequestContextis no longer wrapped in anArc. Endpoint functions and extractors now acceptRequestContext<T>directly. Action: Change the first argument of every endpoint function fromArc<RequestContext<T>>toRequestContext<T>.2. Split Extractors into Shared and Exclusive
The
Extractortrait was split intoSharedExtractorandExclusiveExtractorto prevent runtime errors when multiple extractors attempt to access the request body.- SharedExtractor: For extractors that only need to read headers, the URL, or the method (does not consume the body).
- ExclusiveExtractor: For extractors that need to read the request body (e.g.,
TypedBody,UntypedBody,WebsocketConnection, orRawRequest).
Rules for Exclusive Extractors:
- An endpoint can have at most one
ExclusiveExtractor. - The
ExclusiveExtractormust be the last argument in the endpoint function signature.
3. Accessing the Raw Request
To access the raw
hyper::Request, use theRawRequestextractor instead of accessingrqctx.request(which was previously behind a Mutex). Action: Addraw_request: RawRequestas the last argument to your endpoint, then uselet request = raw_request.into_inner();.How Dropshot handles request middleware and shared logic
mainUnlike many web frameworks that use a chain of middleware handlers (which can make control flow and implicit dependencies difficult to track), Dropshot encourages using explicit function calls to share logic between handlers.
Instead of a middleware that modifies a request object (e.g., adding an
authfield), Dropshot's design pattern involves utility functions that return typed context objects.Example Pattern:
- An authentication utility function is called within a handler.
- This function returns an
AuthzContextstruct. - Any subsequent logic requiring authorization consumes that
AuthzContextas a direct function argument.
This approach ensures that dependencies are explicit: if a handler requires an
AuthzContext, it must call the function to obtain it, making the code easier to reason about and maintain.Implement custom error types with HttpResponseError
mainStarting in v0.15.0, endpoint handlers are no longer restricted to returningdropshot::HttpError. They can return any error type that implements thedropshot::HttpResponseErrortrait. This allows you to use domain-specific error types and automatically generate OpenAPI response schemas for them.Upgrade to hyper 1.0 and http 1.0 in v0.12.0
mainDropshot v0.12.0 updated to support
hyper1.0 andhttp1.0. When upgrading, you must also update your own crate's dependencies to these versions.Steps to upgrade:
- Update
hyperandhttpto 1.0 (or newer compatible versions) in yourCargo.toml. - Replace all references to
hyper::Bodywithdropshot::Body. - You may need to use
http-body-utilto assist withdropshot::Bodymanipulations.
- Update
Migrate to ErrorStatusCode in v0.15.0
mainIn version 0.15.0,
HttpErrorwas changed to usedropshot::ErrorStatusCodeinstead ofhttp::StatusCode.ErrorStatusCodeis a newtype wrapper aroundhttp::StatusCodethat is restricted to 4xx and 5xx status codes.To update your code:
- Replace
http::StatusCode::...constants withdropshot::ErrorStatusCode::.... - For non-standard status codes, use
ErrorStatusCode::from_u16(code). - Note that
ErrorStatusCodeimplementsTryFrom<http::StatusCode>for easy conversion from external sources.
// Before dropshot::HttpError { status: http::StatusCode::NOT_FOUND, // ... } // After dropshot::HttpError { status: dropshot::ErrorStatusCode::NOT_FOUND, // ... } // For extension codes dropshot::ErrorStatusCode::from_u16(420).expect("420 is a valid 4xx status code")- Replace
Run Dropshot examples
mainTo run the provided examples in the repository, use
cargo runwith the--exampleflag followed by the name of the example (without the.rsextension).Example:
cargo run --example basicMigrate to the new HttpServer API
mainThe
HttpServerimplementation has been split into two distinct types to better separate server configuration from a running server instance:HttpServerStarter: Used to construct and configure the server. UseHttpServerStarter::start()to begin execution.HttpServer: Represents the active, running server.
Key changes in behavior:
- Starting the server: Instead of calling
.run()on anHttpServerobject, you now call.start()on anHttpServerStarterobject, which returns anHttpServer. - Awaiting shutdown: In previous versions,
HttpServerreturned atokio::JoinHandleand requiredwait_for_shutdown. In the new version,HttpServerimplementsFutureand can be directly.await-ed to wait for the server to complete.
// Old Version: let mut server = HttpServer::new( /* Arguments are the same between versions */ ) .map_err(|error| format!("failed to start server: {}", error))?; let server_task = server.run(); server.wait_for_shutdown(server_task).await; // New Version let server = HttpServerStarter::new( /* Arguments are the same between versions */ ) .map_err(|error| format!("failed to start server: {}", error))? .start(); server.await;Migrate to ServerBuilder in v0.13.0
mainThe
ServerBuilderis now the primary way to construct a Dropshot server, replacingHttpServerStarter. The builder provides structured errors and is the only way to add new construction-time options (like API versioning) in the future.Migration Patterns:
Standard (Non-TLS):
// Old HttpServerStarter::new(&config, api, private, &log).map_err(...)? .start() // New ServerBuilder::new(api, private, log).config(config).start().map_err(...)?With TLS:
// Old HttpServerStarter::new_with_tls(&config, api, private, &log, tls).map_err(...)? .start() // New ServerBuilder::new(api, private, log).config(config).tls(tls).start().map_err(...)?Intermediate Starter: If you need to hold a starter object before calling
.start(), use.build_starter():let starter = ServerBuilder::new(api, private, log).config(config).build_starter().map_err(...)?; // ... starter.start();Publish a new release
mainDropshot consists of two packages,
dropshotanddropshot_endpoint, which are always released together with the same version number. A release involves creating a git tag with the version number and publishing both packages to crates.io.Release Workflow
- Prepare Changelog: Ensure
CHANGELOG.adocis up to date. Breaking changes should include concise summaries of how users are affected and how to upgrade safely. - Verify Working Directory:
- Ensure you are on the
mainbranch and synced with upstream (git pull). - Ensure
git statusshows no uncommitted files. - Run
git clean -nxdto ensure no stale build artifacts are present. - Verify that CI has passed for your current commit on GitHub.
- Ensure you are on the
- Identify Release Metadata:
- Find the
PREV_RELEASE_TAG(the tag used for the most recent release). - Determine the
RELEASE_KIND(e.g.,patch,minor, ormajor) or specify aNEW_VERSION.
- Find the
- Execute Release:
- Install/update
cargo-release. - Run the
cargo releasecommand.
- Install/update
- Finalize:
- Push the resulting commit and tags to the remote repository.
Note: You may need to temporarily allow administrators to override branch protection to push the release commit and tags.
# Install cargo-release cargo install cargo-release # Execute the release $ cargo release --prev-tag-name=PREV_RELEASE_TAG -vv --execute RELEASE_KIND|NEW_VERSION # Push changes $ git push $ git push --tags- Prepare Changelog: Ensure
Configure the maximum request size
mainThe maximum request size is no longer hardcoded. It is now a configurable setting. The default value is 1024 bytes.Configure tag policies with `tag_config` in `#[dropshot::api_description]`
mainYou can organize your OpenAPI documentation using tags. By providing a
tag_configargument to the#[dropshot::api_description]macro, you can enforce compliance at runtime during endpoint registration.Supported fields in
tag_config:tags: A map of tag names to their metadata (description, external documentation).allow_other_tags: Boolean. Iffalse, any tag not explicitly defined in thetagsmap will cause a registration error. Defaults tofalseiftag_configis provided.policy: AnEndpointTagPolicy(e.g.,Any,ExactlyOne) that defines how many tags an endpoint must have.