vdev/commands/build/component_docs/
schema.rs1#[path = "schema_core.rs"]
2pub mod core;
3#[path = "schema_enum.rs"]
4pub mod r#enum;
5#[path = "schema_resolve.rs"]
6pub mod resolve;
7#[path = "schema_utils.rs"]
8pub mod utils;
9
10use anyhow::{Result, anyhow};
11use indexmap::IndexMap;
12use serde_json::Value;
13use std::env;
14
15pub struct SchemaContext {
16 pub root_schema: Value,
17 pub cue_binary_path: String,
18 pub resolved_schema_cache: IndexMap<String, Value>,
19 pub expanded_schema_cache: IndexMap<String, Value>,
20}
21
22impl SchemaContext {
23 pub fn new(root_schema: Value) -> Result<Self> {
24 let cue_binary_path = find_command_on_path("cue")
25 .ok_or_else(|| anyhow!("Failed to find 'cue' binary on the current path."))?;
26 Ok(Self {
27 root_schema,
28 cue_binary_path,
29 resolved_schema_cache: IndexMap::new(),
30 expanded_schema_cache: IndexMap::new(),
31 })
32 }
33}
34
35pub fn find_command_on_path(command: &str) -> Option<String> {
36 let exts = env::var("PATHEXT")
37 .unwrap_or_else(|_| String::new())
38 .split(';')
39 .map(std::string::ToString::to_string)
40 .collect::<Vec<String>>();
41
42 let path_var = env::var("PATH").unwrap_or_default();
43 let paths = std::env::split_paths(&path_var);
44
45 for path in paths {
46 for ext in &exts {
47 let mut expected = path.join(command);
48 expected.set_extension(ext.replace('.', ""));
49
50 if expected.is_file() {
51 return expected.to_str().map(std::string::ToString::to_string);
52 }
53 }
54 }
55 None
56}
57
58pub fn json_type_str(value: &Value) -> &'static str {
59 match value {
60 Value::String(_) => "string",
61 Value::Number(n) if n.is_f64() => "number",
62 Value::Number(_) => "integer",
63 Value::Bool(_) => "boolean",
64 Value::Array(_) => "array",
65 Value::Object(_) => "object",
66 Value::Null => "null",
67 }
68}
69
70pub fn docs_type_str(value: &Value) -> &'static str {
71 let type_str = json_type_str(value);
72 if type_str == "boolean" {
73 "bool"
74 } else {
75 type_str
76 }
77}
78
79pub fn get_schema_metadata<'a>(schema: &'a Value, key: &str) -> Option<&'a Value> {
80 schema.get("_metadata").and_then(|m| m.get(key))
81}
82
83pub fn get_schema_ref(schema: &Value) -> Option<&str> {
84 schema.get("$ref").and_then(|r| r.as_str())
85}
86
87pub fn nested_merge(base: &mut Value, override_val: &Value) {
88 if override_val.is_null() {
89 return;
90 }
91
92 if base.is_null() {
93 *base = override_val.clone();
94 return;
95 }
96
97 if base.is_object() && override_val.is_object() {
98 let base_obj = base.as_object_mut().unwrap();
99 let over_obj = override_val.as_object().unwrap();
100 for (k, v) in over_obj {
101 let entry = base_obj.entry(k.clone()).or_insert_with(|| Value::Null);
102 nested_merge(entry, v);
103 }
104 } else if base.is_array() && override_val.is_array() {
105 let base_arr = base.as_array_mut().unwrap();
106 let over_arr = override_val.as_array().unwrap();
107 for val in over_arr {
108 if !base_arr.contains(val) {
109 base_arr.push(val.clone());
110 }
111 }
112 } else {
113 *base = override_val.clone();
114 }
115}
116
117pub fn schema_aware_nested_merge(base: &mut Value, override_val: &Value) {
118 if override_val.is_null() {
119 return;
120 }
121
122 if base.is_null() {
123 *base = override_val.clone();
124 return;
125 }
126
127 if base.is_object() && override_val.is_object() {
128 let base_obj = base.as_object_mut().unwrap();
129 let over_obj = override_val.as_object().unwrap();
130
131 for (k, v) in over_obj {
132 if k == "const"
133 && is_const_variant(v)
134 && is_existing_const_variants(base_obj.get("const"))
135 {
136 let base_vals = std::mem::take(base_obj.get_mut("const").unwrap());
137 let mut result = Vec::new();
138
139 push_const_variants(&mut result, base_vals);
140 push_const_variants(&mut result, v.clone());
141
142 base_obj.insert("const".to_string(), Value::Array(result));
143 } else {
144 let entry = base_obj.entry(k.clone()).or_insert_with(|| Value::Null);
145 schema_aware_nested_merge(entry, v);
146 }
147 }
148 } else if base.is_array() && override_val.is_array() {
149 let base_arr = base.as_array_mut().unwrap();
150 let over_arr = override_val.as_array().unwrap();
151 for val in over_arr {
152 if !base_arr.contains(val) {
153 base_arr.push(val.clone());
154 }
155 }
156 } else {
157 *base = override_val.clone();
158 }
159}
160
161fn is_const_variant(value: &Value) -> bool {
163 match value {
164 Value::Object(o) => o.contains_key("value"),
165 Value::Array(arr) => arr.iter().all(is_const_variant),
166 _ => false,
167 }
168}
169
170fn is_existing_const_variants(existing: Option<&Value>) -> bool {
171 existing.is_some_and(is_const_variant)
172}
173
174fn push_const_variants(result: &mut Vec<Value>, value: Value) {
175 match value {
176 Value::Array(arr) => {
177 for item in arr {
178 if !result.contains(&item) {
179 result.push(item);
180 }
181 }
182 }
183 obj @ Value::Object(_) => {
184 if !result.contains(&obj) {
185 result.push(obj);
186 }
187 }
188 _ => {}
189 }
190}