rustapi_openapi/lib.rs
1//! OpenAPI documentation for RustAPI
2//!
3//! This crate provides OpenAPI specification generation and Swagger UI serving
4//! for RustAPI applications. It wraps `utoipa` internally while providing a
5//! clean public API.
6//!
7//! # Features
8//!
9//! - **OpenAPI 3.0.3** and **OpenAPI 3.1.0** specification support
10//! - Swagger UI serving at `/docs`
11//! - JSON spec at `/openapi.json`
12//! - Schema derivation via `#[derive(Schema)]`
13//! - **API versioning** with multiple strategies (path, header, query, accept)
14//! - **JSON Schema 2020-12** support for OpenAPI 3.1
15//! - **Webhook definitions** support
16//!
17//! # OpenAPI 3.1 Usage
18//!
19//! ```rust,ignore
20//! use rustapi_openapi::v31::{OpenApi31Spec, Webhook, JsonSchema2020};
21//!
22//! let spec = OpenApi31Spec::new("My API", "1.0.0")
23//! .description("API with OpenAPI 3.1 support")
24//! .webhook("orderPlaced", Webhook::with_summary("Order notification"))
25//! .schema("User", JsonSchema2020::object()
26//! .with_property("id", JsonSchema2020::integer())
27//! .with_property("name", JsonSchema2020::string())
28//! .with_required("id"))
29//! .build();
30//! ```
31//!
32//! # API Versioning Usage
33//!
34//! ```rust,ignore
35//! use rustapi_openapi::versioning::{VersionRouter, ApiVersion, VersionStrategy};
36//!
37//! let router = VersionRouter::new()
38//! .strategy(VersionStrategy::path())
39//! .default_version(ApiVersion::v1())
40//! .version(ApiVersion::v1(), VersionedRouteConfig::version(ApiVersion::v1()))
41//! .version(ApiVersion::v2(), VersionedRouteConfig::version(ApiVersion::v2()));
42//! ```
43//!
44//! # Legacy Usage (OpenAPI 3.0)
45//!
46//! ```rust,ignore
47//! use rustapi_rs::prelude::*;
48//!
49//! #[derive(Serialize, Schema)]
50//! struct User {
51//! id: i64,
52//! name: String,
53//! }
54//!
55//! RustApi::new()
56//! .route("/users", get(list_users))
57//! .docs("/docs")
58//! .run("127.0.0.1:8080")
59//! .await
60//! ```
61
62mod config;
63#[cfg(feature = "redoc")]
64mod redoc;
65mod schemas;
66mod spec;
67#[cfg(feature = "swagger-ui")]
68mod swagger;
69
70// OpenAPI 3.1 support
71pub mod v31;
72
73// API versioning support
74pub mod versioning;
75
76pub use config::OpenApiConfig;
77pub use schemas::{
78 ErrorBodySchema, ErrorSchema, FieldErrorSchema, ValidationErrorBodySchema,
79 ValidationErrorSchema,
80};
81pub use spec::{
82 ApiInfo, MediaType, OpenApiSpec, Operation, OperationModifier, Parameter, PathItem,
83 RequestBody, ResponseModifier, ResponseSpec, SchemaRef,
84};
85
86// Re-export utoipa's ToSchema derive macro as Schema
87pub use utoipa::ToSchema as Schema;
88// Re-export utoipa's IntoParams derive macro
89pub use utoipa::IntoParams;
90
91// Re-export utoipa types for advanced usage
92pub mod utoipa_types {
93 pub use utoipa::{openapi, IntoParams, Modify, OpenApi, ToSchema};
94}
95
96use bytes::Bytes;
97use http::{header, Response, StatusCode};
98use http_body_util::Full;
99
100/// Generate OpenAPI JSON response
101pub fn openapi_json(spec: &OpenApiSpec) -> Response<Full<Bytes>> {
102 match serde_json::to_string_pretty(&spec.to_json()) {
103 Ok(json) => Response::builder()
104 .status(StatusCode::OK)
105 .header(header::CONTENT_TYPE, "application/json")
106 .body(Full::new(Bytes::from(json)))
107 .unwrap(),
108 Err(_) => Response::builder()
109 .status(StatusCode::INTERNAL_SERVER_ERROR)
110 .body(Full::new(Bytes::from("Failed to serialize OpenAPI spec")))
111 .unwrap(),
112 }
113}
114
115/// Generate Swagger UI HTML response
116#[cfg(feature = "swagger-ui")]
117pub fn swagger_ui_html(openapi_url: &str) -> Response<Full<Bytes>> {
118 let html = swagger::generate_swagger_html(openapi_url);
119 Response::builder()
120 .status(StatusCode::OK)
121 .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
122 .body(Full::new(Bytes::from(html)))
123 .unwrap()
124}
125
126/// Generate ReDoc HTML response
127///
128/// ReDoc provides a three-panel API documentation interface.
129///
130/// # Example
131/// ```rust,ignore
132/// use rustapi_openapi::redoc_html;
133/// let response = redoc_html("/openapi.json");
134/// ```
135#[cfg(feature = "redoc")]
136pub fn redoc_html(openapi_url: &str) -> Response<Full<Bytes>> {
137 let html = redoc::generate_redoc_html(openapi_url, None);
138 Response::builder()
139 .status(StatusCode::OK)
140 .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
141 .body(Full::new(Bytes::from(html)))
142 .unwrap()
143}
144
145/// Generate ReDoc HTML response with custom configuration
146#[cfg(feature = "redoc")]
147pub fn redoc_html_with_config(
148 openapi_url: &str,
149 title: Option<&str>,
150 config: &redoc::RedocConfig,
151) -> Response<Full<Bytes>> {
152 let html = redoc::generate_redoc_html_with_config(openapi_url, title, config);
153 Response::builder()
154 .status(StatusCode::OK)
155 .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
156 .body(Full::new(Bytes::from(html)))
157 .unwrap()
158}
159
160#[cfg(feature = "redoc")]
161pub use redoc::{RedocConfig, RedocTheme};
162
163/// Generate OpenAPI 3.1 JSON response
164pub fn openapi_31_json(spec: &v31::OpenApi31Spec) -> Response<Full<Bytes>> {
165 match serde_json::to_string_pretty(&spec) {
166 Ok(json) => Response::builder()
167 .status(StatusCode::OK)
168 .header(header::CONTENT_TYPE, "application/json")
169 .body(Full::new(Bytes::from(json)))
170 .unwrap(),
171 Err(_) => Response::builder()
172 .status(StatusCode::INTERNAL_SERVER_ERROR)
173 .body(Full::new(Bytes::from(
174 "Failed to serialize OpenAPI 3.1 spec",
175 )))
176 .unwrap(),
177 }
178}