NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
crates.io · #3372 most downloaded on crates.io
serde util for salvo.
Last release 3 days ago
04 Oct 2026
Release timing varies
gaps range from 8 days to 2 months
Rarely documented
notes for 13 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
73 releases · first in 2024
fix(examples): align OpenTelemetry examples with 0.33 by @chrislearn in #1710
Full Changelog: v1.0.0...v1.0.1
tls_handshake_timeout.http1 and http2 feature flags when enabling Hyper protocols. HTTP/1-only
builds, including WebSocket support, no longer compile h2 unless another dependency
enables HTTP/2. Default protocol support is unchanged.salvo-otel.One column per month.
fix(tls): pass the rustls CryptoProvider explicitly instead of relying on crate features by @chrislearn in #1699
Full Changelog: v0.96.0...v1.0.0
image/svg+xml and text/xml were
classified as inline by their top-level image/text type, so an uploaded SVG carrying
<script> — or an XML document naming an XSLT stylesheet through <?xml-stylesheet ?> —
executed script in the serving origin when opened. XML-based content types now default to
attachment, matching how application/xml and application/xhtml+xml already behaved.
That covers the +xml suffix, an xml subtype, RFC 7303's xml-dtd and
xml-external-parsed-entity, and the legacy text/xsl that <?xml-stylesheet ?> itself
names. text/html still defaults to inline. Reported by sl91994.NamedFile and StaticEmbed responses now carry X-Content-Type-Options: nosniff.salvo-otel no longer records the full request URI. Metrics dropped url.full entirely,
so query strings no longer become metric dimensions. Tracing replaced it with url.path
plus a url.query whose sig, Signature, AWSAccessKeyId and X-Goog-Signature values
are redacted, as the semantic conventions require.NamedFileBuilder::use_content_type_options and NamedFile::use_content_type_options to
control the X-Content-Type-Options header.StaticDir::disposition_type, StaticDir::use_content_type_options,
StaticFile::disposition_type, StaticFile::attached_name and
StaticFile::use_content_type_options, so applications serving trusted assets can opt back
into inline rendering, which previously had no public API on either handler.NamedFileBuilder::disposition_name, which sets the name Content-Disposition reports
without forcing the disposition to attachment the way attached_name does.CookieParam<T> falls back to JSON when
scalar conversion fails, while #[derive(Extractible)] accepts explicit cookie field sources
such as #[salvo(extract(source(from = "cookie", parse = "json")))].rfc9457 feature.salvo-oapi, including the $self field.salvo-oapi:
summary, parent and kind.name.query (via PathItemType::Query) and additionalOperations.in: querystring (ParameterIn::QueryString) and style: cookie
(ParameterStyle::Cookie).itemSchema, prefixEncoding and itemEncoding.encoding, prefixEncoding and itemEncoding.dataValue and serializedValue.summary, and description is now optional on input.mediaTypes.defaultMapping.nodeType (new XmlNodeType enum).deprecated and oauth2MetadataUrl.deviceAuthorization (new DeviceAuthorization flow).summary.required, deprecated, style,
explode, example, examples, content and extensions, plus Header::with_content.QUERY operations and custom HTTP methods (via
additionalOperations) when the document declares OpenAPI 3.2. Documents that still declare
3.1 skip such routes and log a warning explaining how to opt in.#[endpoint] parameters accept the QueryString location and the Cookie style.Content::from_ref / Content::ref_location, so a content map entry can reference a
reusable Media Type Object without changing those maps to RefOr<Content>.examples/oapi-3-2 demonstrates emitting a 3.2 document with a QUERY route.salvo_core::fs::extension_content_encoding, which reports the content coding a file
extension implies, so a handler choosing a file to serve can tell that it already carries one.salvo_core::conn::rustls::default_crypto_provider, which reports the rustls CryptoProvider
Salvo builds its TLS configurations with. Pass it to other rustls based libraries so the whole
application agrees on one backend.salvo-otel records http.server.active_requests, http.server.request.body.size and
http.server.response.body.size, which its documentation already described. The in-flight
gauge is decremented from a drop guard, so a cancelled request — a client disconnect or a
timeout — does not leave it drifting upwards.Metrics::with_known_methods and Tracing::with_known_methods widen the set of request
methods reported verbatim in http.request.method, for applications serving methods beyond
RFC 9110 and RFC 5789 (WebDAV's PROPFIND, say).salvo-otel gained a matched-path feature, on by default, which supplies http.route.
Through the salvo crate it follows that crate's own matched-path feature.PathItemType::Query, ParameterIn::QueryString, ParameterStyle::Cookie and
Flow::DeviceAuthorization are new variants.SecurityScheme::MutualTls gained a deprecated field.Header::schema is now Option<RefOr<Schema>> so a header can describe its value with
content instead. Header::default() still yields a String schema, so serialized output
is unchanged.OpenApi still defaults to emitting OpenAPI 3.1; 3.2 remains opt-in via
OpenApi::openapi_version.salvo-otel's Metrics middleware now emits the instruments the
OpenTelemetry HTTP semantic conventions define, replacing names it had invented. Dashboards
and alerts built on the old names have to be repointed:
salvo_request_duration_ms became http.server.request.duration, and it measures
seconds rather than milliseconds, with the bucket boundaries the conventions
recommend. A Prometheus exporter renders it as http_server_request_duration_seconds.salvo_request_count and salvo_error_count are gone. The conventions define no such
instruments: the duration histogram already carries the request count
(http_server_request_duration_seconds_count), and failures are selected by filtering on
error.type or http.response.status_code.salvo-otel instrumentation scope, versioned
and carrying the semantic conventions' schema URL, instead of a bare salvo meter.salvo-otel's metric dimensions changed to the ones the conventions list, so
the number of time series is now proportional to the number of routes instead of growing
with traffic:
url.full — one series per distinct URI, query string included — was replaced by
http.route, the matched route template (/users/{id}). A request that matched no route
carries no http.route at all rather than a fallback, and a goal mounted at the router
root is reported as /.exception.message was replaced by error.type, which carries the status code of a
server error. The old attribute was unbounded, and it was appended to the labels of
salvo_request_count and salvo_request_duration_ms but not of salvo_error_count, so
a failed request produced a different label set than a successful one on the same metric._OTHER, so a client
cannot open a time series per made-up method name. See Metrics::with_known_methods.url.scheme and network.protocol.version were added. A request target given in absolute
form lets the client choose the scheme, so a scheme other than http or https is
reported as _OTHER for the same reason an unknown method is; spans still record it.salvo-otel's Tracing middleware follows the same conventions:
{method} {route} — GET /users/{id} — instead of {method} {uri},
which made every distinct URI its own span name.url.full gave way to url.path, a redacted url.query and url.scheme, and
http.route was added.network.protocol.version reports 1.1 and 2 rather than the HTTP/1.1 form
Version's Debug output produces._OTHER, with the value the client sent kept in
http.request.method_original.5xx response now sets error.type and an error span status. A 4xx does not: it is a
valid outcome for a server span.http.response.header.content-length, which was not an attribute the conventions define,
became http.response.body.size, and it is left out for a response whose body the
catcher fills in after middleware returns rather than reported as zero.client.address reports the peer's IP (198.51.100.4) with the port split out into
client.port, instead of salvo's socket://198.51.100.4:54321 display form.telemetry.sdk.name, telemetry.sdk.version and telemetry.sdk.language attributes
were removed from every span. They describe the resource, and the SDK already reports them
there.StaticDir, StaticFile or
NamedFile now sends Content-Disposition: attachment by default, so following a link to
one downloads it instead of rendering it, and <object>/<embed>/<iframe> no longer
display it. Referencing the same file from <img>, CSS url() or <use> is unaffected,
because browsers ignore Content-Disposition on subresource loads. Directories holding only
trusted assets can restore the old behavior with
StaticDir::new(..).disposition_type("inline"). text/html still defaults to inline,
since serving HTML documents is the point of a static file server.query operations. Scalar, RapiDoc and ReDoc display the rest of the
document and silently omit them. Their CDN URLs are deliberately left unpinned, since no
released build of those three renders query yet.CryptoProvider from Rustls crate features". rustls can only pick a backend by itself when
exactly one of its aws-lc-rs and ring features is enabled across the whole dependency
graph, so pulling in any crate that enables the other one made RustlsListener,
QuinnListener, AcmeListener and the proxy's default HyperClient panic. Salvo now selects
the provider from its own features and passes it to rustls explicitly. An application that
installed a process level provider through CryptoProvider::install_default still has that
one used; Salvo itself never installs one, so applications keep full control over the
process level default.StaticDir names a download after the file that was requested rather than the precompressed
sidecar it was served from, so a request for logo.svg answered out of logo.svg.br no
longer offers Content-Disposition: attachment; filename="logo.svg.br".PathItem no longer duplicates operations into extensions when deserialized, which
previously made a parsed path item re-serialize with repeated keys.clientCredentials flow no longer deserializes as Flow::Password.missing field errors..svgz files are now served with Content-Encoding: gzip. Their extension names both a
media type and the coding applied to it, but an extension resolves to a single media type,
so the response described the gzip stream as image/svg+xml and no client could render it.
The gzipped X3D forms .x3dz, .x3dvz and .x3dbz are handled the same way. .gz and
.tgz are unaffected, since there the gzip stream is the representation being served.
StaticDir also stops serving a precompressed sidecar for such a file — a logo.svgz.br
stacks a second coding on the gzip, and only the outer one can be reported.feat(db-postgres-toasty): add toasty example by @feihua in #1684
Full Changelog: v0.95.2...v0.96.0
Add baseline OpenAPI 3.2 support by @chrislearn in #1686
Full Changelog: v0.95.1...v0.95.2
build(deps): update ulid requirement from 2 to 3 by @dependabot [bot] in #1675
Full Changelog: v0.95.0...v0.95.1
Add support for OsString and PathBuf by @eythaann in #1666
StaticDir by @cnlancehu in #1663Full Changelog: v0.94.0...v0.95.0
Several old names remain as deprecated aliases, but new code should use the clearer names:
Salvo 0.94.0 focuses on security hardening, OpenAPI 3.1 correctness, lower runtime overhead in core routing/dispatch paths, and API naming cleanup. This release also raises the Rust MSRV to 1.94.
1.94.Server::max_connections, ConnCtrl, improved graceful/forceful shutdown behavior, and safer default connection fuse protection.jsonSchemaDialect, webhooks, reusable components, PathItem refs, parameter content/examples/allowEmptyValue, and stricter required path parameters.Json<T> now replaces previously buffered body bytes because JSON is a complete document.salvo-tus exposes storage/locking types, adds Tus::storage_root, and adds Tus::absolute_location for safe absolute Location URLs.Update your toolchain to Rust 1.94 or newer.
rustup update stableSeveral old names remain as deprecated aliases, but new code should use the clearer names:
| Old | New |
|---|---|
Depot::inject(value) |
Depot::insert_typed(value) |
Depot::obtain::<T>() |
Depot::get_typed::<T>() |
Depot::obtain_mut::<T>() |
Depot::get_typed_mut::<T>() |
Depot::contains::<T>() |
Depot::contains_typed::<T>() |
Depot::scrape::<T>() |
Depot::remove_typed::<T>() |
Depot::delete(key) |
Depot::remove(key).is_some() |
Response::stuff(status, value) |
Response::render_with_status(status, value) |
Server::stop_forcible() |
Server::stop_forceful() |
ServerHandle::stop_forcible() |
ServerHandle::stop_forceful() |
SchemeFilter::lack(...) |
SchemeFilter::fallback(...) |
HostFilter::lack(...) |
HostFilter::fallback(...) |
PortFilter::lack(...) |
PortFilter::fallback(...) |
StatusError::request_header_fields_toolarge() |
StatusError::request_header_fields_too_large() |
StatusError::unavailable_for_legalreasons() |
StatusError::unavailable_for_legal_reasons() |
AcmeListener::get_directory(...) |
AcmeListener::directory(...) |
Depot now has separate named and type-keyed storage. Use insert/get/remove for string keys and insert_typed/get_typed/remove_typed for type keys. Capacity and inner() refer to named storage.
Response::write_body appends to buffered bodies. Json<T> now replaces existing buffered bytes instead of appending, because concatenated JSON documents are invalid. If you intentionally emit NDJSON or another appendable format, serialize each record yourself and call write_body.
The tus builder API was aligned with Rust API naming guidelines. Update call sites:
| Old | New |
|---|---|
with_store(...) |
store(...) |
with_locker(...) |
locker(...) |
with_upload_id_naming_function(...) |
upload_id_naming_function(...) |
with_generate_url_function(...) |
generate_url_function(...) |
with_on_incoming_request(...) |
on_incoming_request(...) |
with_on_incoming_request_sync(...) |
on_incoming_request_sync(...) |
with_on_upload_create(...) |
on_upload_create(...) |
with_on_upload_finish(...) |
on_upload_finish(...) |
Also prefer MaybeUploadId over the deprecated UploadId alias. TusOptions::extract_file_id_from_request replaces the old file-id helper naming.
For absolute tus Location headers, prefer:
let tus = Tus::new().absolute_location("https://uploads.example.com");Avoid relative_location(false) without a canonical origin on public services, because that derives absolute URLs from request host/forwarded headers.
$schema output was corrected to jsonSchemaDialect for OpenAPI 3.1.Parameter::parameter_in(...) is deprecated; use Parameter::location(...).ToParameters now defaults generated parameters to query location, so required query parameters are represented correctly.required: true.#[derive(Extractible)] now rejects #[serde(flatten)] on fields. Use #[salvo(extract(flatten))] for Salvo request extraction flattening.Access-Control-Allow-Credentials: true with wildcard CORS response headers. Use explicit allowed origins, headers, and methods for credentialed APIs.ForwardedHeaderIssuer is deprecated because it trusts client-controlled forwarded headers unconditionally. Use TrustedProxyIssuer::new([...]) behind known proxies, or RemoteIpIssuer for direct client IPs.Proxy now uses the standards-compliant host header getter by default, including non-default upstream ports. If you need the old bare-host behavior, configure default_host_header_getter.kty: "oct" / HS*) are rejected by default. Opt in with allow_symmetric_jwks(true) only for issuers that intentionally publish trusted symmetric keys.SecureCookiePolicy is available for CSRF, flash, and session cookies. If TLS terminates before Salvo and the request scheme appears as HTTP, force secure cookies with the relevant secure_cookie(true) or policy API.Server::max_connections to bound concurrent accepted connections.ConnCtrl to the prelude and public exports.$ref support.content, examples, and allowEmptyValue.Extractible bounds, number equality, macro diagnostics, and several user-triggerable macro panics.Set-Cookie parsing/round-tripping and tower-compatible response conversion.RequestId, caching headers, basic auth multi-header fallthrough, and logging path/address behavior.Accept parsing, text_nonce, and write_body.Thanks to everyone who contributed to this release:
FlowCtrl performance (#1608), auto_alt_svc_header for quinn::Builder (#1585), code quality cleanups (#1544, #1524), salvo-cors refactor (#1525), and docs.rs build fixes (#1523)Full Changelog: v0.93.0...v0.94.0
Add strict proxy path normalization by @chrislearn in #1434
Full Changelog: v0.92.2...v0.93.0
Implement timeout management with Mutex and CancellationToken for FlexFusewire by @chrislearn in #1426
Full Changelog: v0.92.1...v0.92.2
Add path filter handling for invalid patterns and try_with_path method by @chrislearn in #1422
Full Changelog: v0.92.0...v0.92.1
feat(schema): implement ToSchema and ComposeSchema for serde_json::RawValue by @chrislearn in #1396
Full Changelog: v0.91.1...v0.92.0
Improve file handling and metadata retrieval in NamedFileBuilder by @chrislearn in #1378
Full Changelog: v0.90.1...v0.91.1
Nothing published for this version
Replace salvo-acme with certon for enhanced ACME support by @chrislearn in #1339
Full Changelog: v0.89.3...v0.90.1
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →