harn-cli 0.10.49

CLI for the Harn programming language — run, test, REPL, format, and lint
Documentation
//! `harn connector-schema-codegen` — generate the Rust normalized-event
//! structs from the canonical Harn schema module.
//!
//! Connector normalized-event schemas are authored once as Harn
//! `type` declarations (`crates/harn-stdlib/src/stdlib/stdlib_event_schemas.harn`,
//! embedded as [`harn_stdlib::CONNECTOR_EVENT_SCHEMAS_SOURCE`]) and the Rust
//! projections are generated from them for tooling that needs native schema
//! types. Runtime ingress keeps package-owned extension payloads instead of
//! re-normalizing them through Rust.
//!
//! It mirrors `pg_codegen`'s read -> parse -> emit -> write/check flow and the
//! `dump-protocol-artifacts` gen + CI-staleness-check workflow. `--check`
//! re-renders and compares against the vendored output without writing, exiting
//! non-zero on drift — wired into CI via `make check-connector-schemas`.

mod emit;

use std::path::Path;

use crate::cli::ConnectorSchemaCodegenArgs;

/// The vendored generated file, relative to the repo root.
const DEFAULT_OUTPUT: &str = "crates/harn-vm/src/triggers/event/schemas_generated.rs";

pub(crate) fn run(args: &ConnectorSchemaCodegenArgs) -> i32 {
    match run_inner(args) {
        Ok(code) => code,
        Err(message) => {
            eprintln!("harn connector-schema-codegen: {message}");
            1
        }
    }
}

fn run_inner(args: &ConnectorSchemaCodegenArgs) -> Result<i32, String> {
    let source = schema_source(args.schema.as_deref())?;
    let header = header_for();
    let rendered = emit::render(&source, &header)?;

    let out = Path::new(args.out.as_deref().unwrap_or(DEFAULT_OUTPUT));

    if args.check {
        return Ok(check_against(out, &rendered));
    }

    harn_vm::atomic_io::atomic_write(out, rendered.as_bytes())
        .map_err(|error| format!("failed to write {}: {error}", out.display()))?;
    eprintln!("wrote {}", out.display());
    Ok(0)
}

/// Read the schema source. Defaults to the embedded canonical copy so the
/// generator does not depend on the current working directory; an explicit
/// `--schema` path overrides it (used by tests and local experiments).
fn schema_source(schema_path: Option<&str>) -> Result<String, String> {
    match schema_path {
        Some(path) => std::fs::read_to_string(path)
            .map_err(|error| format!("failed to read schema module {path}: {error}")),
        None => Ok(harn_stdlib::CONNECTOR_EVENT_SCHEMAS_SOURCE.to_string()),
    }
}

fn check_against(out: &Path, rendered: &str) -> i32 {
    match std::fs::read_to_string(out) {
        Ok(existing) if normalize(&existing) == normalize(rendered) => 0,
        Ok(_) => {
            eprintln!(
                "{} is out of date; regenerate with `make gen-connector-schemas`",
                out.display()
            );
            1
        }
        Err(_) => {
            eprintln!(
                "{} is missing; generate it with `make gen-connector-schemas`",
                out.display()
            );
            1
        }
    }
}

/// Compare ignoring line-ending differences so the check passes on Windows
/// checkouts (mirrors `dump-protocol-artifacts`).
fn normalize(text: &str) -> String {
    text.replace("\r\n", "\n")
}

fn header_for() -> String {
    HEADER.to_string()
}

const HEADER: &str = "\
// DO NOT EDIT — generated by `harn connector-schema-codegen`.
//
// Source of truth: crates/harn-stdlib/src/stdlib/stdlib_event_schemas.harn
// Regenerate with: make gen-connector-schemas
// Verify (CI):     make check-connector-schemas
//
// These structs are generated from canonical Harn `type` declarations so a
// connector's normalized-event JSON matches the Rust struct by construction.
// Runtime ingress preserves package-owned ExtensionProviderPayload values;
// these native types are tooling projections of the Harn schema.

";

#[cfg(test)]
mod tests;