bbox_core/
api.rs

1use crate::ogcapi::*;
2
3#[derive(Clone, Default)]
4pub struct OgcApiInventory {
5    pub landing_page_links: Vec<ApiLink>,
6    pub conformance_classes: Vec<String>,
7    pub collections: Vec<CoreCollection>,
8}
9
10/// OpenAPi doc collection
11#[derive(Default, Clone)]
12pub struct OpenApiDoc(serde_yaml::Value);
13
14impl OpenApiDoc {
15    pub fn new() -> Self {
16        Self::from_yaml("{}", "")
17    }
18    pub fn from_yaml(yaml: &str, _prefix: &str) -> Self {
19        OpenApiDoc(serde_yaml::from_str(yaml).unwrap())
20    }
21    pub fn is_empty(&self) -> bool {
22        self.0 == Self::new().0
23    }
24    /// Merge `paths` and `components` of new yaml into exisiting yaml
25    pub fn extend(&mut self, yaml: &str, _prefix: &str) {
26        let rhs_yaml = serde_yaml::from_str(yaml).unwrap();
27        merge_level(&mut self.0, &rhs_yaml, "paths");
28        if let Some(rhs_components) = rhs_yaml.get("components") {
29            if let Some(components) = self.0.get_mut("components") {
30                // merge 1st level children ("parameters", "responses", "schemas")
31                for (key, _val) in rhs_components.as_mapping().unwrap().iter() {
32                    merge_level(components, rhs_components, key.as_str().unwrap());
33                }
34            } else {
35                self.0
36                    .as_mapping_mut()
37                    .unwrap()
38                    .insert("components".into(), rhs_components.clone());
39            }
40        }
41    }
42    /// Set url of first server entry
43    pub fn set_server_url(&mut self, url: &str) {
44        if let Some(servers) = self.0.get_mut("servers") {
45            if let Some(server) = servers.get_mut(0) {
46                if let Some(server) = server.as_mapping_mut() {
47                    server[&"url".to_string().into()] = url.to_string().into();
48                }
49            }
50        }
51    }
52    pub fn as_yaml(&self, public_server_url: &str) -> String {
53        let mut doc = self.clone();
54        doc.set_server_url(public_server_url);
55        serde_yaml::to_string(&doc.0).unwrap()
56    }
57    pub fn as_json(&self, public_server_url: &str) -> serde_json::Value {
58        let mut doc = self.clone();
59        doc.set_server_url(public_server_url);
60        serde_yaml::from_value::<serde_json::Value>(doc.0).unwrap()
61    }
62}
63
64fn merge_level(yaml: &mut serde_yaml::Value, rhs_yaml: &serde_yaml::Value, key: &str) {
65    if let Some(rhs_elem) = rhs_yaml.get(key) {
66        if let Some(elem) = yaml.get_mut(key) {
67            elem.as_mapping_mut()
68                .unwrap()
69                .extend(rhs_elem.as_mapping().unwrap().clone());
70        } else {
71            yaml.as_mapping_mut()
72                .unwrap()
73                .insert(key.into(), rhs_elem.clone());
74        }
75    }
76}
77
78#[cfg(test)]
79mod test {
80    use super::*;
81
82    const YAML_BASE: &str = r##"---
83openapi: 3.0.2
84info:
85  title: BBOX OGC API
86servers:
87  - url: "http://bbox:8080/"
88    description: Production server
89paths:
90  /conformance:
91    get:
92      operationId: getConformance
93      responses:
94        "333":
95          $ref: "#/components/schemas/confClasses"
96components:
97  schemas:
98    confClasses:
99      type: object
100      required:
101        - conformsTo
102      properties:
103        conformsTo:
104          type: array
105          items:
106            type: string
107"##;
108
109    #[test]
110    fn yaml_empty() {
111        let doc = OpenApiDoc::new();
112        assert!(doc.is_empty());
113    }
114
115    #[test]
116    fn yaml_extend_headers() {
117        let mut doc = OpenApiDoc::from_yaml(YAML_BASE, "");
118        assert_eq!(doc.as_yaml("http://bbox:8080/"), YAML_BASE);
119
120        let yaml = r##"---
121openapi: 3.0.0
122info:
123  title: New Title
124"##;
125        doc.extend(yaml, "");
126        assert_eq!(doc.as_yaml("http://bbox:8080/"), YAML_BASE);
127    }
128
129    #[test]
130    fn yaml_add_path() {
131        let yaml = r#"---
132paths:
133  /newpath: ""
134"#;
135        let yamlout = r##"---
136openapi: 3.0.2
137info:
138  title: BBOX OGC API
139servers:
140  - url: "http://bbox:8080/"
141    description: Production server
142paths:
143  /conformance:
144    get:
145      operationId: getConformance
146      responses:
147        "333":
148          $ref: "#/components/schemas/confClasses"
149  /newpath: ""
150components:
151  schemas:
152    confClasses:
153      type: object
154      required:
155        - conformsTo
156      properties:
157        conformsTo:
158          type: array
159          items:
160            type: string
161"##;
162        let mut doc = OpenApiDoc::from_yaml(YAML_BASE, "");
163        doc.extend(yaml, "");
164        assert_eq!(doc.as_yaml("http://bbox:8080/"), yamlout);
165    }
166
167    #[test]
168    fn yaml_update_path() {
169        let yaml = r##"---
170paths:
171  /conformance:
172    get:
173      operationId: getConformance
174      responses:
175        "333":
176          $ref: "#/components/schemas/confClasses"
177"##;
178        let mut doc = OpenApiDoc::from_yaml(YAML_BASE, "");
179        doc.extend(yaml, "");
180        assert_eq!(doc.as_yaml("http://bbox:8080/"), YAML_BASE);
181    }
182
183    #[test]
184    fn yaml_change_path() {
185        let yaml = r##"---
186paths:
187  /conformance:
188    post:
189      operationId: postConformance
190      responses:
191        "333":
192          $ref: "#/components/schemas/confClasses"
193"##;
194        let yamlout = r##"---
195openapi: 3.0.2
196info:
197  title: BBOX OGC API
198servers:
199  - url: "http://bbox:8080/"
200    description: Production server
201paths:
202  /conformance:
203    post:
204      operationId: postConformance
205      responses:
206        "333":
207          $ref: "#/components/schemas/confClasses"
208components:
209  schemas:
210    confClasses:
211      type: object
212      required:
213        - conformsTo
214      properties:
215        conformsTo:
216          type: array
217          items:
218            type: string
219"##;
220        let mut doc = OpenApiDoc::from_yaml(YAML_BASE, "");
221        doc.extend(yaml, "");
222        assert_eq!(doc.as_yaml("http://bbox:8080/"), yamlout);
223    }
224
225    #[test]
226    fn yaml_add_component() {
227        let yaml = r##"---
228components:
229  schemas:
230    link:
231      type: object
232    confClasses:
233      type: object
234      required:
235        - conformsTo
236      properties:
237        conformsTo:
238          type: array
239          items:
240            type: string
241"##;
242        let yamlout = r##"---
243openapi: 3.0.2
244info:
245  title: BBOX OGC API
246servers:
247  - url: "http://bbox:8080/"
248    description: Production server
249paths:
250  /conformance:
251    get:
252      operationId: getConformance
253      responses:
254        "333":
255          $ref: "#/components/schemas/confClasses"
256components:
257  schemas:
258    confClasses:
259      type: object
260      required:
261        - conformsTo
262      properties:
263        conformsTo:
264          type: array
265          items:
266            type: string
267    link:
268      type: object
269"##;
270        let mut doc = OpenApiDoc::from_yaml(YAML_BASE, "");
271        doc.extend(yaml, "");
272        assert_eq!(doc.as_yaml("http://bbox:8080/"), yamlout);
273    }
274
275    #[test]
276    fn yaml_add_new_component() {
277        let yaml = r##"---
278components:
279  responses:
280    NotFound:
281      description: The requested resource does not exist.
282"##;
283        let yamlout = r##"---
284openapi: 3.0.2
285info:
286  title: BBOX OGC API
287servers:
288  - url: "http://bbox:8080/"
289    description: Production server
290paths:
291  /conformance:
292    get:
293      operationId: getConformance
294      responses:
295        "333":
296          $ref: "#/components/schemas/confClasses"
297components:
298  schemas:
299    confClasses:
300      type: object
301      required:
302        - conformsTo
303      properties:
304        conformsTo:
305          type: array
306          items:
307            type: string
308  responses:
309    NotFound:
310      description: The requested resource does not exist.
311"##;
312        let mut doc = OpenApiDoc::from_yaml(YAML_BASE, "");
313        doc.extend(yaml, "");
314        assert_eq!(doc.as_yaml("http://bbox:8080/"), yamlout);
315    }
316}