openapi-merge 0.1.4

OpenAPI Merge library
Documentation
use openapiv3::OpenAPI;
use serde_json as json;

use super::*;

pub trait OpenAPIExt {
    fn merge_components(&mut self, components: Option<openapiv3::Components>);
    fn merge_security(&mut self, security: Option<Vec<openapiv3::SecurityRequirement>>);
    fn merge_tags(&mut self, tags: Vec<openapiv3::Tag>);
    fn merge_extensions(&mut self, extensions: indexmap::IndexMap<String, json::Value>);
    fn merge_operation(&mut self, path: &str, method: &str, operation: &openapiv3::Operation);
}

impl OpenAPIExt for OpenAPI {
    fn merge_components(&mut self, components: Option<openapiv3::Components>) {
        if let Some(components) = components {
            let base = self.components.get_or_insert_with(default);
            base.schemas.merge(components.schemas);
            base.responses.merge(components.responses);
            base.parameters.merge(components.parameters);
            base.examples.merge(components.examples);
            base.request_bodies.merge(components.request_bodies);
            base.headers.merge(components.headers);
            base.security_schemes.merge(components.security_schemes);
            base.links.merge(components.links);
            base.callbacks.merge(components.callbacks);
            base.extensions.merge(components.extensions);
        }
    }

    fn merge_security(&mut self, security: Option<Vec<openapiv3::SecurityRequirement>>) {
        if let Some(security) = security {
            tracing::warn!("Merging {:?} is not supported yet", security);
        }
    }

    fn merge_tags(&mut self, tags: Vec<openapiv3::Tag>) {
        for tag in tags {
            if !self.tags.contains(&tag) {
                self.tags.push(tag)
            }
        }
    }

    fn merge_extensions(&mut self, extensions: indexmap::IndexMap<String, serde_json::Value>) {
        self.extensions.merge(extensions)
    }

    fn merge_operation(&mut self, path: &str, method: &str, operation: &openapiv3::Operation) {
        tracing::info!(path, method, operation.summary, "Merging operation");
        let paths = &mut self.paths.paths;

        if paths.contains_key(path) {
            tracing::warn!(path, "already found in self");
        }
        let item = paths
            .entry(path.into())
            .or_insert_with(|| openapiv3::ReferenceOr::Item(openapiv3::PathItem::default()));
        update_item(item, method, operation);
    }
}

fn update_item(
    item: &mut openapiv3::ReferenceOr<openapiv3::PathItem>,
    method: &str,
    operation: &openapiv3::Operation,
) {
    match item {
        openapiv3::ReferenceOr::Reference { reference } => {
            tracing::warn!(reference, "Cannot modify reference")
        }
        openapiv3::ReferenceOr::Item(item) => update_path_item(item, method, operation),
    }
}

fn update_path_item(
    item: &mut openapiv3::PathItem,
    method: &str,
    operation: &openapiv3::Operation,
) {
    if let Some(op) = item.get_mut(method) {
        if op.is_none() {
            *op = Some(operation.clone());
        } else {
            tracing::warn!(method, "Cannot replace existing operation");
        }
    }
}

trait Method {
    fn get_mut(&mut self, method: &str) -> Option<&mut Option<openapiv3::Operation>>;
}

impl Method for openapiv3::PathItem {
    fn get_mut(&mut self, method: &str) -> Option<&mut Option<openapiv3::Operation>> {
        match method.to_ascii_lowercase().as_str() {
            "get" => Some(&mut self.get),
            "put" => Some(&mut self.put),
            "post" => Some(&mut self.post),
            "delete" => Some(&mut self.delete),
            "options" => Some(&mut self.options),
            "head" => Some(&mut self.head),
            "patch" => Some(&mut self.patch),
            "trace" => Some(&mut self.trace),
            other => {
                tracing::warn!(method = other, "Skipping unsupported");
                None
            }
        }
    }
}

trait Merge {
    fn merge(&mut self, other: Self);
}

impl<T> Merge for indexmap::IndexMap<String, T> {
    fn merge(&mut self, other: Self) {
        other.into_iter().for_each(|(key, value)| {
            self.entry(key).or_insert(value);
        });
    }
}