1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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);
            merge_indexmap(&mut base.schemas, components.schemas);
            merge_indexmap(&mut base.responses, components.responses);
            merge_indexmap(&mut base.parameters, components.parameters);
            merge_indexmap(&mut base.examples, components.examples);
            merge_indexmap(&mut base.request_bodies, components.request_bodies);
            merge_indexmap(&mut base.headers, components.headers);
            merge_indexmap(&mut base.security_schemes, components.security_schemes);
            merge_indexmap(&mut base.links, components.links);
            merge_indexmap(&mut base.callbacks, components.callbacks);
            merge_indexmap(&mut base.extensions, 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>) {
        self.tags.extend(tags);
    }

    fn merge_extensions(&mut self, extensions: indexmap::IndexMap<String, serde_json::Value>) {
        extensions.into_iter().for_each(|(key, value)| {
            self.extensions.entry(key).or_insert(value);
        })
    }

    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()));
        modify_item(item, method, operation);
    }
}

fn merge_indexmap<T>(
    base: &mut indexmap::IndexMap<String, T>,
    merge: indexmap::IndexMap<String, T>,
) {
    merge.into_iter().for_each(|(key, value)| {
        base.entry(key).or_insert(value);
    });
}

fn modify_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) => modify_path_item(item, method, operation),
    }
}

fn modify_path_item(
    item: &mut openapiv3::PathItem,
    method: &str,
    operation: &openapiv3::Operation,
) {
    let op = match method.to_ascii_lowercase().as_str() {
        "get" => Some(&mut item.get),
        "put" => Some(&mut item.put),
        "post" => Some(&mut item.post),
        "delete" => Some(&mut item.delete),
        "options" => Some(&mut item.options),
        "head" => Some(&mut item.head),
        "patch" => Some(&mut item.patch),
        "trace" => Some(&mut item.trace),
        other => {
            tracing::warn!(method = other, "Skipping unsupported");
            None
        }
    };

    if let Some(op) = op {
        if op.is_none() {
            *op = Some(operation.clone());
        } else {
            tracing::warn!(method, "Cannot replace existing operation");
        }
    }
}