tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use serde_json::Value;

use crate::FieldMetadata;

use super::redaction::{is_secret_schema_node, redact_example_value};

pub(super) fn annotate_schema_node(node: &mut Value, field: &FieldMetadata) {
    let Some(object) = node.as_object_mut() else {
        return;
    };
    let is_secret = field.secret || is_secret_schema_node(object);

    if let Some(doc) = &field.doc {
        object.insert("description".to_owned(), Value::String(doc.clone()));
    }
    if let Some(example) = &field.example {
        let value = parse_example_value(example);
        object.insert(
            "example".to_owned(),
            if is_secret {
                redact_example_value(&value)
            } else {
                value
            },
        );
    }
    if let Some(note) = &field.deprecated {
        object.insert("deprecated".to_owned(), Value::Bool(true));
        object.insert(
            "x-tier-deprecated-note".to_owned(),
            Value::String(note.clone()),
        );
    }
    if is_secret {
        object.insert("writeOnly".to_owned(), Value::Bool(true));
        object.insert("x-tier-secret".to_owned(), Value::Bool(true));
    }
    if let Some(env) = &field.env {
        object.insert("x-tier-env".to_owned(), Value::String(env.clone()));
    }
    if let Some(env_decode) = &field.env_decode {
        object.insert(
            "x-tier-env-decode".to_owned(),
            Value::String(env_decode.to_string()),
        );
    }
    if !field.aliases.is_empty() {
        object.insert(
            "x-tier-aliases".to_owned(),
            Value::Array(field.aliases.iter().cloned().map(Value::String).collect()),
        );
    }
    if field.has_default {
        object.insert("x-tier-has-default".to_owned(), Value::Bool(true));
    }
    object.insert(
        "x-tier-merge".to_owned(),
        Value::String(field.merge.to_string()),
    );

    insert_source_policy_annotations(node, field);
    insert_validation_annotations(node, field);
}

fn insert_source_policy_annotations(node: &mut Value, field: &FieldMetadata) {
    let Some(object) = node.as_object_mut() else {
        return;
    };

    let allowed_sources = field.allowed_source_names();
    if !allowed_sources.is_empty() {
        object.insert(
            "x-tier-sources".to_owned(),
            Value::Array(allowed_sources.into_iter().map(Value::String).collect()),
        );
    }
    let denied_sources = field.denied_source_names();
    if !denied_sources.is_empty() {
        object.insert(
            "x-tier-denied-sources".to_owned(),
            Value::Array(denied_sources.into_iter().map(Value::String).collect()),
        );
    }
}

fn insert_validation_annotations(node: &mut Value, field: &FieldMetadata) {
    let Some(object) = node.as_object_mut() else {
        return;
    };

    if !field.validations.is_empty() {
        object.insert(
            "x-tier-validate".to_owned(),
            serde_json::to_value(&field.validations).unwrap_or_else(|_| Value::Array(Vec::new())),
        );
    }
    if let Some(validation_config) = field.validation_config_json() {
        object.insert("x-tier-validation-config".to_owned(), validation_config);
    }
}

fn parse_example_value(example: &str) -> Value {
    serde_json::from_str(example).unwrap_or_else(|_| Value::String(example.to_owned()))
}