gnosis 0.1.0

Gnosis — enterprise knowledge compiler (CLI + library)
Documentation
use crate::providers::util::{attrs, entity, ext_is};
use crate::{
    AnalysisResult, Confidence, ContentReader, Diagnostic, DiagnosticSeverity, KnowledgeRecord,
    ObjectDescriptor, ProtoData, ProviderId, Result, Support, UnderstandingProvider,
    UnderstandingStatus,
};

const MAX_KEYS: usize = 64;
const MAX_DEPTH: usize = 4;

pub struct YamlProvider;

impl UnderstandingProvider for YamlProvider {
    fn id(&self) -> ProviderId {
        ProviderId::new("yaml")
    }

    fn probe(&self, object: &ObjectDescriptor, _proto: &ProtoData) -> Support {
        if ext_is(&object.extension, &["yaml", "yml"]) {
            Support::Full
        } else {
            Support::None
        }
    }

    fn analyze(
        &self,
        object: &ObjectDescriptor,
        _proto: &ProtoData,
        content: &mut dyn ContentReader,
    ) -> Result<AnalysisResult> {
        let mut buf = Vec::new();
        content.read_to_end(&mut buf)?;
        let provider = self.id();
        let path = &object.relative_path;
        let mut diagnostics = Vec::new();

        let value: serde_yaml::Value = match serde_yaml::from_slice(&buf) {
            Ok(v) => v,
            Err(e) => {
                diagnostics.push(Diagnostic {
                    severity: DiagnosticSeverity::Error,
                    message: format!("yaml parse error: {e}"),
                });
                return Ok(failed_doc(object, &provider, "yaml", diagnostics));
            }
        };

        let mut keys = Vec::new();
        collect_yaml_keys(&value, "", 0, &mut keys);

        let name = path
            .file_stem()
            .map(|s| s.to_string_lossy().into_owned())
            .unwrap_or_else(|| "yaml".into());

        let mut attr = attrs(&[("format", "yaml"), ("key_count", &keys.len().to_string())]);
        if let serde_yaml::Value::Mapping(map) = &value {
            let top: Vec<String> = map
                .keys()
                .filter_map(|k| k.as_str().map(|s| s.to_string()))
                .take(32)
                .collect();
            attr.insert("top_level_keys".into(), top.join(","));
        }

        let mut entities = vec![entity(
            "document",
            &name,
            path,
            &object.id,
            &provider,
            1,
            attr,
            Confidence::High,
        )];
        for key in keys.into_iter().take(MAX_KEYS) {
            entities.push(entity(
                "key_path",
                &key,
                path,
                &object.id,
                &provider,
                1,
                attrs(&[("path", &key)]),
                Confidence::Medium,
            ));
        }

        Ok(AnalysisResult {
            record: KnowledgeRecord {
                entities,
                relationships: Vec::new(),
                diagnostics,
                status: Some(UnderstandingStatus::Understood),
                classification_reason: Some("yaml structure extracted".into()),
            },
            status: UnderstandingStatus::Understood,
            classification_reason: Some("yaml structure extracted".into()),
        })
    }
}

fn collect_yaml_keys(value: &serde_yaml::Value, prefix: &str, depth: usize, out: &mut Vec<String>) {
    if depth > MAX_DEPTH || out.len() >= MAX_KEYS {
        return;
    }
    match value {
        serde_yaml::Value::Mapping(map) => {
            for (k, v) in map {
                let key = k.as_str().unwrap_or("?").to_string();
                let path = if prefix.is_empty() {
                    key
                } else {
                    format!("{prefix}.{key}")
                };
                out.push(path.clone());
                collect_yaml_keys(v, &path, depth + 1, out);
            }
        }
        serde_yaml::Value::Sequence(seq) => {
            if let Some(first) = seq.first() {
                let path = format!("{prefix}[]");
                out.push(path.clone());
                collect_yaml_keys(first, &path, depth + 1, out);
            }
        }
        _ => {}
    }
}

fn failed_doc(
    object: &ObjectDescriptor,
    provider: &ProviderId,
    format: &str,
    diagnostics: Vec<Diagnostic>,
) -> AnalysisResult {
    let path = &object.relative_path;
    let doc = entity(
        "document",
        &path.display().to_string(),
        path,
        &object.id,
        provider,
        1,
        attrs(&[("format", format), ("valid", "false")]),
        Confidence::Low,
    );
    AnalysisResult {
        record: KnowledgeRecord {
            entities: vec![doc],
            relationships: Vec::new(),
            diagnostics,
            status: Some(UnderstandingStatus::Failed),
            classification_reason: Some(format!("{format} parse failed")),
        },
        status: UnderstandingStatus::Failed,
        classification_reason: Some(format!("{format} parse failed")),
    }
}