axgf_rs/lib.rs
1// SPDX-License-Identifier: Apache-2.0
2//! # axgf-rs — Reference implementation of the Axiom Genealogy Format (AXGF) 1.0
3//!
4//! This crate is the canonical Rust implementation of the [AXGF specification].
5//! It provides a **stateless, data-oriented boundary**: every public function
6//! takes JSON strings or bytes and returns a single uniform [`boundary::envelope::Envelope`]
7//! serialized to JSON. No native Rust types cross the boundary — this is what
8//! makes language bindings mechanical.
9//!
10//! ## Design contract (V1)
11//!
12//! 1. **Stateless & immutable.** Every operation takes a bundle in, returns a
13//! new bundle out. No sessions, handles, or hidden mutation.
14//! 2. **Flat JSON is the working form.** The on-disk `.axgf` is a ZIP, but the
15//! library converts it to a single flat JSON object for all editing. ZIP is
16//! read only by [`import_bundle`] and written only by [`export_bundle`].
17//! 3. **No disk, no graph traversal, no query engine, no rendering in V1.**
18//! The caller passes bytes; the library never touches the filesystem.
19//! 4. **Explicit spec-version gating.** Every operation checks `manifest.axgf`
20//! against [`SUPPORTED_SPEC_VERSIONS`] and refuses unknown versions.
21//! 5. **Uniform envelope with stable diagnostic codes.** Validation is
22//! non-blocking: operations may succeed with warnings.
23//! 6. **Forward compatibility.** Unknown fields survive a round-trip untouched.
24//!
25//! [AXGF specification]: https://github.com/plkarin/axgf-spec
26//!
27//! ## Module layout
28//!
29//! - [`model`] — Typed structs for the 8 entity kinds and the manifest.
30//! Internal to the library; never crosses the boundary.
31//! - [`logic`] — Pure value-core: validation, CRUD, deduplication. Operates on
32//! [`model`] types, never on raw JSON.
33//! - [`boundary`] — The only layer that speaks JSON, ZIP and bytes: envelope
34//! type, [`boundary::flat::FlatBundle`], and lifecycle helpers.
35//! - [`convert`] — Foreign-format converters (GEDCOM 5.5.1 → AXGF).
36//! - [`adapters`] — Thin per-target wrappers (rust, wasm, cffi, mobile) behind
37//! feature flags.
38//!
39//! ## Minimal example
40//!
41//! Create an empty bundle, add a person, and validate the result. Every
42//! function takes and returns JSON, wrapped in a uniform
43//! [`boundary::envelope::Envelope`].
44//!
45//! ```
46//! use axgf_rs::{add_entity, create_bundle, validate, EntityKind};
47//! use axgf_rs::boundary::envelope::Status;
48//!
49//! // 1. Create an empty bundle. `data` is a serde_json::Value; convert to a
50//! // string for the next call.
51//! let bundle = create_bundle(Some("Karin")).data.to_string();
52//!
53//! // 2. Add a minimal person. The library generates a UUID v4 if none given
54//! // and fills in `type` and `axgf_version`. The envelope's `data` here
55//! // is `{ "id": <uuid>, "bundle": <updated flat bundle> }`.
56//! let person = r#"{
57//! "identity": {
58//! "name": {"display": "Jean Pierre-Léonard", "components": []},
59//! "gender": {"value": "M"},
60//! "is_living": true
61//! }
62//! }"#;
63//! let added = add_entity(&bundle, EntityKind::Person, person);
64//! assert_eq!(added.status, Status::Ok);
65//!
66//! // 3. Structural + semantic validation over the updated bundle. Warnings
67//! // are non-blocking, so `status == Ok` even if diagnostics are present.
68//! let updated_bundle = added.data["bundle"].to_string();
69//! let checked = validate(&updated_bundle);
70//! assert_eq!(checked.status, Status::Ok);
71//! ```
72//!
73//! ## Command-line binary
74//!
75//! The same core is shipped as a standalone `axgf` executable. The `cli`
76//! Cargo feature is on by default so `cargo install axgf-rs` produces
77//! the binary; each subcommand prints a concise human summary by default
78//! and the raw [`boundary::envelope::Envelope`] under `--json`. See
79//! [`docs/CLI.md`] for the full reference. Library-only consumers can
80//! opt out with `default-features = false, features = ["gedcom"]`.
81//!
82//! ## Further reading
83//!
84//! - [`docs/API.md`] — a longer walk-through of every public function.
85//! - [`docs/CLI.md`] — the `axgf` binary: subcommands, flags, scripting.
86//! - [`SETUP.md`] — build instructions and per-target adapter notes.
87//! - [AXGF specification] — the format itself.
88//!
89//! [`docs/API.md`]: https://github.com/plkarin/axgf-lib/blob/main/docs/API.md
90//! [`docs/CLI.md`]: https://github.com/plkarin/axgf-lib/blob/main/docs/CLI.md
91//! [`SETUP.md`]: https://github.com/plkarin/axgf-lib/blob/main/SETUP.md
92
93#![forbid(unsafe_code)]
94#![deny(missing_docs)]
95
96pub mod adapters;
97pub mod boundary;
98pub mod convert;
99pub mod logic;
100pub mod model;
101
102/// AXGF specification versions this build understands. Every lifecycle
103/// operation verifies `manifest.axgf` against this set and refuses to proceed
104/// on an unrecognized value with a stable `UNSUPPORTED_SPEC_VERSION`
105/// diagnostic.
106pub const SUPPORTED_SPEC_VERSIONS: &[&str] = &["1.0"];
107
108/// The AXGF specification version this build writes when creating or
109/// re-exporting bundles.
110pub const CURRENT_SPEC_VERSION: &str = "1.0";
111
112// -------------------------------------------------------------------------
113// Public API surface
114//
115// Every function on the boundary takes and returns JSON (as `&str` or bytes)
116// and yields an `Envelope` serialized to a JSON string. See individual layer
117// modules for the underlying implementations.
118// -------------------------------------------------------------------------
119
120use boundary::envelope::Envelope;
121pub use logic::crud::{DeletePolicy, EntityKind};
122
123/// Create a new, empty AXGF bundle as flat JSON.
124///
125/// The optional `family_name` populates `manifest.family.name` when provided.
126/// The returned envelope's `data` is the flat-bundle JSON.
127pub fn create_bundle(family_name: Option<&str>) -> Envelope {
128 boundary::lifecycle::create_bundle(family_name)
129}
130
131/// Import a `.axgf` ZIP archive (bytes) and return its flat-bundle JSON.
132///
133/// The manifest's `axgf` version is checked against [`SUPPORTED_SPEC_VERSIONS`]
134/// and the operation fails with `UNSUPPORTED_SPEC_VERSION` on mismatch.
135pub fn import_bundle(zip_bytes: &[u8]) -> Envelope {
136 boundary::lifecycle::import_bundle(zip_bytes)
137}
138
139/// Export a flat-bundle JSON string to a `.axgf` ZIP archive.
140///
141/// Stats are recomputed and the canonical JSON Schema is embedded. The
142/// returned envelope's `data` carries the ZIP bytes as base64 in a
143/// `{"zip_base64": ...}` object.
144pub fn export_bundle(flat_json: &str) -> Envelope {
145 boundary::lifecycle::export_bundle(flat_json)
146}
147
148/// Return manifest and computed stats for the given flat bundle without
149/// modifying it.
150pub fn inspect(flat_json: &str) -> Envelope {
151 boundary::lifecycle::inspect(flat_json)
152}
153
154/// Validate a flat bundle structurally (JSON Schema) and semantically
155/// (dangling refs, cycles, chronology, duplicate unique refs). Warnings do
156/// **not** cause a non-`ok` status.
157pub fn validate(flat_json: &str) -> Envelope {
158 logic::validate::validate(flat_json)
159}
160
161/// Add a new entity of the given kind to a flat bundle. A UUID v4 is
162/// generated when `entity_json.id` is missing.
163pub fn add_entity(flat_json: &str, kind: EntityKind, entity_json: &str) -> Envelope {
164 logic::crud::add_entity(flat_json, kind, entity_json)
165}
166
167/// Update an existing entity in a flat bundle, keyed by `id`.
168pub fn update_entity(flat_json: &str, kind: EntityKind, entity_json: &str) -> Envelope {
169 logic::crud::update_entity(flat_json, kind, entity_json)
170}
171
172/// Delete an entity by id, applying the caller's referential-integrity
173/// [`DeletePolicy`].
174pub fn delete_entity(
175 flat_json: &str,
176 kind: EntityKind,
177 id: &str,
178 policy: DeletePolicy,
179) -> Envelope {
180 logic::crud::delete_entity(flat_json, kind, id, policy)
181}
182
183/// Run the safe deduplication passes on a flat bundle. Ambiguous merges are
184/// flagged with `MANUAL_REVIEW_REQUIRED` diagnostics rather than performed.
185pub fn deduplicate(flat_json: &str) -> Envelope {
186 logic::dedup::deduplicate(flat_json)
187}
188
189/// Convert a GEDCOM 5.5.1 byte stream to a flat AXGF bundle.
190///
191/// - `default_confidence` is applied to imported facts when the source implies
192/// no explicit confidence.
193/// - `place_lang` is the BCP 47 language tag stored on imported `Place` names
194/// when the GEDCOM record has no explicit language.
195#[cfg(feature = "gedcom")]
196pub fn convert_gedcom(gedcom_bytes: &[u8], default_confidence: f64, place_lang: &str) -> Envelope {
197 convert::gedcom::convert(gedcom_bytes, default_confidence, place_lang)
198}