use std::collections::{BTreeMap, HashMap};
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};
#[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 {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn operation<K: Into<PathItemType>, O: Into<Operation>>(mut self, item_type: K, operation: O) -> Self {
self.insert(item_type, operation);
self
}
pub fn insert<K: Into<PathItemType>, O: Into<Operation>>(&mut self, item_type: K, operation: O) {
self.0.insert(item_type.into(), operation.into());
}
pub fn append(&mut self, other: &mut Operations) {
self.0.append(&mut other.0);
}
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);
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Operation {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operation_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocs>,
#[serde(skip_serializing_if = "Parameters::is_empty")]
pub parameters: Parameters,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_body: Option<RequestBody>,
pub responses: Responses,
#[serde(skip_serializing_if = "Option::is_none")]
pub callbacks: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<Deprecated>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "security")]
pub securities: Vec<SecurityRequirement>,
#[serde(skip_serializing_if = "Servers::is_empty")]
pub servers: Servers,
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<HashMap<String, serde_json::Value>>,
}
impl Operation {
pub fn new() -> Self {
Default::default()
}
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
}
pub fn add_tag<S: Into<String>>(mut self, tag: S) -> Self {
self.tags.push(tag.into());
self
}
pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
self.summary = Some(summary.into());
self
}
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
self.description = Some(description.into());
self
}
pub fn operation_id<S: Into<String>>(mut self, operation_id: S) -> Self {
self.operation_id = Some(operation_id.into());
self
}
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
}
pub fn add_parameter<P: Into<Parameter>>(mut self, parameter: P) -> Self {
self.parameters.insert(parameter);
self
}
pub fn request_body(mut self, request_body: RequestBody) -> Self {
self.request_body = Some(request_body);
self
}
pub fn responses<R: Into<Responses>>(mut self, responses: R) -> Self {
self.responses = responses.into();
self
}
pub fn add_response<S: Into<String>, R: Into<RefOr<Response>>>(mut self, code: S, response: R) -> Self {
self.responses.insert(code, response);
self
}
pub fn deprecated<D: Into<Deprecated>>(mut self, deprecated: D) -> Self {
self.deprecated = Some(deprecated.into());
self
}
pub fn securities<I: IntoIterator<Item = SecurityRequirement>>(mut self, securities: I) -> Self {
self.securities = securities.into_iter().collect();
self
}
pub fn add_security(mut self, security: SecurityRequirement) -> Self {
self.securities.push(security);
self
}
pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: I) -> Self {
self.servers = Servers(servers.into_iter().collect());
self
}
pub fn add_server(mut self, server: Server) -> Self {
self.servers.insert(server);
self
}
pub fn then<F>(self, func: F) -> Self
where
F: FnOnce(Self) -> Self,
{
func(self)
}
}
#[cfg(test)]
mod tests {
use assert_json_diff::assert_json_eq;
use serde_json::json;
use super::{Operation, Operations};
use crate::{
security::SecurityRequirement, server::Server, Deprecated, Parameter, PathItemType, RequestBody, Responses,
};
#[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 test_build_operation() {
let operation = Operation::new()
.tags(["tag1", "tag2"])
.add_tag("tag3")
.summary("summary")
.description("description")
.operation_id("operation_id")
.parameters([Parameter::new("param1")])
.add_parameter(Parameter::new("param2"))
.request_body(RequestBody::new())
.responses(Responses::new())
.deprecated(Deprecated::False)
.securities([SecurityRequirement::new("api_key", ["read:items"])])
.servers([Server::new("/api")]);
assert_json_eq!(
operation,
json!({
"responses": {},
"parameters": [
{
"name": "param1",
"in": "path",
"required": false
},
{
"name": "param2",
"in": "path",
"required": false
}
],
"operationId": "operation_id",
"deprecated": false,
"security": [
{
"api_key": ["read:items"]
}
],
"servers": [{"url": "/api"}],
"summary": "summary",
"tags": ["tag1", "tag2", "tag3"],
"description": "description",
"requestBody": {
"content": {}
}
})
);
}
#[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());
}
#[test]
fn test_operations() {
let operations = Operations::new();
assert!(operations.is_empty());
let mut operations = operations.operation(PathItemType::Get, Operation::new());
operations.insert(PathItemType::Post, Operation::new());
operations.extend([(PathItemType::Head, Operation::new())]);
assert_eq!(3, operations.len());
}
#[test]
fn test_operations_into_iter() {
let mut operations = Operations::new();
operations.insert(PathItemType::Get, Operation::new());
operations.insert(PathItemType::Post, Operation::new());
operations.insert(PathItemType::Head, Operation::new());
let mut iter = operations.into_iter();
assert_eq!((PathItemType::Get, Operation::new()), iter.next().unwrap());
assert_eq!((PathItemType::Post, Operation::new()), iter.next().unwrap());
assert_eq!((PathItemType::Head, Operation::new()), iter.next().unwrap());
}
#[test]
fn test_operations_then() {
let print_operation = |operation: Operation| {
println!("{:?}", operation);
operation
};
let operation = Operation::new();
operation.then(print_operation);
}
}