use crate::*;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OpenAPI {
pub openapi: String,
pub info: Info,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub servers: Vec<Server>,
pub paths: Paths,
#[serde(default, skip_serializing_if = "Components::is_empty")]
pub components: Components,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub security: Vec<SecurityRequirement>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Tag>,
#[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocumentation>,
#[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
pub extensions: IndexMap<String, serde_json::Value>,
}
impl std::ops::Deref for OpenAPI {
type Target = Components;
fn deref(&self) -> &Self::Target {
&self.components
}
}
impl std::ops::DerefMut for OpenAPI {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.components
}
}
impl OpenAPI {
pub fn operations(&self) -> impl Iterator<Item=(&str, &str, &Operation, &PathItem)> {
self.paths
.iter()
.filter_map(|(path, item)| item.as_item().map(|i| (path, i)))
.flat_map(|(path, item)| {
item.iter()
.map(move |(method, op)| (path.as_str(), method, op, item))
})
}
pub fn operations_mut(&mut self) -> impl Iterator<Item=(&str, &str, &mut Operation)> {
self.paths
.iter_mut()
.filter_map(|(path, item)| item.as_mut().map(|i| (path, i)))
.flat_map(|(path, item)| {
item.iter_mut()
.map(move |(method, op)| (path.as_str(), method, op))
})
}
pub fn get_operation_mut(&mut self, operation_id: &str) -> Option<&mut Operation> {
self.operations_mut()
.find(|(_, _, op)| op.operation_id.as_ref().unwrap() == operation_id)
.map(|(_, _, op)| op)
}
pub fn get_operation(&self, operation_id: &str) -> Option<(&Operation, &PathItem)> {
self.operations()
.find(|(_, _, op, _)| op.operation_id.as_ref().unwrap() == operation_id)
.map(|(_, _, op, item)| (op, item))
}
pub fn merge(mut self, other: OpenAPI) -> Result<Self, MergeError> {
merge_map(&mut self.info.extensions, other.info.extensions);
merge_vec(&mut self.servers, other.servers, |a, b| a.url == b.url);
for (path, item) in other.paths {
let item = item.into_item().ok_or_else(|| MergeError::new("PathItem references are not yet supported. Please opena n issue if you need this feature."))?;
if self.paths.paths.contains_key(&path) {
let self_item = self.paths.paths.get_mut(&path).unwrap().as_mut().ok_or_else(|| MergeError::new("PathItem references are not yet supported. Please open an issue if you need this feature."))?;
option_or(&mut self_item.get, item.get);
option_or(&mut self_item.put, item.put);
option_or(&mut self_item.post, item.post);
option_or(&mut self_item.delete, item.delete);
option_or(&mut self_item.options, item.options);
option_or(&mut self_item.head, item.head);
option_or(&mut self_item.patch, item.patch);
option_or(&mut self_item.trace, item.trace);
merge_vec(&mut self_item.servers, item.servers, |a, b| a.url == b.url);
merge_map(&mut self_item.extensions, item.extensions);
if self_item.parameters.len() != item.parameters.len() {
return Err(MergeError(format!("PathItem {} parameters do not have the same length", path)));
}
for (a, b) in self_item.parameters.iter_mut().zip(item.parameters) {
let a = a.as_item().ok_or_else(|| MergeError::new("Parameter references are not yet supported. Please open an issue if you need this feature."))?;
let b = b.as_item().ok_or_else(|| MergeError::new("Parameter references are not yet supported. Please open an issue if you need this feature."))?;
if a.name != b.name {
return Err(MergeError(format!("PathItem {} parameter {} does not have the same name as {}", path, a.name, b.name)));
}
}
} else {
self.paths.paths.insert(path, RefOr::Item(item));
}
}
merge_map(&mut self.components.extensions, other.components.extensions);
merge_map(&mut self.components.schemas, other.components.schemas.into());
merge_map(&mut self.components.responses, other.components.responses.into());
merge_map(&mut self.components.parameters, other.components.parameters.into());
merge_map(&mut self.components.examples, other.components.examples.into());
merge_map(&mut self.components.request_bodies, other.components.request_bodies.into());
merge_map(&mut self.components.headers, other.components.headers.into());
merge_map(&mut self.components.security_schemes, other.components.security_schemes.into());
merge_map(&mut self.components.links, other.components.links.into());
merge_map(&mut self.components.callbacks, other.components.callbacks.into());
merge_vec(&mut self.security, other.security, |a, b| {
if a.len() != b.len() {
return false;
}
a.iter().all(|(a, _)| b.contains_key(a))
});
merge_vec(&mut self.tags, other.tags, |a, b| a.name == b.name);
match self.external_docs.as_mut() {
Some(ext) => {
if let Some(other) = other.external_docs {
merge_map(&mut ext.extensions, other.extensions)
}
}
None => self.external_docs = other.external_docs
}
merge_map(&mut self.extensions, other.extensions);
Ok(self)
}
pub fn merge_overwrite(self, other: OpenAPI) -> Result<Self, MergeError> {
other.merge(self)
}
}
impl Default for OpenAPI {
fn default() -> Self {
OpenAPI {
openapi: "3.0.3".to_string(),
info: default(),
servers: default(),
paths: default(),
components: default(),
security: default(),
tags: default(),
external_docs: default(),
extensions: default(),
}
}
}
fn merge_vec<T>(original: &mut Vec<T>, mut other: Vec<T>, cmp: fn(&T, &T) -> bool) {
other.retain(|o| !original.iter().any(|r| cmp(o, r)));
original.extend(other);
}
fn merge_map<K, V>(original: &mut IndexMap<K, V>, mut other: IndexMap<K, V>) where K: Eq + std::hash::Hash {
other.retain(|k, _| !original.contains_key(k));
original.extend(other);
}
fn option_or<T>(original: &mut Option<T>, other: Option<T>) {
if original.is_none() {
*original = other;
}
}
#[derive(Debug)]
pub struct MergeError(String);
impl MergeError {
pub fn new(msg: &str) -> Self {
MergeError(msg.to_string())
}
}
impl std::error::Error for MergeError {}
impl std::fmt::Display for MergeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_basic() {
let mut a = OpenAPI::default();
a.servers.push(Server {
url: "http://localhost".to_string(),
..Server::default()
});
let mut b = OpenAPI::default();
b.servers.push(Server {
url: "http://localhost".to_string(),
..Server::default()
});
a = a.merge(b).unwrap();
assert_eq!(a.servers.len(), 1);
}
}