use std::path::{Path, PathBuf};
use confique::meta::{FieldKind, Meta};
use serde_json::Value;
use crate::config::ConfigSchema;
use super::{
marker::{
DEFAULT_TREE_INNER_FIELD, ENV_ONLY_SCHEMA_EXTENSION, TREE_INNER_FIELD_EXTENSION,
TREE_SPLIT_SCHEMA_EXTENSION, TREE_TRANSPARENT_ARRAY_EXTENSION,
},
reference::resolve_schema_reference,
};
pub fn schema_path_for_section(root_schema_path: &Path, section_path: &[&str]) -> PathBuf {
let Some((last, parents)) = section_path.split_last() else {
return root_schema_path.to_path_buf();
};
let mut path = root_schema_path
.parent()
.unwrap_or_else(|| Path::new("."))
.to_path_buf();
for parent in parents {
path.push(*parent);
}
path.push(format!("{}.schema.json", *last));
path
}
pub fn nested_section_paths(meta: &'static Meta) -> Vec<Vec<&'static str>> {
let mut paths = Vec::new();
collect_nested_section_paths(meta, &mut Vec::new(), &mut paths);
paths
}
pub fn split_section_paths<S>(full_schema: &Value) -> Vec<Vec<&'static str>>
where
S: ConfigSchema,
{
nested_section_paths(&S::META)
.into_iter()
.filter(|section_path| section_has_tree_split_marker(full_schema, section_path))
.collect()
}
pub fn transparent_array_section_paths<S>(full_schema: &Value) -> Vec<Vec<&'static str>>
where
S: ConfigSchema,
{
nested_section_paths(&S::META)
.into_iter()
.filter(|section_path| section_has_transparent_array_marker(full_schema, section_path))
.collect()
}
pub fn inner_field_for_section(full_schema: &Value, section_path: &[&str]) -> String {
property_schema_for_path(full_schema, section_path)
.and_then(|schema| schema.get(TREE_INNER_FIELD_EXTENSION))
.and_then(Value::as_str)
.map(str::to_owned)
.unwrap_or_else(|| DEFAULT_TREE_INNER_FIELD.to_string())
}
pub fn section_has_transparent_array_marker(root_schema: &Value, section_path: &[&str]) -> bool {
property_schema_for_path(root_schema, section_path)
.and_then(|schema| schema.get(TREE_TRANSPARENT_ARRAY_EXTENSION))
.and_then(Value::as_bool)
.unwrap_or(false)
}
pub fn env_only_field_paths<S>(full_schema: &Value) -> Vec<Vec<&'static str>>
where
S: ConfigSchema,
{
let mut paths = Vec::new();
collect_env_only_field_paths(&S::META, full_schema, &mut Vec::new(), &mut paths);
paths
}
fn section_has_tree_split_marker(root_schema: &Value, section_path: &[&str]) -> bool {
property_schema_for_path(root_schema, section_path)
.and_then(|schema| schema.get(TREE_SPLIT_SCHEMA_EXTENSION))
.and_then(Value::as_bool)
.unwrap_or(false)
}
fn field_has_env_only_marker(root_schema: &Value, field_path: &[&str]) -> bool {
property_schema_for_path(root_schema, field_path)
.and_then(|schema| schema.get(ENV_ONLY_SCHEMA_EXTENSION))
.and_then(Value::as_bool)
.unwrap_or(false)
}
fn property_schema_for_path<'a>(root_schema: &'a Value, path: &[&str]) -> Option<&'a Value> {
let mut current = root_schema;
for (index, section) in path.iter().enumerate() {
let property = current.get("properties")?.get(*section)?;
if index + 1 == path.len() {
return Some(property);
}
current = resolve_schema_reference(root_schema, property).unwrap_or(property);
}
None
}
fn collect_nested_section_paths(
meta: &'static Meta,
prefix: &mut Vec<&'static str>,
paths: &mut Vec<Vec<&'static str>>,
) {
for field in meta.fields {
if let FieldKind::Nested { meta } = field.kind {
prefix.push(field.name);
paths.push(prefix.clone());
collect_nested_section_paths(meta, prefix, paths);
prefix.pop();
}
}
}
fn collect_env_only_field_paths(
meta: &'static Meta,
root_schema: &Value,
prefix: &mut Vec<&'static str>,
paths: &mut Vec<Vec<&'static str>>,
) {
for field in meta.fields {
prefix.push(field.name);
match field.kind {
FieldKind::Leaf { .. } => {
if field_has_env_only_marker(root_schema, prefix) {
paths.push(prefix.clone());
}
}
FieldKind::Nested { meta } => {
collect_env_only_field_paths(meta, root_schema, prefix, paths);
}
}
prefix.pop();
}
}
pub fn direct_child_split_section_paths(
section_path: &[&'static str],
split_paths: &[Vec<&'static str>],
) -> Vec<Vec<&'static str>> {
split_paths
.iter()
.filter(|path| path.len() == section_path.len() + 1 && path.starts_with(section_path))
.cloned()
.collect()
}