1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
8
9mod batch_routes;
10mod demand_routes;
11mod portfolio_routes;
12mod product_routes;
13
14use aide::{
15 axum::{ApiRouter, routing::get},
16 openapi::OpenApi,
17};
18use axum::{Extension, Json};
19use fts_core::ports::{Application, Repository, Solver};
20use headers::{Authorization, authorization::Bearer};
21use schemars::JsonSchema;
22use serde::{Serialize, de::DeserializeOwned};
23use std::{fmt::Display, sync::Arc};
24
25mod openapi;
26use openapi::{api_docs, docs_routes};
27
28pub mod config;
29use config::AxumConfig;
30
31#[derive(Serialize, JsonSchema)]
33#[schemars(inline)]
34struct HealthResponse {
35 status: String,
36}
37
38async fn health_check() -> Json<HealthResponse> {
40 Json(HealthResponse {
41 status: "ok".to_string(),
42 })
43}
44
45pub fn schema<T: ApiApplication>() -> OpenApi {
47 let mut api = OpenApi::default();
48 let _ = ApiRouter::new()
49 .api_route("/health", get(health_check))
50 .nest("/product", product_routes::router::<T>())
51 .nest("/demand", demand_routes::router::<T>())
52 .nest("/portfolio", portfolio_routes::router::<T>())
53 .nest("/batch", batch_routes::router::<T>())
54 .nest_api_service("/docs", docs_routes())
55 .finish_api_with(&mut api, api_docs);
56 api
57}
58
59pub fn router<T: ApiApplication>(state: T, config: AxumConfig) -> axum::Router {
61 let mut api = OpenApi::default();
62 ApiRouter::new()
63 .api_route("/health", get(health_check))
64 .nest("/product", product_routes::router())
65 .nest("/demand", demand_routes::router())
66 .nest("/portfolio", portfolio_routes::router())
67 .nest("/batch", batch_routes::router())
68 .nest_api_service("/docs", docs_routes())
69 .finish_api_with(&mut api, api_docs)
70 .layer(Extension(Arc::new(api))) .layer(Extension(Arc::new(config)))
72 .with_state(state)
73}
74
75pub async fn start_server<T: ApiApplication>(
77 config: AxumConfig,
78 app: T,
79) -> Result<(), std::io::Error> {
80 let listener = tokio::net::TcpListener::bind(config.bind_address)
81 .await
82 .expect("Unable to bind to address");
83
84 tracing::info!(
85 "Listening for requests on {}",
86 listener.local_addr().unwrap()
87 );
88
89 let service = router(app, config);
91 axum::serve(listener, service).await
92}
93
94pub trait ApiApplication:
99 Clone
100 + Send
101 + Sync
102 + 'static
103 + Application<
104 Context = Authorization<Bearer>,
105 DemandData: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
106 PortfolioData: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
107 ProductData: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
108 Repository: Clone
109 + Send
110 + Sync
111 + 'static
112 + Repository<
113 DateTime: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
114 BidderId: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
115 DemandId: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
116 PortfolioId: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
117 ProductId: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
118 >,
119 Solver: Solver<
120 <Self::Repository as Repository>::DemandId,
121 <Self::Repository as Repository>::PortfolioId,
122 <Self::Repository as Repository>::ProductId,
123 PortfolioOutcome: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
124 ProductOutcome: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
125 >,
126 >
127{
128}
129
130impl<T: Clone + Send + Sync + 'static> ApiApplication for T where
132 T: Application<
133 Context = Authorization<Bearer>,
134 DemandData: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
135 PortfolioData: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
136 ProductData: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
137 Repository: Clone
138 + Send
139 + Sync
140 + 'static
141 + Repository<
142 DateTime: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
143 BidderId: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
144 DemandId: Clone + Display + Serialize + DeserializeOwned + JsonSchema + Send + Sync,
145 PortfolioId: Clone
146 + Display
147 + Serialize
148 + DeserializeOwned
149 + JsonSchema
150 + Send
151 + Sync,
152 ProductId: Clone
153 + Display
154 + Serialize
155 + DeserializeOwned
156 + JsonSchema
157 + Send
158 + Sync,
159 >,
160 Solver: Solver<
161 <T::Repository as Repository>::DemandId,
162 <T::Repository as Repository>::PortfolioId,
163 <T::Repository as Repository>::ProductId,
164 PortfolioOutcome: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
165 ProductOutcome: Send + Sync + Serialize + DeserializeOwned + JsonSchema + 'static,
166 >,
167 >
168{
169}