src/openapi/resolve.rs 1.9 K raw
1
use serde_json::Value;
2
3
/// Safety cap on `$ref` chains to survive reference cycles.
4
pub const MAX_DEREF_DEPTH: usize = 16;
5
6
/// Resolve a local reference like `#/components/schemas/Pet` against the document.
7
/// Remote references are not supported and resolve to `None`.
8
pub fn resolve_pointer<'a>(doc: &'a Value, reference: &str) -> Option<&'a Value> {
9
    let ptr = reference.strip_prefix('#')?;
10
    if ptr.is_empty() {
11
        return Some(doc);
12
    }
13
    doc.pointer(ptr)
14
}
15
16
/// Follow `$ref` chains (with a depth cap) and return the concrete node.
17
/// Nodes without a `$ref` are returned unchanged.
18
pub fn deref<'a>(doc: &'a Value, mut node: &'a Value) -> &'a Value {
19
    let mut depth = 0;
20
    while let Some(r) = node.get("$ref").and_then(Value::as_str) {
21
        if depth >= MAX_DEREF_DEPTH {
22
            break;
23
        }
24
        match resolve_pointer(doc, r) {
25
            Some(target) => node = target,
26
            None => break,
27
        }
28
        depth += 1;
29
    }
30
    node
31
}
32
33
#[cfg(test)]
34
mod tests {
35
    use super::*;
36
    use serde_json::json;
37
38
    #[test]
39
    fn derefs_local_ref() {
40
        let doc = json!({
41
            "components": { "schemas": { "Pet": { "type": "object" } } },
42
            "node": { "$ref": "#/components/schemas/Pet" }
43
        });
44
        let node = deref(&doc, &doc["node"]);
45
        assert_eq!(node["type"], "object");
46
    }
47
48
    #[test]
49
    fn survives_ref_cycles() {
50
        let doc = json!({
51
            "components": { "schemas": {
52
                "A": { "$ref": "#/components/schemas/B" },
53
                "B": { "$ref": "#/components/schemas/A" }
54
            } },
55
            "node": { "$ref": "#/components/schemas/A" }
56
        });
57
        // must terminate
58
        let _ = deref(&doc, &doc["node"]);
59
    }
60
61
    #[test]
62
    fn passes_through_concrete_nodes() {
63
        let doc = json!({"node": {"type": "string"}});
64
        let node = deref(&doc, &doc["node"]);
65
        assert_eq!(node["type"], "string");
66
    }
67
}