1use 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 #[serde(skip_serializing_if = "Option::is_none")]
23 pub tags: Option<Vec<String>>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
28 pub summary: Option<String>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
33 pub description: Option<String>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 #[serde(rename = "externalDocs")]
38 pub external_docs: Option<ExternalDocumentation>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
45 #[serde(rename = "operationId")]
46 pub operation_id: Option<String>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
56 pub parameters: Option<Vec<RefOr<Parameter>>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
63 #[serde(rename = "requestBody")]
64 pub request_body: Option<RefOr<RequestBody>>,
65
66 pub responses: Responses,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
74 pub callbacks: Option<BTreeMap<String, RefOr<Callback>>>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
80 pub deprecated: Option<bool>,
81
82 #[serde(skip_serializing_if = "Option::is_none")]
89 pub security: Option<Vec<BTreeMap<String, Vec<String>>>>,
90
91 #[serde(skip_serializing_if = "Option::is_none")]
95 pub servers: Option<Vec<Server>>,
96
97 #[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 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}