scarb_manifest_schema/
lib.rs1use anyhow::{Result, anyhow};
7use serde_json::Value;
8use std::sync::OnceLock;
9
10static GLOBAL_TRAVERSER: OnceLock<SchemaTraverser> = OnceLock::new();
11
12pub const SCARB_SCHEMA_JSON: &str = include_str!("../schema.json");
13
14pub fn get_shared_traverser() -> &'static SchemaTraverser {
16 GLOBAL_TRAVERSER.get_or_init(|| {
17 let manifest_schema = get_manifest_schema();
18 SchemaTraverser::new(manifest_schema)
19 })
20}
21
22pub fn get_manifest_schema() -> Value {
24 serde_json::from_str(SCARB_SCHEMA_JSON).expect("Failed to serialize Manifest schema")
25}
26
27pub struct SchemaTraverser {
29 root: Value,
30}
31
32impl SchemaTraverser {
33 pub fn new(schema: Value) -> Self {
35 Self { root: schema }
36 }
37
38 pub fn traverse<I, S>(&self, path: I) -> Result<&Value>
40 where
41 I: IntoIterator<Item = S>,
42 S: AsRef<str>,
43 {
44 let path_vec: Vec<String> = path.into_iter().map(|s| s.as_ref().to_owned()).collect();
45
46 let mut current = &self.root;
47 let mut iter = path_vec.iter().map(String::as_str).peekable();
48
49 while let Some(key) = iter.next() {
50 current = self.resolve_node(current)?;
51
52 let properties = current.get("properties").and_then(|v| v.as_object());
53
54 if let Some(props) = properties {
55 current = props.get(key).ok_or_else(|| {
56 anyhow!(
57 "Couldn't resolve '{}' at key '{}'.",
58 path_vec.join("."),
59 key
60 )
61 })?;
62 } else if iter.peek().is_none() {
63 return Ok(current);
66 } else {
67 return Err(anyhow!(
68 "Couldn't resolve '{}' at key '{}'.",
69 path_vec.join("."),
70 key
71 ));
72 }
73 }
74 Ok(current)
75 }
76
77 fn resolve_node<'a>(&'a self, node: &'a Value) -> Result<&'a Value> {
79 if let Some(ref_path) = node.get("$ref").and_then(|r| r.as_str()) {
80 return self.resolve_ref(ref_path);
81 }
82
83 if let Some(any_of) = node.get("anyOf").and_then(|a| a.as_array()) {
84 for option in any_of {
85 if option.get("properties").is_some() || option.get("$ref").is_some() {
86 return self.resolve_node(option);
87 }
88 }
89 }
90
91 Ok(node)
92 }
93
94 fn resolve_ref(&self, ref_path: &str) -> Result<&Value> {
96 let parts: Vec<&str> = ref_path.split('/').collect();
100 if parts[0] == "#" && parts[1] == "$defs" {
101 return self
102 .root
103 .get("$defs")
104 .and_then(|d| d.get(parts[2]))
105 .ok_or_else(|| anyhow!("Definition {} not found", ref_path));
106 }
107 Err(anyhow!("Unsupported ref format: {}", ref_path))
108 }
109}