OpenAPI is an open-source specification for describing RESTful API interface designs. It defines API request/response structures, parameters, return types, error codes, and other details in JSON or YAML format, making communication between clients and servers more explicit and standardized.
Originally the open-source version of the Swagger specification, OpenAPI has now become an independent project with support from many large enterprises and developers. Using the OpenAPI specification helps development teams collaborate better, reduce communication overhead, and improve efficiency. Additionally, OpenAPI provides tools for automatically generating API documentation, mock data, and test cases, facilitating development and testing workflows.
Salvo offers OpenAPI integration (adapted from utoipa). Leveraging its own features, Salvo elegantly extracts relevant OpenAPI data type information automatically from Handler
. It also integrates several popular open-source OpenAPI interfaces like SwaggerUI, scalar, rapidoc, and redoc.
To address Rust's long type names that may not be suitable for OpenAPI usage, salvo-oapi
provides the Namer
type, allowing customization of rules to modify type names in OpenAPI.
Example Code
Enter http://localhost:5800/swagger-ui
in your browser to view the Swagger UI page.
Salvo's OpenAPI integration is remarkably elegant. For the example above, compared to a regular Salvo project, we only needed to:
Enable the oapi
feature in Cargo.toml
: salvo = { workspace = true, features = ["oapi"] }
;
Replace #[handler]
with #[endpoint]
;
Use name: QueryParam<String, false>
to get query string values. When visiting http://localhost/hello?name=chris
, this name
query string will be parsed. Here, false
indicates the parameter is optional—accessing http://localhost/hello
won't trigger an error. Conversely, QueryParam<String, true>
means the parameter is mandatory, and its absence will return an error.
Create OpenAPI
and its corresponding Router
. OpenApi::new("test api", "0.0.1").merge_router(&router)
uses merge_router
to indicate that this OpenAPI
extracts necessary documentation information by parsing a specific route and its descendants. Routes whose Handler
doesn't provide documentation generation info will be ignored (e.g., those defined with #[handler]
instead of #[endpoint]
). In real projects, you can choose not to generate OpenAPI docs (or generate them partially) for development speed. Later, you can incrementally add OpenAPI interfaces by simply changing #[handler]
to #[endpoint]
and adjusting function signatures.
Import predefined common data extractors via use salvo::oapi::extract::*;
. Extractors provide Salvo with essential information for OpenAPI documentation generation.
QueryParam<T, const REQUIRED: bool>
: Extracts data from query strings. QueryParam<T, false>
means the parameter is optional, while QueryParam<T, true>
makes it mandatory (returns an error if missing).
HeaderParam<T, const REQUIRED: bool>
: Extracts data from request headers. HeaderParam<T, false>
indicates optionality; HeaderParam<T, true>
enforces requirement.
CookieParam<T, const REQUIRED: bool>
: Extracts data from request cookies. CookieParam<T, false>
allows omission; CookieParam<T, true>
requires presence.
PathParam<T>
: Extracts path parameters from URLs. Absence prevents route matching, making this inherently non-optional.
FormBody<T>
: Extracts information from form submissions.
JsonBody<T>
: Extracts information from JSON payloads.
#[endpoint]
When generating OpenAPI docs, use the #[endpoint]
macro instead of the regular #[handler]
macro—it's an enhanced version.
It gathers OpenAPI-required information from function signatures.
For details inconvenient to provide via signatures, add attributes directly in #[endpoint]
. These merge with signature-derived info, with attribute values overriding any conflicts.
You can mark a Handler as deprecated using Rust's built-in #[deprecated]
. While this attribute supports reasons/versioning, OpenAPI ignores such details, only recognizing deprecation status.
Code documentation comments auto-populate OpenAPI: the first line becomes the summary
, while the entire comment forms the description
.
Define data structures with #[derive(ToSchema)]
:
Optional settings via #[salvo(schema(...))]
:
example = ...
can be json!(...)
. json!(...)
parses to serde_json::Value
via serde_json::json!
.
xml(...)
defines XML object properties:
Generates [path parameters][path_parameters] from struct fields.
This is the #[derive]
implementation of the ToParameters
trait.
Normally, path parameters need defining in the endpoint's [#[salvo_oapi::endpoint(...parameters(...))]
][path_parameters]. But when using [struct
][struct] for parameters, this step is unnecessary. However, descriptions or default configuration changes still require defining [primitive types
][primitive] and [String
][std_string] path parameters or [tuple]-style ones in parameters(...)
.
Mark fields as deprecated with Rust's #[deprecated]
, reflected in the generated OpenAPI spec.
While #[deprecated]
supports reasons/versioning, OpenAPI only acknowledges boolean deprecation status. Reasons like #[deprecated = "There is better way to do this"]
won't appear in OpenAPI.
Field doc comments become parameter descriptions in OpenAPI.
#[salvo(parameters(...))]
These attributes apply to structs deriving ToParameters
via #[salvo(parameters(…))]
:
names(...)
: Defines comma-separated names for unnamed struct fields used as path parameters. Only for unnamed structs.style = ...
: Defines serialization for all parameters via [ParameterStyle
][style]. Defaults based on parameter_in
.default_parameter_in = ...
: Sets default parameter location from [parameter::ParameterIn
][in_enum]. Defaults to query
if unspecified.rename_all = ...
: Alternative to serde
's rename_all
with identical functionality.Use names
to name single unnamed parameters:
Or multiple:
#[salvo(parameter(...))]
Field-level attributes via #[salvo(parameter(...))]
:
style = ...
: Serialization via [ParameterStyle
][style]. Defaults based on parameter_in
.parameter_in = ...
: Parameter location from [parameter::ParameterIn
][in_enum]. Defaults to query
.explode
: Creates separate parameter=value
pairs for each object
/array
item.allow_reserved
: Permits reserved characters :/?#[]@!$&'()*+,;=
in values.example = ...
: Method reference or json!(...)
. Overrides underlying type's example.value_type = ...
: Overrides default OpenAPI field type. Useful for third-party types not implementing ToSchema
or [primitive
types][primitive]. Accepts any JSON-serializable Rust type or custom types like Object
.inline
: Requires field type to implement ToSchema
, inlining its definition.default = ...
: Method reference or json!(...)
.format = ...
: KnownFormat
variant or open string value. Defaults derived from type per OpenAPI spec.write_only
: Restricts field to write operations (POST,PUT,PATCH).read_only
: Restricts field to read operations (GET).nullable
: Indicates if field can be null
(distinct from optionality).required = ...
: Enforces mandatory parameters. See rules.rename = ...
: Alternative to serde
's rename
.multiple_of = ...
: Value must divide evenly by this (>0).maximum = ...
: Inclusive upper bound.minimum = ...
: Inclusive lower bound.exclusive_maximum = ...
: Exclusive upper bound.exclusive_minimum = ...
: Exclusive lower bound.max_length = ...
: Maximum string
length.min_length = ...
: Minimum string
length.pattern = ...
: ECMA-262 regex for valid values.max_items = ...
: Maximum array
items (non-negative).min_items = ...
: Minimum array
items (non-negative).with_schema = ...
: Uses function-generated schema
instead of default. Function must satisfy fn() -> Into<RefOr<Schema>>
.additional_properties = ...
: Defines free-form types for map
s (e.g., HashMap
/BTreeMap
). Formats: additional_properties
or additional_properties = true
.Rules for ToParameters
field attributes mirror those for ToSchema
. See rules.
#[serde(...)]
attributes supportToParameters
derivation supports select [serde attributes][serde attributes], reflected in OpenAPI docs:
rename_all = "..."
(container-level)rename = "..."
(field-level only)default
(container/field-level per [serde][serde attributes])skip_serializing_if = "..."
(field-level only)with = ...
(field-level only)skip_serializing = "..."
(field/variant-level)skip_deserializing = "..."
(field/variant-level)skip = "..."
(field-level only)Other serde
attributes affect serialization but not OpenAPI generation.
Demonstrates #[salvo(parameters(...))]
container attributes with ToParameters
for path parameters, inlining a query field:
Overriding String
with i64
via value_type
:
Overriding String
with Object
(renders as type:object
in OpenAPI):
Override with generics:
Override Vec
with another Vec
:
Override with another ToSchema
:
Validation examples:
Manual schema implementation via schema_with
:
rename_all = ...
: Supports serde
-like syntax for renaming rules. If both #[serde(rename_all = "...")]
and #[salvo(schema(rename_all = "..."))]
exist, #[serde(rename_all = "...")]
takes precedence.
symbol = ...
: String literal defining the struct's OpenAPI schema path (e.g., #[salvo(schema(symbol = "path.to.Pet"))]
).
default
: Uses the struct's Default
impl to populate field defaults.
For typical applications, we define a global error type (AppError), implementing Writer
or Scribe
to send errors as web messages to clients.
For OpenAPI, we additionally implement EndpointOutRegister
to expose error details:
This centralizes all possible application errors. However, individual Handler
s may only return specific subsets. Use status_codes
to filter relevant errors: