Skip to main content

kyyn_core/
protocol.rs

1//! The engine ⇄ schema-crate wire protocol: RON over stdio. The engine spawns
2//! the KB's compiled schema binary, writes one `Request` to stdin, reads one
3//! `Response` from stdout. Nothing else crosses the boundary — the engine
4//! never links a schema crate.
5
6use serde::{Deserialize, Serialize};
7
8use crate::registry::Registry;
9use crate::violation::Violation;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub enum Request {
13    /// Emit the registry (the schema's self-description).
14    Registry,
15    /// Validate a whole tree: (repo-relative path, RON text) pairs, plus the
16    /// link system namespaces the tree's own `sources.ron` declares (the
17    /// engine reads that manifest — schema crates never parse it).
18    Validate {
19        entries: Vec<(String, String)>,
20        systems: Vec<String>,
21    },
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub enum Response {
26    Registry(Registry),
27    Violations(Vec<Violation>),
28    /// The schema binary understood the request but could not serve it.
29    Error(String),
30}
31
32/// The whole main() of a schema binary: one RON [`Request`] on stdin, one
33/// RON [`Response`] on stdout. A schema crate supplies its registry and its
34/// validation fn; everything else about the wire lives here.
35pub fn serve_schema(
36    registry: impl Fn() -> crate::registry::Registry,
37    validate: impl Fn(&[(String, String)], &[&str]) -> Vec<crate::violation::Violation>,
38) {
39    use std::io::Read;
40    let respond = |r: &Response| match crate::ronfmt::to_ron(r) {
41        Ok(text) => print!("{text}"),
42        Err(e) => {
43            eprintln!("serializing response: {e}");
44            std::process::exit(2);
45        }
46    };
47    let mut input = String::new();
48    if let Err(e) = std::io::stdin().read_to_string(&mut input) {
49        respond(&Response::Error(format!("reading stdin: {e}")));
50        std::process::exit(2);
51    }
52    let response = match ron::from_str::<Request>(&input) {
53        Err(e) => Response::Error(format!("parsing request: {e}")),
54        Ok(Request::Registry) => Response::Registry(registry()),
55        Ok(Request::Validate { entries, systems }) => {
56            let systems: Vec<&str> = systems.iter().map(String::as_str).collect();
57            Response::Violations(validate(&entries, &systems))
58        }
59    };
60    respond(&response);
61}