1use std::ops::{Deref, DerefMut};
5
6use serde::{Deserialize, Serialize};
7
8use super::{
9 Deprecated, ExternalDocs, RefOr, SecurityRequirement, Server,
10 request_body::RequestBody,
11 response::{Response, Responses},
12};
13use crate::{Parameter, Parameters, PathItemType, PropMap, Servers};
14
15#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
17pub struct Operations(pub PropMap<PathItemType, Operation>);
18impl Deref for Operations {
19 type Target = PropMap<PathItemType, Operation>;
20
21 fn deref(&self) -> &Self::Target {
22 &self.0
23 }
24}
25impl DerefMut for Operations {
26 fn deref_mut(&mut self) -> &mut Self::Target {
27 &mut self.0
28 }
29}
30impl IntoIterator for Operations {
31 type Item = (PathItemType, Operation);
32 type IntoIter = <PropMap<PathItemType, Operation> as IntoIterator>::IntoIter;
33
34 fn into_iter(self) -> Self::IntoIter {
35 self.0.into_iter()
36 }
37}
38impl Operations {
39 pub fn new() -> Self {
41 Default::default()
42 }
43
44 pub fn is_empty(&self) -> bool {
46 self.0.is_empty()
47 }
48 pub fn operation<K: Into<PathItemType>, O: Into<Operation>>(
50 mut self,
51 item_type: K,
52 operation: O,
53 ) -> Self {
54 self.insert(item_type, operation);
55 self
56 }
57
58 pub fn insert<K: Into<PathItemType>, O: Into<Operation>>(
60 &mut self,
61 item_type: K,
62 operation: O,
63 ) {
64 self.0.insert(item_type.into(), operation.into());
65 }
66
67 pub fn append(&mut self, other: &mut Operations) {
72 self.0.append(&mut other.0);
73 }
74 pub fn extend<I>(&mut self, iter: I)
76 where
77 I: IntoIterator<Item = (PathItemType, Operation)>,
78 {
79 for (item_type, operation) in iter {
80 self.insert(item_type, operation);
81 }
82 }
83}
84
85#[non_exhaustive]
89#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
90#[serde(rename_all = "camelCase")]
91pub struct Operation {
92 #[serde(skip_serializing_if = "Vec::is_empty")]
102 pub tags: Vec<String>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
111 pub summary: Option<String>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
120 pub description: Option<String>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
129 pub operation_id: Option<String>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub external_docs: Option<ExternalDocs>,
134
135 #[serde(skip_serializing_if = "Parameters::is_empty")]
137 pub parameters: Parameters,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub request_body: Option<RequestBody>,
142
143 pub responses: Responses,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub callbacks: Option<String>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub deprecated: Option<Deprecated>,
153
154 #[serde(skip_serializing_if = "Vec::is_empty")]
160 #[serde(rename = "security")]
161 pub securities: Vec<SecurityRequirement>,
162
163 #[serde(skip_serializing_if = "Servers::is_empty")]
165 pub servers: Servers,
166
167 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
169 pub extensions: PropMap<String, serde_json::Value>,
170}
171
172impl Operation {
173 pub fn new() -> Self {
175 Default::default()
176 }
177
178 pub fn tags<I, T>(mut self, tags: I) -> Self
180 where
181 I: IntoIterator<Item = T>,
182 T: Into<String>,
183 {
184 self.tags = tags.into_iter().map(|t| t.into()).collect();
185 self
186 }
187 pub fn add_tag<S: Into<String>>(mut self, tag: S) -> Self {
189 self.tags.push(tag.into());
190 self
191 }
192
193 pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
195 self.summary = Some(summary.into());
196 self
197 }
198
199 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
201 self.description = Some(description.into());
202 self
203 }
204
205 pub fn operation_id<S: Into<String>>(mut self, operation_id: S) -> Self {
207 self.operation_id = Some(operation_id.into());
208 self
209 }
210
211 pub fn parameters<I: IntoIterator<Item = P>, P: Into<Parameter>>(
213 mut self,
214 parameters: I,
215 ) -> Self {
216 self.parameters
217 .extend(parameters.into_iter().map(|parameter| parameter.into()));
218 self
219 }
220 pub fn add_parameter<P: Into<Parameter>>(mut self, parameter: P) -> Self {
222 self.parameters.insert(parameter);
223 self
224 }
225
226 pub fn request_body(mut self, request_body: RequestBody) -> Self {
228 self.request_body = Some(request_body);
229 self
230 }
231
232 pub fn responses<R: Into<Responses>>(mut self, responses: R) -> Self {
234 self.responses = responses.into();
235 self
236 }
237 pub fn add_response<S: Into<String>, R: Into<RefOr<Response>>>(
242 mut self,
243 code: S,
244 response: R,
245 ) -> Self {
246 self.responses.insert(code, response);
247 self
248 }
249
250 pub fn deprecated<D: Into<Deprecated>>(mut self, deprecated: D) -> Self {
252 self.deprecated = Some(deprecated.into());
253 self
254 }
255
256 pub fn securities<I: IntoIterator<Item = SecurityRequirement>>(
258 mut self,
259 securities: I,
260 ) -> Self {
261 self.securities = securities.into_iter().collect();
262 self
263 }
264 pub fn add_security(mut self, security: SecurityRequirement) -> Self {
266 self.securities.push(security);
267 self
268 }
269
270 pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: I) -> Self {
272 self.servers = Servers(servers.into_iter().collect());
273 self
274 }
275 pub fn add_server(mut self, server: Server) -> Self {
277 self.servers.insert(server);
278 self
279 }
280
281 pub fn then<F>(self, func: F) -> Self
283 where
284 F: FnOnce(Self) -> Self,
285 {
286 func(self)
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use assert_json_diff::assert_json_eq;
293 use serde_json::json;
294
295 use super::{Operation, Operations};
296 use crate::{
297 Deprecated, Parameter, PathItemType, RequestBody, Responses, security::SecurityRequirement,
298 server::Server,
299 };
300
301 #[test]
302 fn operation_new() {
303 let operation = Operation::new();
304
305 assert!(operation.tags.is_empty());
306 assert!(operation.summary.is_none());
307 assert!(operation.description.is_none());
308 assert!(operation.operation_id.is_none());
309 assert!(operation.external_docs.is_none());
310 assert!(operation.parameters.is_empty());
311 assert!(operation.request_body.is_none());
312 assert!(operation.responses.is_empty());
313 assert!(operation.callbacks.is_none());
314 assert!(operation.deprecated.is_none());
315 assert!(operation.securities.is_empty());
316 assert!(operation.servers.is_empty());
317 }
318
319 #[test]
320 fn test_build_operation() {
321 let operation = Operation::new()
322 .tags(["tag1", "tag2"])
323 .add_tag("tag3")
324 .summary("summary")
325 .description("description")
326 .operation_id("operation_id")
327 .parameters([Parameter::new("param1")])
328 .add_parameter(Parameter::new("param2"))
329 .request_body(RequestBody::new())
330 .responses(Responses::new())
331 .deprecated(Deprecated::False)
332 .securities([SecurityRequirement::new("api_key", ["read:items"])])
333 .servers([Server::new("/api")]);
334
335 assert_json_eq!(
336 operation,
337 json!({
338 "responses": {},
339 "parameters": [
340 {
341 "name": "param1",
342 "in": "path",
343 "required": false
344 },
345 {
346 "name": "param2",
347 "in": "path",
348 "required": false
349 }
350 ],
351 "operationId": "operation_id",
352 "deprecated": false,
353 "security": [
354 {
355 "api_key": ["read:items"]
356 }
357 ],
358 "servers": [{"url": "/api"}],
359 "summary": "summary",
360 "tags": ["tag1", "tag2", "tag3"],
361 "description": "description",
362 "requestBody": {
363 "content": {}
364 }
365 })
366 );
367 }
368
369 #[test]
370 fn operation_security() {
371 let security_requirement1 =
372 SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
373 let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
374 let operation = Operation::new()
375 .add_security(security_requirement1)
376 .add_security(security_requirement2);
377
378 assert!(!operation.securities.is_empty());
379 }
380
381 #[test]
382 fn operation_server() {
383 let server1 = Server::new("/api");
384 let server2 = Server::new("/admin");
385 let operation = Operation::new().add_server(server1).add_server(server2);
386 assert!(!operation.servers.is_empty());
387 }
388
389 #[test]
390 fn test_operations() {
391 let operations = Operations::new();
392 assert!(operations.is_empty());
393
394 let mut operations = operations.operation(PathItemType::Get, Operation::new());
395 operations.insert(PathItemType::Post, Operation::new());
396 operations.extend([(PathItemType::Head, Operation::new())]);
397 assert_eq!(3, operations.len());
398 }
399
400 #[test]
401 fn test_operations_into_iter() {
402 let mut operations = Operations::new();
403 operations.insert(PathItemType::Get, Operation::new());
404 operations.insert(PathItemType::Post, Operation::new());
405 operations.insert(PathItemType::Head, Operation::new());
406
407 let mut iter = operations.into_iter();
408 assert_eq!((PathItemType::Get, Operation::new()), iter.next().unwrap());
409 assert_eq!((PathItemType::Post, Operation::new()), iter.next().unwrap());
410 assert_eq!((PathItemType::Head, Operation::new()), iter.next().unwrap());
411 }
412
413 #[test]
414 fn test_operations_then() {
415 let print_operation = |operation: Operation| {
416 println!("{:?}", operation);
417 operation
418 };
419 let operation = Operation::new();
420
421 operation.then(print_operation);
422 }
423}