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)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub servers: Vec<Server>,
pub paths: Paths,
#[serde(skip_serializing_if = "Option::is_none")]
pub components: Option<Components>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub security: Option<Vec<SecurityRequirement>>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Tag>,
#[serde(rename = "externalDocs")]
#[serde(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 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 components_mut(&mut self) -> &mut Components {
self.components.as_mut().unwrap()
}
pub fn schemas_mut(&mut self) -> &mut IndexMap<String, ReferenceOr<Schema>> {
&mut self.components
.as_mut()
.unwrap()
.schemas
}
pub fn schemas(&self) -> &IndexMap<String, ReferenceOr<Schema>> {
&self.components
.as_ref()
.unwrap()
.schemas
}
pub fn clean(&mut self) {
for (_c, schema) in self.schemas_mut() {
let ReferenceOr::Item(schema) = schema else {
continue;
};
match &mut schema.schema_kind {
SchemaKind::Type(Type::String(StringType {
enumeration,
..
})) => {
enumeration.sort();
}
SchemaKind::OneOf { .. } => {}
SchemaKind::AllOf { .. } => {}
SchemaKind::AnyOf { .. } => {}
SchemaKind::Not { .. } => {}
SchemaKind::Any(_) => {}
_ => {}
}
}
}
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."))?;
let a = a.parameter_data_ref();
let b = b.parameter_data_ref();
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, ReferenceOr::Item(item));
}
}
if self.components.is_none() {
self.components = other.components
} else if let (Some(self_components), Some(other_components)) = (&mut self.components, other.components) {
merge_map(&mut self_components.extensions, other_components.extensions);
merge_map(&mut self_components.schemas, other_components.schemas);
merge_map(&mut self_components.responses, other_components.responses);
merge_map(&mut self_components.parameters, other_components.parameters);
merge_map(&mut self_components.examples, other_components.examples);
merge_map(&mut self_components.request_bodies, other_components.request_bodies);
merge_map(&mut self_components.headers, other_components.headers);
merge_map(&mut self_components.security_schemes, other_components.security_schemes);
merge_map(&mut self_components.links, other_components.links);
merge_map(&mut self_components.callbacks, other_components.callbacks);
}
if self.security.is_none() {
self.security = other.security;
} else if let (Some(self_security), Some(other_security)) = (&mut self.security, other.security) {
merge_vec(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)
}
pub fn add_schema(&mut self, name: &str, schema: Schema) {
self.schemas_mut().insert(name.to_string(), ReferenceOr::Item(schema));
}
}
impl Default for OpenAPI {
fn default() -> Self {
OpenAPI {
openapi: "3.0.3".to_string(),
info: Default::default(),
servers: vec![],
paths: Default::default(),
components: Some(Default::default()),
security: None,
tags: vec![],
external_docs: None,
extensions: Default::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);
}
}