datadog_agent_config_overlay_model/
schema_gen.rs1use std::path::Path;
10
11use indexmap::IndexMap;
12use serde_yaml::Value;
13
14pub enum FieldType {
19 String,
20 Bool,
21 Integer,
22 Float,
23 StringList,
24 Unknown,
25}
26
27pub struct FieldInfo {
29 pub value_type: FieldType,
31 pub env_vars: Vec<String>,
34 pub default: Option<String>,
36}
37
38pub fn load_schema(schema_path: &Path) -> IndexMap<String, FieldInfo> {
43 let doc = crate::load_resolved_schema(schema_path).unwrap_or_else(|e| panic!("failed to load schema: {e}"));
44 let properties = doc
45 .get("properties")
46 .and_then(|v| v.as_mapping())
47 .expect("schema root must have a 'properties' mapping");
48
49 let mut entries = Vec::new();
50 collect_entries(properties, &[], &mut entries);
51 entries.sort_by(|a, b| a.0.cmp(&b.0));
52
53 let mut map = IndexMap::new();
54 for (yaml_path, info) in entries {
55 map.insert(yaml_path, info);
56 }
57 map
58}
59
60fn collect_entries(mapping: &serde_yaml::Mapping, path_parts: &[&str], out: &mut Vec<(String, FieldInfo)>) {
61 for (key, value) in mapping {
62 let key_str = match key.as_str() {
63 Some(s) => s,
64 None => continue,
65 };
66
67 let mut parts = path_parts.to_vec();
68 parts.push(key_str);
69
70 let node_type = value.get("node_type").and_then(|v| v.as_str()).unwrap_or("");
73
74 match node_type {
75 "setting" => out.push(parse_setting(&parts, value)),
76 "section" => {
77 if let Some(props) = value.get("properties").and_then(|v| v.as_mapping()) {
78 collect_entries(props, &parts, out);
79 }
80 }
81 _ => {
82 if let Some(props) = value.get("properties").and_then(|v| v.as_mapping()) {
83 collect_entries(props, &parts, out);
84 }
85 }
86 }
87 }
88}
89
90fn parse_setting(path_parts: &[&str], value: &Value) -> (String, FieldInfo) {
91 let yaml_path = path_parts.join(".");
92
93 let has_no_env_tag = value
94 .get("tags")
95 .and_then(|v| v.as_sequence())
96 .map(|tags| tags.iter().any(|t| t.as_str() == Some("no-env")))
97 .unwrap_or(false);
98
99 let env_vars: Vec<String> = if has_no_env_tag {
100 Vec::new()
101 } else {
102 value
103 .get("env_vars")
104 .and_then(|v| v.as_sequence())
105 .map(|seq| seq.iter().filter_map(|v| v.as_str()).map(|s| s.to_string()).collect())
106 .unwrap_or_default()
107 };
108
109 let value_type = parse_value_type(value);
110 let default = value.get("default").and_then(yaml_value_to_json_str);
111
112 (
113 yaml_path,
114 FieldInfo {
115 value_type,
116 env_vars,
117 default,
118 },
119 )
120}
121
122fn parse_value_type(value: &Value) -> FieldType {
123 match value.get("type").and_then(|v| v.as_str()) {
124 Some("string") => FieldType::String,
125 Some("boolean") => FieldType::Bool,
126 Some("integer") => FieldType::Integer,
127 Some("number") => FieldType::Float,
128 Some("array") => {
129 let item_type = value.get("items").and_then(|v| v.get("type")).and_then(|v| v.as_str());
130 if item_type == Some("string") {
131 FieldType::StringList
132 } else {
133 FieldType::Unknown
134 }
135 }
136 _ => FieldType::Unknown,
137 }
138}
139
140fn yaml_value_to_json_str(value: &serde_yaml::Value) -> Option<String> {
141 match value {
142 serde_yaml::Value::Null => None,
143 serde_yaml::Value::Bool(b) => Some(b.to_string()),
144 serde_yaml::Value::Number(n) => Some(n.to_string()),
145 serde_yaml::Value::String(s) => {
146 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
147 Some(format!("\"{}\"", escaped))
148 }
149 serde_yaml::Value::Sequence(seq) => {
150 let items: Option<Vec<String>> = seq.iter().map(yaml_value_to_json_str).collect();
151 items.map(|elems| format!("[{}]", elems.join(",")))
152 }
153 serde_yaml::Value::Mapping(map) if map.is_empty() => Some("{}".to_string()),
154 _ => None,
155 }
156}
157
158pub fn field_type_as_rust(ft: &FieldType) -> &'static str {
160 match ft {
161 FieldType::String | FieldType::Unknown => "ValueType::String",
162 FieldType::Bool => "ValueType::Bool",
163 FieldType::Integer => "ValueType::Integer",
164 FieldType::Float => "ValueType::Float",
165 FieldType::StringList => "ValueType::StringList",
166 }
167}
168
169pub fn is_unknown(ft: &FieldType) -> bool {
171 matches!(ft, FieldType::Unknown)
172}
173
174pub fn escape_str(s: &str) -> String {
176 s.replace('\\', "\\\\").replace('"', "\\\"")
177}
178
179pub fn generate_schema_rs(schema_map: &IndexMap<String, FieldInfo>, dir: &Path) {
184 use std::fmt::Write as _;
185
186 let mut out = String::new();
187 writeln!(
188 out,
189 "// @generated by build.rs from core_schema.yaml + schema_overlay.yaml — DO NOT EDIT"
190 )
191 .unwrap();
192 writeln!(out).unwrap();
193
194 let mut keys: Vec<&str> = schema_map.keys().map(|s| s.as_str()).collect();
195 keys.sort_unstable();
196
197 for yaml_path in &keys {
198 let info = &schema_map[*yaml_path];
199 let const_name = yaml_path_to_const(yaml_path);
200 let vt = field_type_as_rust(&info.value_type);
201
202 if is_unknown(&info.value_type) {
203 writeln!(
204 out,
205 "// TODO: unknown type for '{}' — set value_type_override in the annotation",
206 yaml_path
207 )
208 .unwrap();
209 }
210
211 let env_vars_lit = if info.env_vars.is_empty() {
212 "&[]".to_string()
213 } else {
214 let items: Vec<String> = info.env_vars.iter().map(|e| format!("\"{}\"", escape_str(e))).collect();
215 format!("&[{}]", items.join(", "))
216 };
217
218 let default_lit = match &info.default {
219 Some(d) => format!("Some(\"{}\")", escape_str(d)),
220 None => "None".to_string(),
221 };
222
223 writeln!(out, "pub const {}: SchemaEntry = SchemaEntry {{", const_name).unwrap();
224 writeln!(out, " schema: Schema::Datadog,").unwrap();
225 writeln!(out, " yaml_path: \"{}\",", yaml_path).unwrap();
226 writeln!(out, " env_vars: {},", env_vars_lit).unwrap();
227 writeln!(out, " value_type: {},", vt).unwrap();
228 writeln!(out, " default: {},", default_lit).unwrap();
229 writeln!(out, "}};").unwrap();
230 writeln!(out).unwrap();
231 }
232
233 let path = dir.join("schema.rs");
234 std::fs::write(&path, out).unwrap_or_else(|e| panic!("cannot write {}: {}", path.display(), e));
235}
236
237pub fn yaml_path_to_const(yaml_path: &str) -> String {
240 yaml_path
241 .chars()
242 .map(|c| if c == '.' || c == '-' { '_' } else { c })
243 .collect::<String>()
244 .to_uppercase()
245}