Skip to main content

armour_typ/
graph.rs

1//! Declarative, serializable schema-graph model. No executable code here —
2//! the registry/validator live in `armdb::schema`; this model is what a
3//! future RPC `GetSchema` extension can ship to a GUI.
4
5use serde::Serialize;
6
7use crate::refs::PathSeg;
8use crate::typ::Typ;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
11pub enum CollectionKind {
12    /// Primary collection owning an entity (FK target for its brand).
13    Entity,
14    /// Derived collection synchronized from a source (hook/task).
15    SecondaryIndex,
16    /// Same entity duplicated for another access path.
17    Denormalized,
18    /// Recomputable counters/aggregates.
19    Counters,
20    /// Not related to other collections.
21    Standalone,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
25pub enum OnMissing {
26    /// Missing target — error finding.
27    Error,
28    /// Missing target — warning finding.
29    Warn,
30    /// Missing target is legal (e.g. reply to a deleted message).
31    AllowMissing,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
35pub enum RelationKind {
36    Fk {
37        /// Path of the ID field inside key/value ("k"/"v" prefix in `field_in`).
38        field_path: Vec<PathSeg>,
39        brand: String,
40        on_missing: OnMissing,
41        /// false — graph-only edge; validator reports it as UnvalidatedEdge
42        /// unless an explicit fk declaration covers it.
43        validated: bool,
44    },
45    /// `to` is a secondary index of `from`.
46    Index,
47    /// `to` is a denormalization of `from`.
48    Denorm,
49    /// Field `field` in `from` is recomputed from `to`.
50    Counter { field: String },
51}
52
53#[derive(Debug, Clone, Serialize)]
54pub struct CollectionNode {
55    pub name: String,
56    pub kind: CollectionKind,
57    /// Value type descriptor (already serializable, reused as-is).
58    pub ty: Typ,
59    /// Brand of SelfId if the key is a branded ID.
60    pub self_brand: Option<String>,
61}
62
63#[derive(Debug, Clone, Serialize)]
64pub struct RelationEdge {
65    pub from: String,
66    pub to: String,
67    pub kind: RelationKind,
68}
69
70#[derive(Debug, Clone, Serialize, Default)]
71pub struct SchemaGraph {
72    pub collections: Vec<CollectionNode>,
73    pub relations: Vec<RelationEdge>,
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::scalar::ScalarTyp;
80
81    #[test]
82    fn graph_serializes_to_json() {
83        let g = SchemaGraph {
84            collections: vec![CollectionNode {
85                name: "users".into(),
86                kind: CollectionKind::Entity,
87                ty: Typ::Scalar(ScalarTyp::U64),
88                self_brand: Some("USR".into()),
89            }],
90            relations: vec![RelationEdge {
91                from: "messages".into(),
92                to: "users".into(),
93                kind: RelationKind::Fk {
94                    field_path: vec![PathSeg::Field("sender_id")],
95                    brand: "USR".into(),
96                    on_missing: OnMissing::Error,
97                    validated: true,
98                },
99            }],
100        };
101        let json = serde_json::to_string(&g).expect("serialize");
102        assert!(json.contains("\"users\""));
103        assert!(json.contains("\"sender_id\""));
104    }
105}