1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
//! Implements [OpenAPI Operation Object][operation] types.
//!
//! [operation]: https://spec.openapis.org/oas/latest.html#operation-object
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use serde::{Deserialize, Serialize};
use super::{
request_body::RequestBody,
response::{Response, Responses},
Deprecated, ExternalDocs, RefOr, SecurityRequirement, Server,
};
use crate::{Parameter, Parameters, PathItemType, Servers};
/// Collection for save [`Operation`]s.
#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
pub struct Operations(pub BTreeMap<PathItemType, Operation>);
impl Deref for Operations {
type Target = BTreeMap<PathItemType, Operation>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Operations {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl IntoIterator for Operations {
type Item = (PathItemType, Operation);
type IntoIter = <BTreeMap<PathItemType, Operation> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Operations {
/// Construct a new empty [`Operations`]. This is effectively same as calling [`Operations::default`].
pub fn new() -> Self {
Default::default()
}
/// Returns `true` if instance contains no elements.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Add a new operation and returns `self`.
pub fn operation<K: Into<PathItemType>, O: Into<Operation>>(mut self, item_type: K, operation: O) -> Self {
self.insert(item_type, operation);
self
}
/// Inserts a key-value pair into the instance.
pub fn insert<K: Into<PathItemType>, O: Into<Operation>>(&mut self, item_type: K, operation: O) {
self.0.insert(item_type.into(), operation.into());
}
/// Moves all elements from `other` into `self`, leaving `other` empty.
///
/// If a key from `other` is already present in `self`, the respective
/// value from `self` will be overwritten with the respective value from `other`.
pub fn append(&mut self, other: &mut Operations) {
self.0.append(&mut other.0);
}
/// Extends a collection with the contents of an iterator.
pub fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = (PathItemType, Operation)>,
{
for (item_type, operation) in iter {
self.insert(item_type, operation);
}
}
}
/// Implements [OpenAPI Operation Object][operation] object.
///
/// [operation]: https://spec.openapis.org/oas/latest.html#operation-object
#[non_exhaustive]
#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Operation {
/// List of tags used for grouping operations.
///
/// When used with derive [`#[salvo_oapi::endpoint(...)]`][derive_path] attribute macro the default
/// value used will be resolved from handler path provided in `#[openapi(paths(...))]` with
/// [`#[derive(OpenApi)]`][derive_openapi] macro. If path resolves to `None` value `crate` will
/// be used by default.
///
/// [derive_path]: ../../attr.path.html
/// [derive_openapi]: ../../derive.OpenApi.html
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
/// Short summary what [`Operation`] does.
///
/// When used with derive [`#[salvo_oapi::endpoint(...)]`][derive_path] attribute macro the value
/// is taken from **first line** of doc comment.
///
/// [derive_path]: ../../attr.path.html
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
/// Long explanation of [`Operation`] behaviour. Markdown syntax is supported.
///
/// When used with derive [`#[salvo_oapi::endpoint(...)]`][derive_path] attribute macro the
/// doc comment is used as value for description.
///
/// [derive_path]: ../../attr.path.html
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// Unique identifier for the API [`Operation`]. Most typically this is mapped to handler function name.
///
/// When used with derive [`#[salvo_oapi::endpoint(...)]`][derive_path] attribute macro the handler function
/// name will be used by default.
///
/// [derive_path]: ../../attr.path.html
#[serde(skip_serializing_if = "Option::is_none")]
pub operation_id: Option<String>,
/// Additional external documentation for this operation.
#[serde(skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocs>,
/// List of applicable parameters for this [`Operation`].
#[serde(skip_serializing_if = "Parameters::is_empty")]
pub parameters: Parameters,
/// Optional request body for this [`Operation`].
#[serde(skip_serializing_if = "Option::is_none")]
pub request_body: Option<RequestBody>,
/// List of possible responses returned by the [`Operation`].
pub responses: Responses,
/// Callback information.
#[serde(skip_serializing_if = "Option::is_none")]
pub callbacks: Option<String>,
/// Define whether the operation is deprecated or not and thus should be avoided consuming.
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<Deprecated>,
/// Declaration which security mechanisms can be used for for the operation. Only one
/// [`SecurityRequirement`] must be met.
///
/// Security for the [`Operation`] can be set to optional by adding empty security with
/// [`SecurityRequirement::default`].
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "security")]
pub securities: Vec<SecurityRequirement>,
/// Alternative [`Server`]s for this [`Operation`].
#[serde(skip_serializing_if = "Servers::is_empty")]
pub servers: Servers,
}
impl Operation {
/// Construct a new API [`Operation`].
pub fn new() -> Self {
Default::default()
}
/// Add or change tags of the [`Operation`].
pub fn tags<I, T>(mut self, tags: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
self.tags = tags.into_iter().map(|t| t.into()).collect();
self
}
/// Append tag to [`Operation`] tags.
pub fn add_tag<S: Into<String>>(mut self, tag: S) -> Self {
self.tags.push(tag.into());
self
}
/// Add or change short summary of the [`Operation`].
pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
self.summary = Some(summary.into());
self
}
/// Add or change description of the [`Operation`].
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
self.description = Some(description.into());
self
}
/// Add or change operation id of the [`Operation`].
pub fn operation_id<S: Into<String>>(mut self, operation_id: S) -> Self {
self.operation_id = Some(operation_id.into());
self
}
/// Add or change parameters of the [`Operation`].
pub fn parameters<I: IntoIterator<Item = P>, P: Into<Parameter>>(mut self, parameters: I) -> Self {
self.parameters
.extend(parameters.into_iter().map(|parameter| parameter.into()));
self
}
/// Append parameter to [`Operation`] parameters.
pub fn add_parameter<P: Into<Parameter>>(mut self, parameter: P) -> Self {
self.parameters.insert(parameter);
self
}
/// Add or change request body of the [`Operation`].
pub fn request_body(mut self, request_body: RequestBody) -> Self {
self.request_body = Some(request_body);
self
}
/// Add or change responses of the [`Operation`].
pub fn responses<R: Into<Responses>>(mut self, responses: R) -> Self {
self.responses = responses.into();
self
}
/// Append status code and a [`Response`] to the [`Operation`] responses map.
///
/// * `code` must be valid HTTP status code.
/// * `response` is instances of [`Response`].
pub fn add_response<S: Into<String>, R: Into<RefOr<Response>>>(mut self, code: S, response: R) -> Self {
self.responses.insert(code, response);
self
}
/// Add or change deprecated status of the [`Operation`].
pub fn deprecated<D: Into<Deprecated>>(mut self, deprecated: D) -> Self {
self.deprecated = Some(deprecated.into());
self
}
/// Add or change list of [`SecurityRequirement`]s that are available for [`Operation`].
pub fn securities<I: IntoIterator<Item = SecurityRequirement>>(mut self, securities: I) -> Self {
self.securities = securities.into_iter().collect();
self
}
/// Append [`SecurityRequirement`] to [`Operation`] security requirements.
pub fn add_security(mut self, security: SecurityRequirement) -> Self {
self.securities.push(security);
self
}
/// Add or change list of [`Server`]s of the [`Operation`].
pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: I) -> Self {
self.servers = Servers(servers.into_iter().collect());
self
}
/// Append a new [`Server`] to the [`Operation`] servers.
pub fn add_server(mut self, server: Server) -> Self {
self.servers.insert(server);
self
}
/// For easy chaining of operations.
pub fn then<F>(self, func: F) -> Self
where
F: FnOnce(Self) -> Self,
{
func(self)
}
}
#[cfg(test)]
mod tests {
use super::Operation;
use crate::{security::SecurityRequirement, server::Server};
#[test]
fn operation_new() {
let operation = Operation::new();
assert!(operation.tags.is_empty());
assert!(operation.summary.is_none());
assert!(operation.description.is_none());
assert!(operation.operation_id.is_none());
assert!(operation.external_docs.is_none());
assert!(operation.parameters.is_empty());
assert!(operation.request_body.is_none());
assert!(operation.responses.is_empty());
assert!(operation.callbacks.is_none());
assert!(operation.deprecated.is_none());
assert!(operation.securities.is_empty());
assert!(operation.servers.is_empty());
}
#[test]
fn operation_security() {
let security_requirement1 = SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
let operation = Operation::new()
.add_security(security_requirement1)
.add_security(security_requirement2);
assert!(!operation.securities.is_empty());
}
#[test]
fn operation_server() {
let server1 = Server::new("/api");
let server2 = Server::new("/admin");
let operation = Operation::new().add_server(server1).add_server(server2);
assert!(!operation.servers.is_empty());
}
}