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 ;
17use utoipa:: OpenApi ;
28
39pub ( crate ) const HEALTH_TAG : & str = "Health API" ;
@@ -17,3 +23,95 @@ pub(crate) const AUTHZEN_TAG: &str = "AuthZen API";
1723 )
1824) ]
1925pub ( 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