Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
stano-launcher
Application bootstrap and server runner: wires an Axum router, applies a standard middleware stack, handles graceful shutdown, and listens for HTTP traffic.
Install
[]
= { = "../stano-launcher" }
= { = "../stano-di" }
= { = "../stano-axum" }
= { = "../stano-security" }
= { = "1", = ["macros", "rt-multi-thread"] }
= "0.8"
= "5"
= "0.2"
API
Configuration
-
BootstrapConfig— server startup settings.port: u16— TCP port to listen on.jwt_config: JwtConfig— JWT private/public keys and optional expiration duration.cors_origins: Vec<String>— allowed CORS origins, exact match (empty +cors_origin_suffixesempty = permissive). Used whenis_devis false, or whenis_devis true butcors_dev_originsis empty.cors_origin_suffixes: Vec<String>— allowed CORS origin suffixes, e.g..example.commatches any subdomain (empty +cors_originsempty = permissive). Same applicability ascors_origins.cors_dev_origins: Vec<String>— exact origins allowed whenis_devis true (e.g.http://localhost:5173). Takes priority overcors_origins/cors_origin_suffixeswhile non-empty andis_devis true.is_dev: bool— when true, CORS usescors_dev_originsinstead ofcors_origins/cors_origin_suffixes(falling back to the latter ifcors_dev_originsis empty). Compute withis_dev_environment, or set explicitly.observability: ObservabilityConfig— OTLP tracing/metrics/log export settings (see below).run()initializes observability from this before doing anything else.enable_swagger: bool— when true, mounts Swagger UI at/swaggerand the generated OpenAPI document at/api-docs/openapi.json. Typically wired tois_dev_environment()so it's off in production.
-
parse_csv_env(environment: &dyn Environment, key: &str) -> Vec<String>— helper to populatecors_origins/cors_origin_suffixes/cors_dev_origins(or any other list-valued config) from a comma-separated environment variable. Trims whitespace and drops empty entries; returns an empty vec if the var is unset. -
is_dev_environment(environment: &dyn Environment) -> bool— true for debug builds, or whenRUST_ENV=development(case-insensitive). Note debug builds (includingcargo test) are always considered dev mode.
Observability
-
ObservabilityConfig— OTLP tracing/metrics/log export settings.enabled: bool— master switch. Whenfalse(the default), only a localfmt+EnvFilterconsole subscriber is installed (JSON-formatted) and no OTLP export happens — safe for local dev without a collector.otlp_endpoint: String— collector endpoint, e.g.http://localhost:4317(grpc) orhttp://localhost:4318(http/protobuf).protocol: OtlpProtocol—GrpcorHttpProtobuf, runtime-selectable (both exporter transports are compiled in).service_name: String/service_version: String— OTel resource attributes.resource_attributes: Vec<(String, String)>— additional OTel resource attributes, e.g.("deployment.environment", "prod").trace_sample_ratio: f64— trace sampling ratio in0.0..=1.0.log_filter: String—tracing_subscriber::EnvFilterdirective string, e.g."info,my_app=debug".metrics_enabled: bool— independently enables OTLP metrics export (push, requiresenabled: trueand a live collector) and the HTTP server metrics middleware (request count/duration, active requests).prometheus_enabled: bool— independently exposes a local Prometheus scrape endpoint atGET /metrics(text-exposition format), serving the same metrics recorded via the global OTel meter, including HTTP server metrics when the metrics middleware is mounted. Unlikemetrics_enabled(an OTLP push exporter to a collector), this is a pull exporter with no collector dependency, so it works even whenenabledisfalse. Also implicitly mounts the HTTP server metrics middleware (same asmetrics_enabled) so there's something to scrape.http_logging_enabled: bool— independently enablesstano_axum::http_request_logging_middleware, which logs every HTTP request (method, URI, status, latency, and the current span's OTel trace_id).
-
observability_config_from_env(environment: &dyn Environment) -> ObservabilityConfig— reads standard OTel env vars (OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_EXPORTER_OTLP_PROTOCOL,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION,OTEL_TRACES_SAMPLER_ARG,RUST_LOG) plusSTANO_OTEL_ENABLED/STANO_OTEL_METRICS_ENABLED/STANO_PROMETHEUS_ENABLED/STANO_HTTP_LOGGING_ENABLED(all defaultfalse). -
OtelGuard— returned internally byinit_observabilityand held byrun()for the request lifetime; flushed afteraxum::serve(...)resolves so in-flight spans/metrics are exported before shutdown. Also exposesprometheus_registry(), present whenprometheus_enabledis true, whichrun()uses to mount/metrics.
run() calls init_observability(&config.observability) as the first thing it does, so all subsequent tracing::*! calls (including from stano-di, stano-axum, and your own app code) are captured. No call-site changes are needed anywhere — this composes a tracing_subscriber::Registry that the plain tracing facade already flows through.
Route Auto-Registration
-
#[get(...)]/#[post(...)]/#[put(...)]/#[delete(...)]/#[patch(...)]— per-HTTP-method attribute macros (fromstano-route-macros, re-exported here) that replace#[utoipa::path(...)]on a handler and auto-register it, so it's picked up byrun()without a manual.routes(routes!(handler))call.- Infer
operation_id(from the function name),request_body(from a singleAppJson<T>parameter), the200entry ofresponses(...)(from anAppJson<T>/Result<AppJson<T>, E>return type), andparams(...)(fromAppPath<T>/AppQuery<T>parameters); forward everything else (path,tag/tags,security, extraresponses(...)entries) verbatim into a generated#[utoipa::path(...)]attribute. Write#[get(...)](etc.) instead of#[utoipa::path(...)], not in addition to it. - Internally, each annotated handler submits a factory into a global
inventorycollection at compile time (the same patternstano-di's#[service]uses for DI registration);collect_routes()(see below) folds all of them into oneOpenApiRouterat startup. - These macros don't apply any auth/authz middleware themselves — auth enforcement is applied once, globally, via
run()'sauthorizationparameter (see Authorization below), not per-route.security(...)inside a macro's attributes is documentation only (feeds the generated OpenAPI doc) and has no runtime effect — keep it in sync with yourAuthorizationBuilderrules by hand.
- Infer
-
stano_launcher::routes::collect_routes() -> OpenApiRouter<Arc<ApplicationContext>>— builds the router from every#[get]/#[post]/etc.-annotated handler in the binary. Called automatically byrun()— you normally don't need to call it directly.
Authorization
authorization: Option<stano_axum::security::AuthorizationLayer>(run()'s 4th parameter) — a declarative, Spring-Security-style rule table built viastano_axum::security::AuthorizationBuilder, applied globally as the outermost-but-one layer (just inside CORS). PassNoneto skip authorization enforcement entirely (every route open). Seestano-axum's docs forAuthorizationBuilder's API (.request_matcher(...),.permit_all()/.authenticated()/.has_role(...),.with_claims_validator(...),.cookie_name(...),.any_request()...build(jwt_config)).post_authorization: Option<stano_axum::security::PostAuthorizationHook>(run()'s 5th parameter) — an optional middleware hook applied immediately afterauthorizationin request-flow order (between authorization and the router, closer to handlers). Exists so a consumer can bridge whateverAuthorizationLayerinserted into request extensions (astano_security::SecurityContext<E>) into an app-local mechanism (e.g. atokio::task_local!your service layer already reads from), withoutstano-launcher/stano-axumneeding to know that mechanism exists. Build one viaPostAuthorizationHook::from_fn(your_middleware_fn), whereyour_middleware_fnmatchesaxum::middleware::from_fn's simplest shape:async fn(Request, Next) -> impl IntoResponse. PassNoneto skip it — has no effect ifauthorizationis alsoNone.
Server Startup
run(ctx: Arc<ApplicationContext>, extra_routes: OpenApiRouter<Arc<ApplicationContext>>, config: BootstrapConfig, authorization: Option<AuthorizationLayer>, post_authorization: Option<PostAuthorizationHook>) -> Result<(), anyhow::Error>— start the server.- Merges
collect_routes()(every#[get]/#[post]/etc.-annotated handler) withextra_routes(anything you built by hand — passOpenApiRouter::new()if there's nothing extra). - Applies a fixed middleware stack (see below), including
authorization/post_authorizationif provided. - Binds a
TcpListeneron0.0.0.0:{port}. - Logs "Listening on port {port}".
- Runs until Ctrl+C (or SIGTERM on Unix) is received, then performs graceful shutdown.
- Merges
Middleware Stack
Applied in this request-processing order (outermost → innermost, closest to handlers):
- CORS — allow/disallow origins based on config. Deliberately outermost so it can answer preflight
OPTIONSrequests directly, without them ever reachingauthorization— a rule table scoped to specific methods (GET/POST/etc, not OPTIONS) would otherwise reject preflight requests to protected routes with 401. - Security headers — injects
x-content-type-options: nosniff,x-frame-options: DENY,strict-transport-security: max-age=31536000; includeSubDomains. - Authorization (when
authorizationisSome) — the declarativeAuthorizationLayerrule table (see Authorization above). - Post-authorization hook (when
post_authorizationisSome) — runs immediately after authorization, closer to handlers (see Authorization above). - Request body limit — 10 MB max request body.
- Propagate request ID — propagates
x-request-idupstream. - Set request ID — injects a unique
x-request-idif not present. - Compression — gzip/brotli/deflate (auto-negotiated).
- Catch panic — panics in handlers become 500 responses.
- Error logging — logs
ApiErrorwith request context (seestano_axum::error_logging_middleware). 10a. HTTP request logging (whenobservability.http_logging_enabledis true) — logs every request with method, URI, status, latency, and trace_id (seestano_axum::http_request_logging_middleware). Sits just inside the Tracing layer so it runs within the same span and can read a valid OTel trace_id. - Tracing — structured request/response logging via
tracing, exported via OTLP whenobservability.enabledis true (see Observability above). - Timeout — 300-second per-request timeout.
Additionally, when observability.metrics_enabled or observability.prometheus_enabled is true, an HTTP metrics middleware is applied via route_layer (so it only wraps matched routes, giving it access to MatchedPath for the http.route attribute) — this records http.server.request.duration and http.server.active_requests via the global OTel meter.
When enable_swagger is true, the Swagger UI router (/swagger, /api-docs/openapi.json) is merged in alongside the auto-registered and extra_routes routers before the middleware stack is applied, so it's subject to the same CORS/timeout/compression/security-header handling as the rest of the API.
When observability.prometheus_enabled is true, a GET /metrics route is merged in the same way, before .with_state(...) — so it's also subject to the same CORS/timeout/compression/security-header/authorization handling as the rest of the API.
Usage Example
use ;
use ;
use ;
use JwtConfig;
use Arc;
use OpenApiRouter;
async
// Your handlers — #[get(...)]/#[post(...)]/etc. replace #[utoipa::path(...)], inferring
// operation_id/request_body/the 200 response/params(...) from the handler's signature.
// `security(...)` documents which routes need a bearer token — it has no runtime effect,
// so keep it in sync with the `AuthorizationBuilder` rules above by hand.
async
async
Notes
- Auth is global, not per-route —
#[get]/#[post]/etc. don't apply any middleware themselves; there's noauth = <guard_fn>argument, and none is planned. Enforcement is entirelyrun()'sauthorization/post_authorizationparameters (see Authorization above) — a single declarative rule table covering every route, macro-registered orextra_routes. - Route merging — every
#[get]/#[post]/etc.-annotated handler andextra_routesare merged into a single router, so define paths carefully to avoid collisions. - Graceful shutdown — the server responds to Ctrl+C on all platforms and SIGTERM on Unix-like systems. Connections are drained gracefully.
- CORS configuration — pass
cors_origins,cors_origin_suffixes, andcors_dev_originsall empty for permissive CORS (allow any origin); otherwise list exact origins and/or origin suffixes. Populate any of these from an env var withparse_csv_env, or set them directly from your app config. Setis_dev(viais_dev_environmentor explicitly) to switch tocors_dev_originsin development. - Observability is automatic, not opt-in per call —
run()always callsinit_observability; setobservability.enabled = false(the default viaobservability_config_from_env) to get a JSON-formatted console subscriber and skip OTLP export entirely, e.g. for local dev without a collector. Console output is always JSON, whether or not OTLP export is enabled. If your app already installs its owntracing_subscriber, don't callrun()withenabled: trueat the same time — only one global subscriber can be installed per process. - Swagger UI is auto-discovered, not hand-maintained — because
#[get]/#[post]/etc. generate a#[utoipa::path]attribute internally, the OpenAPI document served at/api-docs/openapi.jsonis generated from the same code that defines the routes. There is no separate spec file to keep in sync by hand. - No feature flags — all APIs available.
See also: stano-di, stano-axum, stano-security.