Skip to main content

lenso_contracts/
admin_schema.rs

1//! Schema-admin data contracts: a module's declared manageable entities.
2
3use serde::{Deserialize, Serialize};
4
5/// A module's declared admin surface: which entities it exposes for management.
6#[derive(
7    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema, schemars::JsonSchema,
8)]
9pub struct AdminSchema {
10    pub entities: Vec<EntitySchema>,
11}
12
13/// One manageable entity (e.g. identity's "users").
14#[derive(
15    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema, schemars::JsonSchema,
16)]
17pub struct EntitySchema {
18    /// Stable entity key, unique within the module, e.g. "users".
19    pub name: String,
20    /// Human label for the console, e.g. "Users".
21    pub label: String,
22    /// Ordered field descriptors driving list columns / detail rows.
23    pub fields: Vec<FieldSchema>,
24    /// Capability required to read this entity. Declared now; gated only
25    /// coarsely (AdminActor) this step. Fine-grained RBAC is a later spec.
26    pub read_capability: String,
27}
28
29/// One field of an entity.
30#[derive(
31    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema, schemars::JsonSchema,
32)]
33pub struct FieldSchema {
34    /// Key in the record's JSON object, e.g. "email".
35    pub name: String,
36    /// Human label, e.g. "Email".
37    pub label: String,
38    /// Rendering hint for the console's display layer.
39    pub field_type: FieldType,
40    /// Whether the value may be null/absent.
41    #[serde(default)]
42    pub nullable: bool,
43}
44
45/// Minimal field-type vocabulary. `Json` is the catch-all so any field renders.
46#[derive(
47    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema, schemars::JsonSchema,
48)]
49#[serde(tag = "kind", rename_all = "snake_case")]
50#[non_exhaustive]
51pub enum FieldType {
52    String,
53    Integer,
54    Boolean,
55    Timestamp,
56    Json,
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    fn sample() -> AdminSchema {
64        AdminSchema {
65            entities: vec![EntitySchema {
66                name: "users".to_owned(),
67                label: "Users".to_owned(),
68                read_capability: "identity.users.read".to_owned(),
69                fields: vec![
70                    FieldSchema {
71                        name: "email".into(),
72                        label: "Email".into(),
73                        field_type: FieldType::String,
74                        nullable: false,
75                    },
76                    FieldSchema {
77                        name: "created_at".into(),
78                        label: "Created".into(),
79                        field_type: FieldType::Timestamp,
80                        nullable: false,
81                    },
82                ],
83            }],
84        }
85    }
86
87    #[test]
88    fn admin_schema_round_trips_through_json() {
89        let schema = sample();
90        let json = serde_json::to_string(&schema).expect("serialize");
91        let back: AdminSchema = serde_json::from_str(&json).expect("deserialize");
92        assert_eq!(schema, back);
93    }
94
95    #[test]
96    fn field_type_serializes_with_kind_tag() {
97        let json = serde_json::to_string(&FieldType::Timestamp).expect("serialize");
98        assert_eq!(json, r#"{"kind":"timestamp"}"#);
99    }
100}