1use nidus_http::router::RouteMetadata;
2use nidus_http::{StatusCode, error::RoutePathError};
3use serde_json::{Value, json};
4use utoipa::ToSchema;
5
6use crate::path::{openapi_path, openapi_path_parameters, operation_id};
7
8#[derive(Clone, Debug)]
10pub struct OpenApiRoute {
11 method: String,
12 path: String,
13 path_parameters: Vec<String>,
14 summary: Option<String>,
15 tags: Vec<String>,
16 response_status: StatusCode,
17 request_schema: Option<String>,
18 response_schema: Option<String>,
19 guards: Vec<String>,
20 pipes: Vec<String>,
21 validates: bool,
22}
23
24impl OpenApiRoute {
25 pub fn get(path: impl Into<String>) -> Self {
27 Self::try_get(path).unwrap_or_else(|error| panic!("{error}"))
28 }
29
30 pub fn try_get(path: impl Into<String>) -> Result<Self, RoutePathError> {
32 Self::try_new("get", path)
33 }
34
35 pub fn post(path: impl Into<String>) -> Self {
37 Self::try_post(path).unwrap_or_else(|error| panic!("{error}"))
38 }
39
40 pub fn try_post(path: impl Into<String>) -> Result<Self, RoutePathError> {
42 Self::try_new("post", path)
43 }
44
45 pub fn put(path: impl Into<String>) -> Self {
47 Self::try_put(path).unwrap_or_else(|error| panic!("{error}"))
48 }
49
50 pub fn try_put(path: impl Into<String>) -> Result<Self, RoutePathError> {
52 Self::try_new("put", path)
53 }
54
55 pub fn patch(path: impl Into<String>) -> Self {
57 Self::try_patch(path).unwrap_or_else(|error| panic!("{error}"))
58 }
59
60 pub fn try_patch(path: impl Into<String>) -> Result<Self, RoutePathError> {
62 Self::try_new("patch", path)
63 }
64
65 pub fn delete(path: impl Into<String>) -> Self {
67 Self::try_delete(path).unwrap_or_else(|error| panic!("{error}"))
68 }
69
70 pub fn try_delete(path: impl Into<String>) -> Result<Self, RoutePathError> {
72 Self::try_new("delete", path)
73 }
74
75 pub fn summary(mut self, summary: impl Into<String>) -> Self {
77 self.summary = Some(summary.into());
78 self
79 }
80
81 pub fn tag(mut self, tag: impl Into<String>) -> Self {
83 self.tags.push(tag.into());
84 self
85 }
86
87 pub fn response_status(mut self, status: StatusCode) -> Self {
89 self.response_status = status;
90 self
91 }
92
93 pub fn request_schema<T>(self) -> Self
95 where
96 T: ToSchema,
97 {
98 self.request_schema_ref(T::name())
99 }
100
101 pub fn response_schema<T>(self) -> Self
103 where
104 T: ToSchema,
105 {
106 self.response_schema_ref(T::name())
107 }
108
109 pub(crate) fn method(&self) -> &str {
110 &self.method
111 }
112
113 pub(crate) fn path(&self) -> &str {
114 &self.path
115 }
116
117 pub(crate) fn try_from_route_metadata(
118 metadata: &RouteMetadata,
119 ) -> Result<Self, RoutePathError> {
120 Self::try_from_route_metadata_at_path(metadata, metadata.path())
121 }
122
123 pub(crate) fn try_from_route_metadata_at_path(
124 metadata: &RouteMetadata,
125 path: impl AsRef<str>,
126 ) -> Result<Self, RoutePathError> {
127 let path = openapi_path(path.as_ref())?;
128 let path_parameters = openapi_path_parameters(&path);
129 let mut route = Self::new(
130 metadata.method().to_ascii_lowercase(),
131 path,
132 path_parameters,
133 );
134 if let Some(summary) = metadata.summary() {
135 route = route.summary(summary);
136 }
137 for tag in metadata.tags() {
138 route = route.tag(*tag);
139 }
140 if let Some(status) = metadata.response_status() {
141 route = route.response_status(status);
142 }
143 if let Some(schema) = metadata.request_schema() {
144 route = route.request_schema_ref(schema);
145 }
146 if let Some(schema) = metadata.response_schema() {
147 route = route.response_schema_ref(schema);
148 }
149 route.guards = metadata
150 .guards()
151 .iter()
152 .map(|guard| (*guard).to_owned())
153 .collect();
154 route.pipes = metadata
155 .pipes()
156 .iter()
157 .map(|pipe| (*pipe).to_owned())
158 .collect();
159 route.validates = metadata.validates();
160 Ok(route)
161 }
162
163 pub(crate) fn to_json_value(&self) -> Value {
164 let mut success_response = json!({
165 "description": "Success"
166 });
167 if let Some(schema) = &self.response_schema {
168 success_response["content"] = json!({
169 "application/json": {
170 "schema": {
171 "$ref": format!("#/components/schemas/{schema}")
172 }
173 }
174 });
175 }
176
177 let mut responses = serde_json::Map::new();
178 responses.insert(self.response_status.as_u16().to_string(), success_response);
179
180 if !self.guards.is_empty() {
183 responses.insert("401".to_owned(), json!({ "description": "Unauthorized" }));
184 responses.insert("403".to_owned(), json!({ "description": "Forbidden" }));
185 }
186 if self.validates {
187 responses.insert(
188 "422".to_owned(),
189 json!({ "description": "Validation failed" }),
190 );
191 }
192
193 let mut operation = json!({
194 "operationId": operation_id(&self.method, &self.path),
195 "responses": responses
196 });
197
198 if let Some(summary) = &self.summary {
199 operation["summary"] = json!(summary);
200 }
201 if !self.tags.is_empty() {
202 operation["tags"] = json!(self.tags);
203 }
204 if let Some(schema) = &self.request_schema {
205 operation["requestBody"] = json!({
206 "required": true,
207 "content": {
208 "application/json": {
209 "schema": {
210 "$ref": format!("#/components/schemas/{schema}")
211 }
212 }
213 }
214 });
215 }
216 if !self.path_parameters.is_empty() {
217 operation["parameters"] = json!(
218 self.path_parameters
219 .iter()
220 .map(|name| {
221 json!({
222 "name": name,
223 "in": "path",
224 "required": true,
225 "schema": {
226 "type": "string"
227 }
228 })
229 })
230 .collect::<Vec<_>>()
231 );
232 }
233 if !self.guards.is_empty() {
234 operation["x-nidus-guards"] = json!(self.guards);
235 }
236 if !self.pipes.is_empty() {
237 operation["x-nidus-pipes"] = json!(self.pipes);
238 }
239 if self.validates {
240 operation["x-nidus-validates"] = json!(true);
241 }
242
243 operation
244 }
245
246 fn request_schema_ref(mut self, schema: impl Into<String>) -> Self {
247 self.request_schema = Some(schema.into());
248 self
249 }
250
251 fn response_schema_ref(mut self, schema: impl Into<String>) -> Self {
252 self.response_schema = Some(schema.into());
253 self
254 }
255
256 fn new(
257 method: impl Into<String>,
258 path: impl Into<String>,
259 path_parameters: Vec<String>,
260 ) -> Self {
261 Self {
262 method: method.into(),
263 path: path.into(),
264 path_parameters,
265 summary: None,
266 tags: Vec::new(),
267 response_status: StatusCode::OK,
268 request_schema: None,
269 response_schema: None,
270 guards: Vec::new(),
271 pipes: Vec::new(),
272 validates: false,
273 }
274 }
275
276 fn try_new(method: impl Into<String>, path: impl Into<String>) -> Result<Self, RoutePathError> {
277 let path = path.into();
278 let path = openapi_path(&path)?;
279 let path_parameters = openapi_path_parameters(&path);
280 Ok(Self::new(method, path, path_parameters))
281 }
282}