golem-openapi-client-generator 0.0.17

OpenAPI glient generator for golem
Documentation
use indexmap::IndexMap;
use openapiv3::{Components, ExternalDocumentation, OpenAPI, Paths};

use crate::Error;
use crate::Result;

pub fn merge_all_openapi_specs(openapi_specs: Vec<OpenAPI>) -> Result<OpenAPI> {
    if openapi_specs.is_empty() {
        Err(Error::unexpected("No OpenAPI specs provided"))
    } else if openapi_specs.len() == 1 {
        Ok(openapi_specs.into_iter().next().unwrap())
    } else {
        let mut openapi_specs = openapi_specs;
        let first = openapi_specs.pop().unwrap();
        let rest = openapi_specs;
        rest.into_iter().try_fold(first, merge_openapi_specs)
    }
}

fn merge_openapi_specs(a: OpenAPI, b: OpenAPI) -> Result<OpenAPI> {
    let openapi_version = {
        if a.openapi != b.openapi {
            return Err(Error::unexpected("OpenAPI versions do not match"));
        }
        a.openapi
    };

    let info = {
        if a.info != b.info {
            return Err(Error::unexpected("Info objects do not match"));
        }
        a.info
    };

    let servers = {
        if a.servers != b.servers {
            return Err(Error::unexpected("Servers do not match"));
        }
        a.servers
    };

    let all_tags = {
        let a_tags_map = a
            .tags
            .into_iter()
            .map(|tag| (tag.name.clone(), tag))
            .collect::<IndexMap<_, _>>();
        let b_tags_map = b
            .tags
            .into_iter()
            .map(|tag| (tag.name.clone(), tag))
            .collect::<IndexMap<_, _>>();
        let merged = merge_unique(a_tags_map, b_tags_map)?;

        merged.into_values().collect::<Vec<_>>()
    };

    let all_paths = {
        let Paths {
            paths: a_paths,
            extensions: a_extensions,
        } = a.paths;
        let Paths {
            paths: b_paths,
            extensions: b_extensions,
        } = b.paths;
        let all_paths = merge_unique(a_paths, b_paths)?;
        let all_extensions = merge_unique(a_extensions, b_extensions)?;
        Paths {
            paths: all_paths,
            extensions: all_extensions,
        }
    };

    let components = merge_components(a.components, b.components)?;
    let security = merge_unique_option_list(a.security, b.security);
    let extensions = merge_unique(a.extensions, b.extensions)?;

    let external_docs = merge_external_docs(a.external_docs, b.external_docs)?;

    let result = OpenAPI {
        openapi: openapi_version,
        info,
        servers,
        paths: all_paths,
        components,
        security,
        tags: all_tags,
        extensions,
        external_docs,
    };

    Ok(result)
}

fn merge_components(a: Option<Components>, b: Option<Components>) -> Result<Option<Components>> {
    let result = match (a, b) {
        (Some(a), Some(b)) => {
            let Components {
                schemas: a_schemas,
                responses: a_responses,
                parameters: a_parameters,
                examples: a_examples,
                request_bodies: a_request_bodies,
                headers: a_headers,
                security_schemes: a_security_schemes,
                links: a_links,
                callbacks: a_callbacks,
                extensions: a_extensions,
            } = a;

            let Components {
                schemas: b_schemas,
                responses: b_responses,
                parameters: b_parameters,
                examples: b_examples,
                request_bodies: b_request_bodies,
                headers: b_headers,
                security_schemes: b_security_schemes,
                links: b_links,
                callbacks: b_callbacks,
                extensions: b_extensions,
            } = b;

            let merged = Components {
                schemas: merge_unique(a_schemas, b_schemas)?,
                responses: merge_unique(a_responses, b_responses)?,
                parameters: merge_unique(a_parameters, b_parameters)?,
                examples: merge_unique(a_examples, b_examples)?,
                request_bodies: merge_unique(a_request_bodies, b_request_bodies)?,
                headers: merge_unique(a_headers, b_headers)?,
                security_schemes: merge_unique(a_security_schemes, b_security_schemes)?,
                links: merge_unique(a_links, b_links)?,
                callbacks: merge_unique(a_callbacks, b_callbacks)?,
                extensions: merge_unique(a_extensions, b_extensions)?,
            };
            Some(merged)
        }
        (Some(a), None) => Some(a),
        (None, Some(b)) => Some(b),
        (None, None) => None,
    };

    Ok(result)
}

fn merge_external_docs(
    a: Option<ExternalDocumentation>,
    b: Option<ExternalDocumentation>,
) -> crate::Result<Option<ExternalDocumentation>> {
    let result = match (a, b) {
        (Some(a), Some(b)) => {
            let ExternalDocumentation {
                description: a_description,
                url: a_url,
                extensions: a_extensions,
            } = a;

            let ExternalDocumentation {
                description: b_description,
                url: b_url,
                extensions: b_extensions,
            } = b;

            let description = match (a_description, b_description) {
                (Some(a), Some(b)) => {
                    if a != b {
                        return Err(Error::unexpected(
                            "External documentation descriptions do not match",
                        ));
                    }
                    Some(a)
                }
                (Some(a), None) => Some(a),
                (None, Some(b)) => Some(b),
                (None, None) => None,
            };

            let url = {
                if a_url != b_url {
                    return Err(Error::unexpected(
                        "External documentation URLs do not match",
                    ));
                }
                a_url
            };

            let extensions = merge_unique(a_extensions, b_extensions)?;

            Some(ExternalDocumentation {
                description,
                url,
                extensions,
            })
        }
        (Some(a), None) => Some(a),
        (None, Some(b)) => Some(b),
        (None, None) => None,
    };
    Ok(result)
}

fn merge_unique_option_list<Key, Item>(
    a: Option<Vec<IndexMap<Key, Item>>>,
    b: Option<Vec<IndexMap<Key, Item>>>,
) -> Option<Vec<IndexMap<Key, Item>>> {
    match (a, b) {
        (Some(a), Some(mut b)) => {
            let mut result = a;
            result.append(&mut b);
            Some(result)
        }
        (Some(a), None) => Some(a),
        (None, Some(b)) => Some(b),
        (None, None) => None,
    }
}

fn merge_unique<Key, Item>(
    mut a: IndexMap<Key, Item>,
    b: IndexMap<Key, Item>,
) -> Result<IndexMap<Key, Item>>
where
    Key: std::fmt::Debug + Eq + std::hash::Hash,
    Item: std::fmt::Debug + PartialEq,
{
    for (key, value) in b {
        match a.entry(key) {
            indexmap::map::Entry::Occupied(entry) => {
                if entry.get() != &value {
                    return Err(Error::unexpected(format!(
                        "Duplicate key {:?} with different values \n Current {:?} \n New {:?}",
                        entry.key(),
                        entry.get(),
                        value
                    )));
                }
            }
            indexmap::map::Entry::Vacant(entry) => {
                entry.insert(value);
            }
        }
    }
    Ok(a)
}