roas/v3_0/
operation.rs

1//! Operation Object
2
3use crate::common::helpers::{Context, PushError, ValidateWithContext, validate_required_string};
4use crate::common::reference::RefOr;
5use crate::v3_0::callback::Callback;
6use crate::v3_0::external_documentation::ExternalDocumentation;
7use crate::v3_0::parameter::Parameter;
8use crate::v3_0::request_body::RequestBody;
9use crate::v3_0::response::Responses;
10use crate::v3_0::security_scheme::SecurityScheme;
11use crate::v3_0::server::Server;
12use crate::v3_0::spec::Spec;
13use crate::v3_0::tag::Tag;
14use crate::validation::Options;
15use serde::{Deserialize, Serialize};
16use std::collections::BTreeMap;
17
18#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
19pub struct Operation {
20    /// A list of tags for API documentation control.
21    /// Tags can be used for logical grouping of operations by resources or any other qualifier.
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub tags: Option<Vec<String>>,
24
25    /// A short summary which by default SHOULD override that of the referenced component.
26    /// If the referenced object-type does not allow a summary field, then this field has no effect.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub summary: Option<String>,
29
30    /// A verbose explanation of the operation behavior.
31    /// [CommonMark](https://spec.commonmark.org) syntax MAY be used for rich text representation.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub description: Option<String>,
34
35    /// Additional external documentation for this operation.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    #[serde(rename = "externalDocs")]
38    pub external_docs: Option<ExternalDocumentation>,
39
40    /// Unique string used to identify the operation.
41    /// The id MUST be unique among all operations described in the API.
42    /// Tools and libraries MAY use the operationId to uniquely identify an operation, therefore,
43    /// it is recommended to follow common programming naming conventions.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    #[serde(rename = "operationId")]
46    pub operation_id: Option<String>,
47
48    /// A list of parameters that are applicable for this operation.
49    /// If a parameter is already defined at the Path Item, the new definition will override it,
50    /// but can never remove it.
51    /// The list MUST NOT include duplicated parameters.
52    /// A unique parameter is defined by a combination of a name and location.
53    /// The list can use the Reference Object to link to parameters that are defined at the Swagger Object's parameters.
54    /// There can be one "body" parameter at most.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub parameters: Option<Vec<RefOr<Parameter>>>,
57
58    /// The request body applicable for this operation.
59    /// The `requestBody` is only supported in HTTP methods where the HTTP 1.1 specification
60    /// [RFC7231](https://www.rfc-editor.org/rfc/rfc7231) has explicitly defined semantics for request bodies.
61    /// In other cases where the HTTP spec is vague, `requestBody` SHALL be ignored by consumers.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    #[serde(rename = "requestBody")]
64    pub request_body: Option<RefOr<RequestBody>>,
65
66    /// **Required** The list of possible responses as they are returned from executing this operation.
67    pub responses: Responses,
68
69    /// A map of possible out-of band callbacks related to the parent operation.
70    /// The key is a unique identifier for the Callback Object.
71    /// Each value in the map is a Callback Object that describes a request that
72    /// may be initiated by the API provider and the expected responses.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub callbacks: Option<BTreeMap<String, RefOr<Callback>>>,
75
76    /// Declares this operation to be deprecated.
77    /// Usage of the declared operation should be refrained.
78    /// Default value is false.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub deprecated: Option<bool>,
81
82    /// A declaration of which security mechanisms can be used for this operation.
83    /// The list of values includes alternative security requirement objects that can be used.
84    /// Only one of the security requirement objects need to be satisfied to authorize a request.
85    /// To make security optional, an empty security requirement (`{}`) can be included in the array.
86    /// This definition overrides any declared top-level `security`.
87    /// To remove a top-level security declaration, an empty array can be used.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub security: Option<Vec<BTreeMap<String, Vec<String>>>>,
90
91    /// An alternative `server` array to service this operation.
92    /// If an alternative `server` object is specified at the Path Item Object or Root level,
93    /// it will be overridden by this value.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub servers: Option<Vec<Server>>,
96
97    /// Allows extensions to the Swagger Schema.
98    /// The field name MUST begin with `x-`, for example, `x-internal-id`.
99    /// The value can be null, a primitive, an array or an object.
100    #[serde(flatten)]
101    #[serde(with = "crate::common::extensions")]
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub extensions: Option<BTreeMap<String, serde_json::Value>>,
104}
105
106impl ValidateWithContext<Spec> for Operation {
107    fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
108        // do not validate operation_id, it is already validated in PathItem
109
110        if let Some(tags) = &self.tags {
111            for (i, tag) in tags.iter().enumerate() {
112                let path = format!("{path}.tags[{i}]");
113                validate_required_string(tag, ctx, path.clone());
114                if tag.is_empty() {
115                    continue;
116                }
117                let reference = format!("#/tags/{tag}");
118                if let Ok(spec_tag) = RefOr::<Tag>::new_ref(reference.clone()).get_item(ctx.spec) {
119                    if ctx.visit(reference.clone()) {
120                        spec_tag.validate_with_context(ctx, reference);
121                    }
122                } else if !ctx.is_option(Options::IgnoreMissingTags) {
123                    ctx.error(path, format_args!(".tags[{i}]: `{tag}` not found in spec"));
124                }
125            }
126        }
127
128        if let Some(parameters) = &self.parameters {
129            for (i, parameter) in parameters.clone().iter().enumerate() {
130                parameter.validate_with_context(ctx, format!("{path}.parameters[{i}]"));
131            }
132        }
133
134        if let Some(request_body) = &self.request_body {
135            request_body.validate_with_context(ctx, format!("{path}.requestBody"));
136        }
137
138        if let Some(servers) = &self.servers {
139            for (i, server) in servers.iter().enumerate() {
140                server.validate_with_context(ctx, format!("{path}.servers[{i}]"));
141            }
142        }
143
144        if let Some(callbacks) = &self.callbacks {
145            for (k, v) in callbacks {
146                v.validate_with_context(ctx, format!("{path}.callbacks[{k}]"));
147            }
148        }
149
150        self.responses
151            .validate_with_context(ctx, format!("{path}.responses"));
152
153        if let Some(external_doc) = &self.external_docs {
154            external_doc.validate_with_context(ctx, format!("{path}.externalDocs"));
155        }
156
157        if let Some(security) = &self.security {
158            for (i, security) in security.iter().enumerate() {
159                for (name, scopes) in security {
160                    let path = format!("{path}.security[{i}][{name}]");
161                    let reference = format!("#/components/securitySchemes/{name}");
162                    let spec_ref = RefOr::<SecurityScheme>::new_ref(reference.clone());
163                    spec_ref.validate_with_context(ctx, path.clone());
164                    if !scopes.is_empty()
165                        && let Ok(SecurityScheme::OAuth2(oauth2)) = spec_ref.get_item(ctx.spec)
166                    {
167                        for scope in scopes {
168                            ctx.visit(format!("{reference}/{scope}"));
169                            let mut found = false;
170                            if let Some(flow) = &oauth2.flows.implicit {
171                                found = found || flow.scopes.contains_key(scope)
172                            }
173                            if !found && let Some(flow) = &oauth2.flows.password {
174                                found = found || flow.scopes.contains_key(scope)
175                            }
176                            if !found && let Some(flow) = &oauth2.flows.client_credentials {
177                                found = found || flow.scopes.contains_key(scope)
178                            }
179                            if !found && let Some(flow) = &oauth2.flows.authorization_code {
180                                found = found || flow.scopes.contains_key(scope)
181                            }
182                            if !found {
183                                ctx.error(
184                                    path.clone(),
185                                    format_args!(
186                                        "scope `{scope}` not found in spec by reference `{reference}`"
187                                    ),
188                                );
189                            }
190                        }
191                    }
192                }
193            }
194        }
195    }
196}