Skip to main content

kobold_json/
lib.rs

1//! # kobold-json -- forensic JSON evidence packets for COBOL record migration
2//!
3//! Export a raw COBOL record + its copybook into a deterministic JSON packet that preserves **both the
4//! semantic value AND the storage truth** (`offset` / `length` / `pic` / `raw_hex` / `encoding` /
5//! `copybook_hash` / `record_hash` / `findings` / `roundtrip`), and import it back to the exact original
6//! bytes. Parsing and re-encoding are **fail-closed**: a value too long for its field, or a non-numeric
7//! value into a numeric field, yields a [`model::Finding`] -- never a silent truncation or coercion.
8//!
9//! ## Non-claim
10//!
11//! **kobold-json is clean-room and zero-libcob.** It links no COBOL runtime, contains no libcob-derived or
12//! GnuCOBOL-derived code, and depends on no `gnucobol-rs*` crate. It is **not a generic JSON library** and
13//! **not a GnuCOBOL 3.2 parity claim.** It answers *"what should a forensic JSON evidence packet for a
14//! COBOL record migration look like?"* Evidence here is the [`COURT_NAMESPACE`] (`KOBOLD.JSON.*`) court.
15//!
16//! ## Courts
17//!
18//! * `KOBOLD.JSON.EXPORT.1` -- [`export::export`]: record + copybook -> audit packet.
19//! * `KOBOLD.JSON.PARSE.1` -- [`parse::parse_into`]: packet + copybook -> reconstructed bytes (fail-closed).
20//! * `KOBOLD.JSON.ROUNDTRIP.1` -- bytes -> Evidence packet -> [`parse::parse_into`] -> identical bytes.
21//! * `KOBOLD.JSON.REDACTION.1` -- [`redact::redact`]: mask / hash / remove named fields.
22//! * `KOBOLD.JSON.DIFF.1` -- [`diff::diff`]: per-path differences between two packets.
23//!
24//! No crate dependencies; SHA-256 is the pure-Rust [`sha256`] module.
25
26#![forbid(unsafe_code)]
27
28pub mod diff;
29pub mod export;
30pub mod json;
31pub mod model;
32pub mod parse;
33pub mod redact;
34pub mod sha256;
35
36/// The court namespace this crate's evidence is filed under.
37pub const COURT_NAMESPACE: &str = "KOBOLD.JSON";
38
39// Re-exports for the common surface.
40pub use diff::{diff, DiffEntry, DiffKind};
41pub use export::{export, Mode};
42pub use json::{parse, serialize, to_string, JsonValue, ParseError, SerializeOptions};
43pub use model::{Copybook, DecodedField, FieldDecl, FieldKind, Finding};
44pub use parse::parse_into;
45pub use redact::{redact, Redaction};
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn end_to_end_roundtrip_and_diff() {
53        // KOBOLD.JSON.ROUNDTRIP.1 end to end through the crate's public surface.
54        let cb = Copybook {
55            record_name: "ACCT".into(),
56            encoding: "ascii".into(),
57            fields: vec![
58                FieldDecl::alnum("HOLDER", "X(6)", 0, 6),
59                FieldDecl::numeric("BAL", "S9(4)V99", 6, 6, 2, true),
60            ],
61        };
62        // BAL = +0123.45 zoned: "012345" with overpunch of last '5' positive -> 'E'
63        let rec = b"ALICE 01234E";
64        let packet = export(&cb, rec, Mode::Evidence);
65        let back = parse_into(&cb, &packet).expect("roundtrip");
66        assert_eq!(&back, rec);
67
68        // A packet diffs cleanly against itself.
69        assert!(diff(&packet, &packet).is_empty());
70        assert_eq!(COURT_NAMESPACE, "KOBOLD.JSON");
71    }
72}