skiff_cli/openapi/
refs.rs1use serde_json::{Map, Value};
4use std::collections::HashSet;
5
6pub fn resolve_refs(spec: Value) -> Value {
7 let root = spec.clone();
8 resolve_node(spec, &root, &HashSet::new())
9}
10
11fn resolve_node(node: Value, root: &Value, seen: &HashSet<String>) -> Value {
12 match node {
13 Value::Object(map) => {
14 if let Some(Value::String(ref_path)) = map.get("$ref") {
15 if seen.contains(ref_path) {
16 return Value::Object(map);
17 }
18 if let Some(rest) = ref_path.strip_prefix("#/") {
19 let mut target = root;
20 for part in rest.split('/') {
21 match target.get(part) {
22 Some(next) => target = next,
23 None => return Value::Object(map),
24 }
25 }
26 let mut next_seen = seen.clone();
27 next_seen.insert(ref_path.clone());
28 return resolve_node(target.clone(), root, &next_seen);
29 }
30 return Value::Object(map);
31 }
32 let mut out = Map::new();
33 for (k, v) in map {
34 out.insert(k, resolve_node(v, root, seen));
35 }
36 Value::Object(out)
37 }
38 Value::Array(arr) => Value::Array(
39 arr.into_iter()
40 .map(|item| resolve_node(item, root, seen))
41 .collect(),
42 ),
43 other => other,
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use serde_json::json;
51
52 #[test]
53 fn simple_ref() {
54 let spec = json!({
55 "components": {
56 "parameters": {
57 "LimitParam": {
58 "name": "limit",
59 "in": "query",
60 "schema": {"type": "integer"}
61 }
62 }
63 },
64 "paths": {
65 "/pets": {
66 "get": {
67 "parameters": [{"$ref": "#/components/parameters/LimitParam"}]
68 }
69 }
70 }
71 });
72 let resolved = resolve_refs(spec);
73 let params = &resolved["paths"]["/pets"]["get"]["parameters"];
74 assert_eq!(params.as_array().unwrap().len(), 1);
75 assert_eq!(params[0]["name"], "limit");
76 assert!(params[0].get("$ref").is_none());
77 }
78
79 #[test]
80 fn circular_ref_safe() {
81 let spec = json!({
82 "a": {"$ref": "#/b"},
83 "b": {"$ref": "#/a"},
84 });
85 let resolved = resolve_refs(spec);
86 assert!(resolved["a"].get("$ref").is_some() || resolved["b"].get("$ref").is_some());
87 }
88}