kyyn-core 0.1.13

Core vocabulary for kyyn: registry, links, query AST, plugin and validation contracts for typed, git-backed knowledge bases.
Documentation
//! Registry-construction helpers for schema crates — the machinery every
//! KB's `registry.rs` used to carry as an identical copy. A schema crate
//! declares its kinds with these; everything that can drift silently is
//! pulled from the compiled types instead of retyped: docs and enum
//! variants come out of the schemars output (i.e. straight from the doc
//! comments and derives on the model), so the same comment documents the
//! type in Rust AND guides the agent through the generated tools.

use serde_json::Value;

use crate::registry::{Field, FieldType, Tone, Variant};

/// The schemars JSON of a model type — the source `kind_doc`, `doc_of` and
/// `enum_variants` mine.
pub fn json_of<T: schemars::JsonSchema>() -> Value {
    serde_json::to_value(schemars::schema_for!(T)).expect("schema serializes")
}

/// The type's own doc comment — a kind's curation intent.
pub fn kind_doc(schema: &Value) -> String {
    schema["description"]
        .as_str()
        .unwrap_or_default()
        .to_string()
}

/// A field's doc comment, via its property's description.
pub fn doc_of(schema: &Value, name: &str) -> String {
    schema["properties"][name]["description"]
        .as_str()
        .unwrap_or_default()
        .to_string()
}

/// A declared field: name + type + optional role, doc pulled from the model.
pub fn field(schema: &Value, name: &str, ty: FieldType, role: Option<&str>) -> Field {
    Field {
        name: name.into(),
        doc: doc_of(schema, name),
        ty,
        role: role.map(str::to_string),
        refers_to: None,
    }
}

pub fn enum_ty(schema: &Value, name: &str) -> FieldType {
    FieldType::Enum(enum_variants(schema, name))
}

pub fn link(allowed: &[&str]) -> FieldType {
    FieldType::Link {
        allowed: Some(allowed.iter().map(|s| s.to_string()).collect()),
    }
}

pub fn any_link() -> FieldType {
    FieldType::Link { allowed: None }
}

pub fn opt(ty: FieldType) -> FieldType {
    FieldType::Option(Box::new(ty))
}

pub fn list(ty: FieldType) -> FieldType {
    FieldType::List(Box::new(ty))
}

/// Attach a KB's tone mapping to extracted variants — every variant must be
/// mapped (a new enum variant without a tone decision fails loudly here).
pub fn toned(variants: Vec<Variant>, tones: &[(&str, Tone)]) -> Vec<Variant> {
    variants
        .into_iter()
        .map(|mut v| {
            let (_, tone) = tones
                .iter()
                .find(|(n, _)| *n == v.name)
                .unwrap_or_else(|| panic!("variant '{}' has no tone mapping", v.name));
            v.tone = Some(*tone);
            v
        })
        .collect()
}

/// The variants of an enum-typed field, found wherever schemars put them:
/// a plain `enum` array (undocumented unit variants), a `oneOf` of `const`
/// entries (documented unit variants), inline, behind `anyOf` for
/// `Option<...>`, or in `$defs`. Payload-carrying variants are declared by
/// hand at the field site.
pub fn enum_variants(schema: &Value, name: &str) -> Vec<Variant> {
    let prop = &schema["properties"][name];
    find_variants(prop, schema)
        .unwrap_or_else(|| panic!("field '{name}' has no enum variants in its schema"))
}

fn unit(name: &str, doc: &str) -> Variant {
    Variant {
        name: name.into(),
        doc: doc.into(),
        fields: Vec::new(),
        tone: None,
    }
}

fn find_variants(v: &Value, root: &Value) -> Option<Vec<Variant>> {
    match v {
        Value::Object(map) => {
            if let Some(Value::Array(vals)) = map.get("enum") {
                return Some(
                    vals.iter()
                        .filter_map(|x| x.as_str().map(|n| unit(n, "")))
                        .collect(),
                );
            }
            if let Some(Value::Array(alts)) = map.get("oneOf") {
                let mut variants: Vec<Variant> = Vec::new();
                for alt in alts {
                    if let Some(name) = alt["const"].as_str() {
                        variants.push(unit(name, alt["description"].as_str().unwrap_or_default()));
                    } else if let Some(Value::Array(vals)) = alt.get("enum") {
                        // schemars groups UNdocumented unit variants into one
                        // enum entry beside the documented const entries.
                        variants
                            .extend(vals.iter().filter_map(|x| x.as_str().map(|n| unit(n, ""))));
                    }
                }
                if !variants.is_empty() {
                    return Some(variants);
                }
            }
            if let Some(Value::String(r)) = map.get("$ref")
                && let Some(def) = r.strip_prefix("#/$defs/")
            {
                return find_variants(&root["$defs"][def], root);
            }
            map.values().find_map(|x| find_variants(x, root))
        }
        Value::Array(items) => items.iter().find_map(|x| find_variants(x, root)),
        _ => None,
    }
}