1use std::ops::{Deref, DerefMut};
5
6use serde::{Deserialize, Serialize};
7
8use super::callback::Callback;
9use super::request_body::RequestBody;
10use super::response::{Response, Responses};
11use super::{Deprecated, ExternalDocs, RefOr, SecurityRequirement, Server};
12use crate::{Parameter, Parameters, PathItemType, PropMap, Servers};
13
14#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
16pub struct Operations(pub PropMap<PathItemType, Operation>);
17impl Deref for Operations {
18 type Target = PropMap<PathItemType, Operation>;
19
20 fn deref(&self) -> &Self::Target {
21 &self.0
22 }
23}
24impl DerefMut for Operations {
25 fn deref_mut(&mut self) -> &mut Self::Target {
26 &mut self.0
27 }
28}
29impl IntoIterator for Operations {
30 type Item = (PathItemType, Operation);
31 type IntoIter = <PropMap<PathItemType, Operation> as IntoIterator>::IntoIter;
32
33 fn into_iter(self) -> Self::IntoIter {
34 self.0.into_iter()
35 }
36}
37impl Operations {
38 #[must_use]
41 pub fn new() -> Self {
42 Default::default()
43 }
44
45 #[must_use]
47 pub fn is_empty(&self) -> bool {
48 self.0.is_empty()
49 }
50 #[must_use]
52 pub fn operation<K: Into<PathItemType>, O: Into<Operation>>(
53 mut self,
54 item_type: K,
55 operation: O,
56 ) -> Self {
57 self.insert(item_type, operation);
58 self
59 }
60
61 pub fn insert<K: Into<PathItemType>, O: Into<Operation>>(
63 &mut self,
64 item_type: K,
65 operation: O,
66 ) {
67 self.0.insert(item_type.into(), operation.into());
68 }
69
70 pub fn append(&mut self, other: &mut Self) {
75 self.0.append(&mut other.0);
76 }
77 pub fn extend<I>(&mut self, iter: I)
79 where
80 I: IntoIterator<Item = (PathItemType, Operation)>,
81 {
82 for (item_type, operation) in iter {
83 self.insert(item_type, operation);
84 }
85 }
86}
87
88#[non_exhaustive]
92#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
93#[serde(rename_all = "camelCase")]
94pub struct Operation {
95 #[serde(skip_serializing_if = "Vec::is_empty")]
105 pub tags: Vec<String>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
114 pub summary: Option<String>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
123 pub description: Option<String>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
133 pub operation_id: Option<String>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub external_docs: Option<ExternalDocs>,
138
139 #[serde(skip_serializing_if = "Parameters::is_empty")]
141 pub parameters: Parameters,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub request_body: Option<RequestBody>,
146
147 pub responses: Responses,
149
150 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
156 pub callbacks: PropMap<String, RefOr<Callback>>,
157
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub deprecated: Option<Deprecated>,
161
162 #[serde(skip_serializing_if = "Vec::is_empty")]
168 #[serde(rename = "security")]
169 pub securities: Vec<SecurityRequirement>,
170
171 #[serde(skip_serializing_if = "Servers::is_empty")]
173 pub servers: Servers,
174
175 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
177 pub extensions: PropMap<String, serde_json::Value>,
178}
179
180impl Operation {
181 #[must_use]
183 pub fn new() -> Self {
184 Default::default()
185 }
186
187 #[must_use]
189 pub fn tags<I, T>(mut self, tags: I) -> Self
190 where
191 I: IntoIterator<Item = T>,
192 T: Into<String>,
193 {
194 self.tags = tags.into_iter().map(|t| t.into()).collect();
195 self
196 }
197 #[must_use]
199 pub fn add_tag<S: Into<String>>(mut self, tag: S) -> Self {
200 self.tags.push(tag.into());
201 self
202 }
203
204 #[must_use]
206 pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
207 self.summary = Some(summary.into());
208 self
209 }
210
211 #[must_use]
213 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
214 self.description = Some(description.into());
215 self
216 }
217
218 #[must_use]
220 pub fn operation_id<S: Into<String>>(mut self, operation_id: S) -> Self {
221 self.operation_id = Some(operation_id.into());
222 self
223 }
224
225 #[must_use]
227 pub fn parameters<I: IntoIterator<Item = P>, P: Into<Parameter>>(
228 mut self,
229 parameters: I,
230 ) -> Self {
231 self.parameters
232 .extend(parameters.into_iter().map(|parameter| parameter.into()));
233 self
234 }
235 #[must_use]
237 pub fn add_parameter<P: Into<Parameter>>(mut self, parameter: P) -> Self {
238 self.parameters.insert(parameter);
239 self
240 }
241
242 #[must_use]
244 pub fn request_body(mut self, request_body: RequestBody) -> Self {
245 self.request_body = Some(request_body);
246 self
247 }
248
249 #[must_use]
251 pub fn responses<R: Into<Responses>>(mut self, responses: R) -> Self {
252 self.responses = responses.into();
253 self
254 }
255 #[must_use]
260 pub fn add_response<S: Into<String>, R: Into<RefOr<Response>>>(
261 mut self,
262 code: S,
263 response: R,
264 ) -> Self {
265 self.responses.insert(code, response);
266 self
267 }
268
269 #[must_use]
271 pub fn deprecated<D: Into<Deprecated>>(mut self, deprecated: D) -> Self {
272 self.deprecated = Some(deprecated.into());
273 self
274 }
275
276 #[must_use]
278 pub fn securities<I: IntoIterator<Item = SecurityRequirement>>(
279 mut self,
280 securities: I,
281 ) -> Self {
282 self.securities = securities.into_iter().collect();
283 self
284 }
285 #[must_use]
287 pub fn add_security(mut self, security: SecurityRequirement) -> Self {
288 self.securities.push(security);
289 self
290 }
291
292 #[must_use]
294 pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: I) -> Self {
295 self.servers = Servers(servers.into_iter().collect());
296 self
297 }
298 #[must_use]
300 pub fn add_server(mut self, server: Server) -> Self {
301 self.servers.insert(server);
302 self
303 }
304
305 #[must_use]
307 pub fn callbacks<I, K, V>(mut self, callbacks: I) -> Self
308 where
309 I: IntoIterator<Item = (K, V)>,
310 K: Into<String>,
311 V: Into<RefOr<Callback>>,
312 {
313 self.callbacks = callbacks
314 .into_iter()
315 .map(|(name, callback)| (name.into(), callback.into()))
316 .collect();
317 self
318 }
319
320 #[must_use]
322 pub fn add_callback<K: Into<String>, V: Into<RefOr<Callback>>>(
323 mut self,
324 name: K,
325 callback: V,
326 ) -> Self {
327 self.callbacks.insert(name.into(), callback.into());
328 self
329 }
330
331 #[must_use]
333 pub fn then<F>(self, func: F) -> Self
334 where
335 F: FnOnce(Self) -> Self,
336 {
337 func(self)
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use assert_json_diff::assert_json_eq;
344 use serde_json::json;
345
346 use super::{Operation, Operations};
347 use crate::security::SecurityRequirement;
348 use crate::server::Server;
349 use crate::{
350 Callback, Deprecated, Parameter, PathItem, PathItemType, RefOr, RequestBody, Responses,
351 };
352
353 #[test]
354 fn operation_new() {
355 let operation = Operation::new();
356
357 assert!(operation.tags.is_empty());
358 assert!(operation.summary.is_none());
359 assert!(operation.description.is_none());
360 assert!(operation.operation_id.is_none());
361 assert!(operation.external_docs.is_none());
362 assert!(operation.parameters.is_empty());
363 assert!(operation.request_body.is_none());
364 assert!(operation.responses.is_empty());
365 assert!(operation.callbacks.is_empty());
366 assert!(operation.deprecated.is_none());
367 assert!(operation.securities.is_empty());
368 assert!(operation.servers.is_empty());
369 }
370
371 #[test]
372 fn test_build_operation() {
373 let operation = Operation::new()
374 .tags(["tag1", "tag2"])
375 .add_tag("tag3")
376 .summary("summary")
377 .description("description")
378 .operation_id("operation_id")
379 .parameters([Parameter::new("param1")])
380 .add_parameter(Parameter::new("param2"))
381 .request_body(RequestBody::new())
382 .responses(Responses::new())
383 .deprecated(Deprecated::False)
384 .securities([SecurityRequirement::new("api_key", ["read:items"])])
385 .servers([Server::new("/api")]);
386
387 assert_json_eq!(
388 operation,
389 json!({
390 "responses": {},
391 "parameters": [
392 {
393 "name": "param1",
394 "in": "path",
395 "required": true
396 },
397 {
398 "name": "param2",
399 "in": "path",
400 "required": true
401 }
402 ],
403 "operationId": "operation_id",
404 "deprecated": false,
405 "security": [
406 {
407 "api_key": ["read:items"]
408 }
409 ],
410 "servers": [{"url": "/api"}],
411 "summary": "summary",
412 "tags": ["tag1", "tag2", "tag3"],
413 "description": "description",
414 "requestBody": {
415 "content": {}
416 }
417 })
418 );
419 }
420
421 #[test]
422 fn operation_security() {
423 let security_requirement1 =
424 SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
425 let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
426 let operation = Operation::new()
427 .add_security(security_requirement1)
428 .add_security(security_requirement2);
429
430 assert!(!operation.securities.is_empty());
431 }
432
433 #[test]
434 fn operation_server() {
435 let server1 = Server::new("/api");
436 let server2 = Server::new("/admin");
437 let operation = Operation::new().add_server(server1).add_server(server2);
438 assert!(!operation.servers.is_empty());
439 }
440
441 #[test]
442 fn test_operations() {
443 let operations = Operations::new();
444 assert!(operations.is_empty());
445
446 let mut operations = operations.operation(PathItemType::Get, Operation::new());
447 operations.insert(PathItemType::Post, Operation::new());
448 operations.extend([(PathItemType::Head, Operation::new())]);
449 assert_eq!(3, operations.len());
450 }
451
452 #[test]
453 fn test_operations_into_iter() {
454 let mut operations = Operations::new();
455 operations.insert(PathItemType::Get, Operation::new());
456 operations.insert(PathItemType::Post, Operation::new());
457 operations.insert(PathItemType::Head, Operation::new());
458
459 let mut iter = operations.into_iter();
460 assert_eq!((PathItemType::Get, Operation::new()), iter.next().unwrap());
461 assert_eq!((PathItemType::Post, Operation::new()), iter.next().unwrap());
462 assert_eq!((PathItemType::Head, Operation::new()), iter.next().unwrap());
463 }
464
465 #[test]
466 fn operation_callbacks_serialize_as_per_spec() {
467 let callback = Callback::new().path(
468 "{$request.body#/callbackUrl}",
469 PathItem::new(PathItemType::Post, Operation::new()),
470 );
471 let operation = Operation::new()
472 .add_callback("orderShipped", callback)
473 .add_callback(
474 "orderRefunded",
475 RefOr::Ref(crate::Ref::new("#/components/callbacks/RefundEvent")),
476 );
477
478 assert_json_eq!(
479 operation,
480 json!({
481 "responses": {},
482 "callbacks": {
483 "orderShipped": {
484 "{$request.body#/callbackUrl}": {
485 "post": { "responses": {} }
486 }
487 },
488 "orderRefunded": {
489 "$ref": "#/components/callbacks/RefundEvent"
490 }
491 }
492 })
493 );
494 }
495
496 #[test]
497 fn test_operations_then() {
498 let print_operation = |operation: Operation| {
499 println!("{operation:?}");
500 operation
501 };
502 let operation = Operation::new();
503
504 let _ = operation.then(print_operation);
505 }
506}