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