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