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 #[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")]
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::{
317 Deprecated, Parameter, PathItemType, RequestBody, Responses, security::SecurityRequirement,
318 server::Server,
319 };
320
321 #[test]
322 fn operation_new() {
323 let operation = Operation::new();
324
325 assert!(operation.tags.is_empty());
326 assert!(operation.summary.is_none());
327 assert!(operation.description.is_none());
328 assert!(operation.operation_id.is_none());
329 assert!(operation.external_docs.is_none());
330 assert!(operation.parameters.is_empty());
331 assert!(operation.request_body.is_none());
332 assert!(operation.responses.is_empty());
333 assert!(operation.callbacks.is_none());
334 assert!(operation.deprecated.is_none());
335 assert!(operation.securities.is_empty());
336 assert!(operation.servers.is_empty());
337 }
338
339 #[test]
340 fn test_build_operation() {
341 let operation = Operation::new()
342 .tags(["tag1", "tag2"])
343 .add_tag("tag3")
344 .summary("summary")
345 .description("description")
346 .operation_id("operation_id")
347 .parameters([Parameter::new("param1")])
348 .add_parameter(Parameter::new("param2"))
349 .request_body(RequestBody::new())
350 .responses(Responses::new())
351 .deprecated(Deprecated::False)
352 .securities([SecurityRequirement::new("api_key", ["read:items"])])
353 .servers([Server::new("/api")]);
354
355 assert_json_eq!(
356 operation,
357 json!({
358 "responses": {},
359 "parameters": [
360 {
361 "name": "param1",
362 "in": "path",
363 "required": false
364 },
365 {
366 "name": "param2",
367 "in": "path",
368 "required": false
369 }
370 ],
371 "operationId": "operation_id",
372 "deprecated": false,
373 "security": [
374 {
375 "api_key": ["read:items"]
376 }
377 ],
378 "servers": [{"url": "/api"}],
379 "summary": "summary",
380 "tags": ["tag1", "tag2", "tag3"],
381 "description": "description",
382 "requestBody": {
383 "content": {}
384 }
385 })
386 );
387 }
388
389 #[test]
390 fn operation_security() {
391 let security_requirement1 =
392 SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
393 let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
394 let operation = Operation::new()
395 .add_security(security_requirement1)
396 .add_security(security_requirement2);
397
398 assert!(!operation.securities.is_empty());
399 }
400
401 #[test]
402 fn operation_server() {
403 let server1 = Server::new("/api");
404 let server2 = Server::new("/admin");
405 let operation = Operation::new().add_server(server1).add_server(server2);
406 assert!(!operation.servers.is_empty());
407 }
408
409 #[test]
410 fn test_operations() {
411 let operations = Operations::new();
412 assert!(operations.is_empty());
413
414 let mut operations = operations.operation(PathItemType::Get, Operation::new());
415 operations.insert(PathItemType::Post, Operation::new());
416 operations.extend([(PathItemType::Head, Operation::new())]);
417 assert_eq!(3, operations.len());
418 }
419
420 #[test]
421 fn test_operations_into_iter() {
422 let mut operations = Operations::new();
423 operations.insert(PathItemType::Get, Operation::new());
424 operations.insert(PathItemType::Post, Operation::new());
425 operations.insert(PathItemType::Head, Operation::new());
426
427 let mut iter = operations.into_iter();
428 assert_eq!((PathItemType::Get, Operation::new()), iter.next().unwrap());
429 assert_eq!((PathItemType::Post, Operation::new()), iter.next().unwrap());
430 assert_eq!((PathItemType::Head, Operation::new()), iter.next().unwrap());
431 }
432
433 #[test]
434 fn test_operations_then() {
435 let print_operation = |operation: Operation| {
436 println!("{operation:?}");
437 operation
438 };
439 let operation = Operation::new();
440
441 let _ = operation.then(print_operation);
442 }
443}