Skip to main content

a3s_boot/
openapi_security.rs

1use serde::Serialize;
2use std::collections::BTreeMap;
3
4/// OpenAPI security requirement object.
5pub type OpenApiSecurityRequirement = BTreeMap<String, Vec<String>>;
6
7/// OpenAPI security scheme metadata registered by routes.
8#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
9pub struct OpenApiSecurityScheme {
10    #[serde(rename = "type")]
11    pub ty: String,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub scheme: Option<String>,
14    #[serde(rename = "bearerFormat", skip_serializing_if = "Option::is_none")]
15    pub bearer_format: Option<String>,
16    #[serde(rename = "in", skip_serializing_if = "Option::is_none")]
17    pub location: Option<OpenApiApiKeyLocation>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub name: Option<String>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub description: Option<String>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub flows: Option<OpenApiOAuthFlows>,
24    #[serde(rename = "openIdConnectUrl", skip_serializing_if = "Option::is_none")]
25    pub open_id_connect_url: Option<String>,
26}
27
28impl OpenApiSecurityScheme {
29    pub fn http_bearer() -> Self {
30        Self {
31            ty: "http".to_string(),
32            scheme: Some("bearer".to_string()),
33            bearer_format: Some("JWT".to_string()),
34            location: None,
35            name: None,
36            description: None,
37            flows: None,
38            open_id_connect_url: None,
39        }
40    }
41
42    pub fn api_key(location: OpenApiApiKeyLocation, name: impl Into<String>) -> Self {
43        Self {
44            ty: "apiKey".to_string(),
45            scheme: None,
46            bearer_format: None,
47            location: Some(location),
48            name: Some(name.into()),
49            description: None,
50            flows: None,
51            open_id_connect_url: None,
52        }
53    }
54
55    pub fn api_key_header(name: impl Into<String>) -> Self {
56        Self::api_key(OpenApiApiKeyLocation::Header, name)
57    }
58
59    pub fn api_key_query(name: impl Into<String>) -> Self {
60        Self::api_key(OpenApiApiKeyLocation::Query, name)
61    }
62
63    pub fn api_key_cookie(name: impl Into<String>) -> Self {
64        Self::api_key(OpenApiApiKeyLocation::Cookie, name)
65    }
66
67    pub fn oauth2(flows: OpenApiOAuthFlows) -> Self {
68        Self {
69            ty: "oauth2".to_string(),
70            scheme: None,
71            bearer_format: None,
72            location: None,
73            name: None,
74            description: None,
75            flows: Some(flows),
76            open_id_connect_url: None,
77        }
78    }
79
80    pub fn open_id_connect(url: impl Into<String>) -> Self {
81        Self {
82            ty: "openIdConnect".to_string(),
83            scheme: None,
84            bearer_format: None,
85            location: None,
86            name: None,
87            description: None,
88            flows: None,
89            open_id_connect_url: Some(url.into()),
90        }
91    }
92
93    pub fn with_description(mut self, description: impl Into<String>) -> Self {
94        self.description = Some(description.into());
95        self
96    }
97}
98
99/// OpenAPI OAuth flow map.
100#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
101pub struct OpenApiOAuthFlows {
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub implicit: Option<OpenApiOAuthFlow>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub password: Option<OpenApiOAuthFlow>,
106    #[serde(rename = "clientCredentials", skip_serializing_if = "Option::is_none")]
107    pub client_credentials: Option<OpenApiOAuthFlow>,
108    #[serde(rename = "authorizationCode", skip_serializing_if = "Option::is_none")]
109    pub authorization_code: Option<OpenApiOAuthFlow>,
110}
111
112impl OpenApiOAuthFlows {
113    pub fn new() -> Self {
114        Self::default()
115    }
116
117    pub fn implicit<I, K, V>(authorization_url: impl Into<String>, scopes: I) -> Self
118    where
119        I: IntoIterator<Item = (K, V)>,
120        K: Into<String>,
121        V: Into<String>,
122    {
123        Self::new().with_implicit(OpenApiOAuthFlow::implicit(authorization_url, scopes))
124    }
125
126    pub fn password<I, K, V>(token_url: impl Into<String>, scopes: I) -> Self
127    where
128        I: IntoIterator<Item = (K, V)>,
129        K: Into<String>,
130        V: Into<String>,
131    {
132        Self::new().with_password(OpenApiOAuthFlow::password(token_url, scopes))
133    }
134
135    pub fn client_credentials<I, K, V>(token_url: impl Into<String>, scopes: I) -> Self
136    where
137        I: IntoIterator<Item = (K, V)>,
138        K: Into<String>,
139        V: Into<String>,
140    {
141        Self::new().with_client_credentials(OpenApiOAuthFlow::client_credentials(token_url, scopes))
142    }
143
144    pub fn authorization_code<I, K, V>(
145        authorization_url: impl Into<String>,
146        token_url: impl Into<String>,
147        scopes: I,
148    ) -> Self
149    where
150        I: IntoIterator<Item = (K, V)>,
151        K: Into<String>,
152        V: Into<String>,
153    {
154        Self::new().with_authorization_code(OpenApiOAuthFlow::authorization_code(
155            authorization_url,
156            token_url,
157            scopes,
158        ))
159    }
160
161    pub fn with_implicit(mut self, flow: OpenApiOAuthFlow) -> Self {
162        self.implicit = Some(flow);
163        self
164    }
165
166    pub fn with_password(mut self, flow: OpenApiOAuthFlow) -> Self {
167        self.password = Some(flow);
168        self
169    }
170
171    pub fn with_client_credentials(mut self, flow: OpenApiOAuthFlow) -> Self {
172        self.client_credentials = Some(flow);
173        self
174    }
175
176    pub fn with_authorization_code(mut self, flow: OpenApiOAuthFlow) -> Self {
177        self.authorization_code = Some(flow);
178        self
179    }
180}
181
182/// OpenAPI OAuth flow metadata.
183#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
184pub struct OpenApiOAuthFlow {
185    #[serde(rename = "authorizationUrl", skip_serializing_if = "Option::is_none")]
186    pub authorization_url: Option<String>,
187    #[serde(rename = "tokenUrl", skip_serializing_if = "Option::is_none")]
188    pub token_url: Option<String>,
189    #[serde(rename = "refreshUrl", skip_serializing_if = "Option::is_none")]
190    pub refresh_url: Option<String>,
191    pub scopes: BTreeMap<String, String>,
192}
193
194impl OpenApiOAuthFlow {
195    pub fn implicit<I, K, V>(authorization_url: impl Into<String>, scopes: I) -> Self
196    where
197        I: IntoIterator<Item = (K, V)>,
198        K: Into<String>,
199        V: Into<String>,
200    {
201        Self {
202            authorization_url: Some(authorization_url.into()),
203            token_url: None,
204            refresh_url: None,
205            scopes: oauth_scopes(scopes),
206        }
207    }
208
209    pub fn password<I, K, V>(token_url: impl Into<String>, scopes: I) -> Self
210    where
211        I: IntoIterator<Item = (K, V)>,
212        K: Into<String>,
213        V: Into<String>,
214    {
215        Self {
216            authorization_url: None,
217            token_url: Some(token_url.into()),
218            refresh_url: None,
219            scopes: oauth_scopes(scopes),
220        }
221    }
222
223    pub fn client_credentials<I, K, V>(token_url: impl Into<String>, scopes: I) -> Self
224    where
225        I: IntoIterator<Item = (K, V)>,
226        K: Into<String>,
227        V: Into<String>,
228    {
229        Self {
230            authorization_url: None,
231            token_url: Some(token_url.into()),
232            refresh_url: None,
233            scopes: oauth_scopes(scopes),
234        }
235    }
236
237    pub fn authorization_code<I, K, V>(
238        authorization_url: impl Into<String>,
239        token_url: impl Into<String>,
240        scopes: I,
241    ) -> Self
242    where
243        I: IntoIterator<Item = (K, V)>,
244        K: Into<String>,
245        V: Into<String>,
246    {
247        Self {
248            authorization_url: Some(authorization_url.into()),
249            token_url: Some(token_url.into()),
250            refresh_url: None,
251            scopes: oauth_scopes(scopes),
252        }
253    }
254
255    pub fn with_refresh_url(mut self, refresh_url: impl Into<String>) -> Self {
256        self.refresh_url = Some(refresh_url.into());
257        self
258    }
259}
260
261fn oauth_scopes<I, K, V>(scopes: I) -> BTreeMap<String, String>
262where
263    I: IntoIterator<Item = (K, V)>,
264    K: Into<String>,
265    V: Into<String>,
266{
267    scopes
268        .into_iter()
269        .map(|(scope, description)| (scope.into(), description.into()))
270        .collect()
271}
272
273/// Location for OpenAPI API key security schemes.
274#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
275#[serde(rename_all = "lowercase")]
276pub enum OpenApiApiKeyLocation {
277    Query,
278    Header,
279    Cookie,
280}