Skip to content

Commit c727cd8

Browse files
authored
Refactor OpenAPI routes and add new handlers for documentation endpoints (#282)
* Refactor OpenAPI routes and add new handlers for documentation endpoints - Updated the main application to integrate the OpenAPI router. - Added handlers for OpenAPI JSON, ReDoc, and Scalar documentation endpoints in `openapi.rs`. - Improved error handling for HTTP responses in the new handlers. - Cleaned up code formatting in `main.rs` for better readability. * fix compiler warnings
1 parent 734a530 commit c727cd8

File tree

2 files changed

+100
-5
lines changed

2 files changed

+100
-5
lines changed

pdp-server/src/main.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use log::{error, info};
1515
use std::net::SocketAddr;
1616
use utoipa::OpenApi;
1717
use utoipa_axum::router::OpenApiRouter;
18-
use utoipa_scalar::{Scalar, Servable};
1918

2019
#[tokio::main]
2120
async fn main() {
@@ -39,7 +38,6 @@ async fn main() {
3938
std::process::exit(1);
4039
}
4140
};
42-
4341
// Initialize application state
4442
let state: AppState = AppState::with_existing_cache(&config, cache)
4543
.await
@@ -78,14 +76,13 @@ async fn main() {
7876
/// Create a new application instance with a given state
7977
pub async fn create_app(state: AppState) -> Router {
8078
// Create OpenAPI documentation
81-
let (openapi_router, api_doc) =
82-
OpenApiRouter::with_openapi(openapi::ApiDoc::openapi()).split_for_parts();
79+
let openapi_router = OpenApiRouter::with_openapi(openapi::ApiDoc::openapi());
8380

8481
// Create base router with routes
8582
Router::new()
8683
.merge(api::router(&state))
8784
.merge(openapi_router)
88-
.merge(Scalar::with_url("/scalar", api_doc.clone()))
85+
.merge(crate::openapi::router())
8986
.with_state(state)
9087
}
9188

pdp-server/src/openapi.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
use crate::state::AppState;
2+
use axum::body::Body;
3+
use axum::http::StatusCode;
4+
use axum::response::{IntoResponse, Response};
5+
use axum::{extract::State, routing::get, Router};
6+
use http::HeaderValue;
17
use utoipa::OpenApi;
28

39
pub(crate) const HEALTH_TAG: &str = "Health API";
@@ -17,3 +23,95 @@ pub(crate) const AUTHZEN_TAG: &str = "AuthZen API";
1723
)
1824
)]
1925
pub(crate) struct ApiDoc;
26+
27+
/// Handler for the OpenAPI JSON specification endpoint
28+
async fn openapi_json_handler(State(state): State<AppState>) -> impl IntoResponse {
29+
let openapi_url = state.config.horizon.get_url("/openapi.json");
30+
31+
match state.horizon_client.get(&openapi_url).send().await {
32+
Ok(response) => match response.bytes().await {
33+
Ok(bytes) => match serde_json::from_slice::<serde_json::Value>(&bytes) {
34+
Ok(json) => axum::Json(json).into_response(),
35+
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Invalid JSON").into_response(),
36+
},
37+
Err(_) => {
38+
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read response").into_response()
39+
}
40+
},
41+
Err(_) => (
42+
StatusCode::SERVICE_UNAVAILABLE,
43+
"Horizon service unavailable",
44+
)
45+
.into_response(),
46+
}
47+
}
48+
49+
/// Handler for the ReDoc documentation endpoint
50+
async fn redoc_handler(State(state): State<AppState>) -> impl IntoResponse {
51+
let redoc_url = state.config.horizon.get_url("/redoc");
52+
53+
match state.horizon_client.get(&redoc_url).send().await {
54+
Ok(response) => {
55+
// Extract content-type header before consuming the response
56+
let content_type = response
57+
.headers()
58+
.get("content-type")
59+
.unwrap_or(&HeaderValue::from_static("text/html"))
60+
.clone();
61+
62+
match response.text().await {
63+
Ok(text) => Response::builder()
64+
.header("content-type", content_type)
65+
.body(Body::from(text))
66+
.unwrap(),
67+
Err(_) => {
68+
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read response").into_response()
69+
}
70+
}
71+
}
72+
Err(_) => (
73+
StatusCode::SERVICE_UNAVAILABLE,
74+
"Horizon service unavailable",
75+
)
76+
.into_response(),
77+
}
78+
}
79+
80+
/// Handler for the Scalar documentation endpoint
81+
async fn scalar_handler(State(state): State<AppState>) -> impl IntoResponse {
82+
let scalar_url = state.config.horizon.get_url("/scalar");
83+
84+
match state.horizon_client.get(&scalar_url).send().await {
85+
Ok(response) => {
86+
// Extract content-type header before consuming the response
87+
let content_type = response
88+
.headers()
89+
.get("content-type")
90+
.unwrap_or(&HeaderValue::from_static("text/html"))
91+
.clone();
92+
93+
match response.text().await {
94+
Ok(res_text) => Response::builder()
95+
.header("content-type", content_type)
96+
.body(Body::from(res_text))
97+
.unwrap(),
98+
Err(_) => {
99+
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read response").into_response()
100+
}
101+
}
102+
}
103+
Err(_) => (
104+
StatusCode::SERVICE_UNAVAILABLE,
105+
"Horizon service unavailable",
106+
)
107+
.into_response(),
108+
}
109+
}
110+
111+
/// Creates a router for OpenAPI documentation routes
112+
pub(crate) fn router() -> Router<AppState> {
113+
Router::new()
114+
.route("/openapi.json", get(openapi_json_handler))
115+
.route("/redoc", get(redoc_handler))
116+
.route("/scalar", get(scalar_handler))
117+
}

0 commit comments

Comments
 (0)