rust_config_tree/config_schema/
adapt.rs1use std::collections::BTreeSet;
4
5use serde_json::Value;
6
7use crate::config::ConfigResult;
8
9use super::{
10 marker::{ENV_ONLY_SCHEMA_EXTENSION, TREE_SPLIT_SCHEMA_EXTENSION},
11 paths::direct_child_split_section_paths,
12 reference::{
13 collect_schema_refs, collect_transitive_schema_refs, resolve_schema_reference,
14 retain_schema_map,
15 },
16};
17
18fn section_schema_for_path(root_schema: &Value, section_path: &[&str]) -> Option<Value> {
35 let mut current = root_schema;
36
37 for section in section_path {
38 current = current.get("properties")?.get(*section)?;
39 current = resolve_schema_reference(root_schema, current).unwrap_or(current);
40 }
41
42 Some(standalone_section_schema(root_schema, current))
43}
44fn standalone_section_schema(root_schema: &Value, section_schema: &Value) -> Value {
62 let mut section_schema = section_schema.clone();
63 let Some(object) = section_schema.as_object_mut() else {
64 return section_schema;
65 };
66
67 if let Some(schema_uri) = root_schema.get("$schema") {
68 object
69 .entry("$schema".to_owned())
70 .or_insert_with(|| schema_uri.clone());
71 }
72
73 if let Some(definitions) = root_schema.get("definitions") {
74 object
75 .entry("definitions".to_owned())
76 .or_insert_with(|| definitions.clone());
77 }
78
79 if let Some(defs) = root_schema.get("$defs") {
80 object
81 .entry("$defs".to_owned())
82 .or_insert_with(|| defs.clone());
83 }
84
85 section_schema
86}
87pub fn schema_for_output_path(
105 full_schema: &Value,
106 section_path: &[&'static str],
107 split_paths: &[Vec<&'static str>],
108) -> ConfigResult<Value> {
109 let mut schema = if section_path.is_empty() {
110 full_schema.clone()
111 } else {
112 section_schema_for_path(full_schema, section_path).ok_or_else(|| {
113 std::io::Error::new(
114 std::io::ErrorKind::InvalidData,
115 format!(
116 "failed to extract JSON Schema for config section {}",
117 section_path.join(".")
118 ),
119 )
120 })?
121 };
122
123 remove_child_section_properties(&mut schema, section_path, split_paths);
126 remove_env_only_properties(&mut schema);
127 remove_empty_object_properties(&mut schema);
128 prune_unused_schema_maps(&mut schema);
129 remove_schema_extensions(&mut schema);
130
131 Ok(schema)
132}
133
134fn remove_child_section_properties(
152 schema: &mut Value,
153 section_path: &[&'static str],
154 split_paths: &[Vec<&'static str>],
155) {
156 let Some(properties) = schema.get_mut("properties").and_then(Value::as_object_mut) else {
157 return;
158 };
159
160 for child_section_path in direct_child_split_section_paths(section_path, split_paths) {
161 if let Some(child_name) = child_section_path.last() {
162 properties.remove(*child_name);
163 }
164 }
165}
166
167pub fn remove_env_only_properties(value: &mut Value) {
183 match value {
184 Value::Object(object) => {
185 if let Some(properties) = object.get_mut("properties").and_then(Value::as_object_mut) {
186 properties.retain(|_, schema| {
187 !schema
188 .get(ENV_ONLY_SCHEMA_EXTENSION)
189 .and_then(Value::as_bool)
190 .unwrap_or(false)
191 });
192
193 for schema in properties.values_mut() {
194 remove_env_only_properties(schema);
195 }
196 }
197
198 for (key, child) in object.iter_mut() {
199 if key != "properties" {
200 remove_env_only_properties(child);
201 }
202 }
203 }
204 Value::Array(items) => {
205 for item in items {
206 remove_env_only_properties(item);
207 }
208 }
209 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
210 }
211}
212
213pub fn remove_empty_object_properties(schema: &mut Value) {
229 loop {
230 let root_schema = schema.clone();
231 if !remove_empty_object_properties_with_root(schema, &root_schema) {
232 break;
233 }
234 }
235}
236
237fn remove_empty_object_properties_with_root(value: &mut Value, root_schema: &Value) -> bool {
254 let mut changed = false;
255
256 match value {
257 Value::Object(object) => {
258 if let Some(properties) = object.get_mut("properties").and_then(Value::as_object_mut) {
259 let before_len = properties.len();
260 properties.retain(|_, schema| !is_empty_object_schema(root_schema, schema));
261 changed |= properties.len() != before_len;
262
263 for schema in properties.values_mut() {
264 changed |= remove_empty_object_properties_with_root(schema, root_schema);
265 }
266 }
267
268 for (key, child) in object.iter_mut() {
269 if key != "properties" {
270 changed |= remove_empty_object_properties_with_root(child, root_schema);
271 }
272 }
273 }
274 Value::Array(items) => {
275 for item in items {
276 changed |= remove_empty_object_properties_with_root(item, root_schema);
277 }
278 }
279 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
280 }
281
282 changed
283}
284
285fn is_empty_object_schema(root_schema: &Value, schema: &Value) -> bool {
302 let schema = resolve_schema_reference(root_schema, schema).unwrap_or(schema);
303 let Some(object) = schema.as_object() else {
304 return false;
305 };
306
307 let is_object = object.get("type").and_then(Value::as_str) == Some("object")
308 || object.contains_key("properties");
309 let has_properties = object
310 .get("properties")
311 .and_then(Value::as_object)
312 .is_some_and(|properties| !properties.is_empty());
313 let has_dynamic_properties =
314 object.contains_key("additionalProperties") || object.contains_key("patternProperties");
315
316 is_object && !has_properties && !has_dynamic_properties
317}
318
319pub fn prune_unused_schema_maps(schema: &mut Value) {
335 let mut definitions = BTreeSet::new();
336 let mut defs = BTreeSet::new();
337
338 collect_schema_refs(schema, false, &mut definitions, &mut defs);
339
340 loop {
341 let previous_len = definitions.len() + defs.len();
342 collect_transitive_schema_refs(schema, &mut definitions, &mut defs);
343
344 if definitions.len() + defs.len() == previous_len {
345 break;
346 }
347 }
348
349 retain_schema_map(schema, "definitions", &definitions);
350 retain_schema_map(schema, "$defs", &defs);
351}
352
353pub fn remove_schema_extensions(value: &mut Value) {
369 match value {
370 Value::Object(object) => {
371 object.remove(TREE_SPLIT_SCHEMA_EXTENSION);
372 object.remove(ENV_ONLY_SCHEMA_EXTENSION);
373
374 for child in object.values_mut() {
375 remove_schema_extensions(child);
376 }
377 }
378 Value::Array(items) => {
379 for item in items {
380 remove_schema_extensions(item);
381 }
382 }
383 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
384 }
385}