11//! RustApi application builder
22
33use crate :: error:: Result ;
4- use crate :: router:: { MethodRouter , Router } ;
4+ use crate :: router:: { get , MethodRouter , Router } ;
55use crate :: server:: Server ;
6+ use crate :: response:: { Html , Response } ;
7+ use crate :: extract:: Json ;
8+ use rustapi_openapi:: { OpenApiDoc , swagger_ui_html} ;
9+ use std:: sync:: Arc ;
610use tracing_subscriber:: { layer:: SubscriberExt , util:: SubscriberInitExt , EnvFilter } ;
711
812/// Main application builder for RustAPI
@@ -24,6 +28,8 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte
2428/// ```
2529pub struct RustApi {
2630 router : Router ,
31+ openapi : Option < OpenApiDoc > ,
32+ docs_path : Option < String > ,
2733}
2834
2935impl RustApi {
@@ -39,6 +45,8 @@ impl RustApi {
3945
4046 Self {
4147 router : Router :: new ( ) ,
48+ openapi : None ,
49+ docs_path : None ,
4250 }
4351 }
4452
@@ -100,16 +108,40 @@ impl RustApi {
100108 self
101109 }
102110
111+ /// Configure OpenAPI documentation
112+ ///
113+ /// # Example
114+ ///
115+ /// ```rust,ignore
116+ /// use rustapi_openapi::OpenApiDoc;
117+ ///
118+ /// RustApi::new()
119+ /// .openapi(OpenApiDoc::new("My API", "1.0.0")
120+ /// .description("A sample API")
121+ /// .server("http://localhost:8080"))
122+ /// ```
123+ pub fn openapi ( mut self , doc : OpenApiDoc ) -> Self {
124+ self . openapi = Some ( doc) ;
125+ self
126+ }
127+
103128 /// Enable Swagger UI at the specified path
104129 ///
130+ /// Also enables `/openapi.json` endpoint automatically.
131+ ///
105132 /// # Example
106133 ///
107134 /// ```rust,ignore
108135 /// RustApi::new()
109- /// .docs("/docs") // Swagger UI at /docs
136+ /// .openapi(OpenApiDoc::new("My API", "1.0.0"))
137+ /// .docs("/docs") // Swagger UI at /docs, spec at /openapi.json
110138 /// ```
111- pub fn docs ( self , _path : & str ) -> Self {
112- // TODO: Implement OpenAPI + Swagger UI
139+ pub fn docs ( mut self , path : & str ) -> Self {
140+ self . docs_path = Some ( path. to_string ( ) ) ;
141+ // Create default OpenAPI doc if not set
142+ if self . openapi . is_none ( ) {
143+ self . openapi = Some ( OpenApiDoc :: new ( "RustAPI" , "1.0.0" ) ) ;
144+ }
113145 self
114146 }
115147
@@ -123,7 +155,42 @@ impl RustApi {
123155 /// .run("127.0.0.1:8080")
124156 /// .await
125157 /// ```
126- pub async fn run ( self , addr : & str ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
158+ pub async fn run ( mut self , addr : & str ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
159+ // Add OpenAPI endpoints if configured
160+ if let Some ( doc) = self . openapi . take ( ) {
161+ let doc = Arc :: new ( doc) ;
162+ let docs_path = self . docs_path . take ( ) ;
163+
164+ // Add /openapi.json endpoint
165+ let doc_for_json = doc. clone ( ) ;
166+ self . router = self . router . route (
167+ "/openapi.json" ,
168+ get ( move || {
169+ let doc = doc_for_json. clone ( ) ;
170+ async move {
171+ OpenApiJsonResponse ( doc. to_json ( ) )
172+ }
173+ } ) ,
174+ ) ;
175+
176+ // Add Swagger UI endpoint if docs path is set
177+ if let Some ( path) = docs_path {
178+ let title = doc. title ( ) . to_string ( ) ;
179+ self . router = self . router . route (
180+ & path,
181+ get ( move || {
182+ let title = title. clone ( ) ;
183+ async move {
184+ Html ( swagger_ui_html ( "/openapi.json" , & title) )
185+ }
186+ } ) ,
187+ ) ;
188+ tracing:: info!( "📚 Swagger UI available at http://{}{}" , addr, path) ;
189+ }
190+
191+ tracing:: info!( "📄 OpenAPI spec at http://{}/openapi.json" , addr) ;
192+ }
193+
127194 let server = Server :: new ( self . router ) ;
128195 server. run ( addr) . await
129196 }
@@ -139,3 +206,20 @@ impl Default for RustApi {
139206 Self :: new ( )
140207 }
141208}
209+
210+ /// Response type for OpenAPI JSON
211+ struct OpenApiJsonResponse ( String ) ;
212+
213+ impl crate :: response:: IntoResponse for OpenApiJsonResponse {
214+ fn into_response ( self ) -> Response {
215+ use bytes:: Bytes ;
216+ use http:: { header, StatusCode } ;
217+ use http_body_util:: Full ;
218+
219+ http:: Response :: builder ( )
220+ . status ( StatusCode :: OK )
221+ . header ( header:: CONTENT_TYPE , "application/json" )
222+ . body ( Full :: new ( Bytes :: from ( self . 0 ) ) )
223+ . unwrap ( )
224+ }
225+ }
0 commit comments