Skip to main content

hayson_engine/schema/
types.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6pub const META_PREFIX: &str = "_";
7
8/// The one value a table schema document may declare under `"schemaVersion"`.
9pub const SCHEMA_VERSION: u64 = 1;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum FieldType {
13    #[serde(rename = "string")]
14    String,
15    #[serde(rename = "int")]
16    Int,
17    #[serde(rename = "float")]
18    Float,
19    #[serde(rename = "bool")]
20    Bool,
21    #[serde(rename = "timestamp")]
22    Timestamp,
23    #[serde(rename = "enum")]
24    Enum,
25    #[serde(rename = "json")]
26    Json,
27    #[serde(rename = "string[]")]
28    StringArray,
29    #[serde(rename = "int[]")]
30    IntArray,
31    #[serde(rename = "float[]")]
32    FloatArray,
33    #[serde(rename = "bool[]")]
34    BoolArray,
35    #[serde(rename = "markdown")]
36    Markdown,
37    #[serde(rename = "image")]
38    Image,
39}
40
41impl FieldType {
42    pub fn is_array(self) -> bool {
43        matches!(
44            self,
45            FieldType::StringArray | FieldType::IntArray | FieldType::FloatArray | FieldType::BoolArray
46        )
47    }
48
49    pub fn element_type(self) -> Option<FieldType> {
50        match self {
51            FieldType::StringArray => Some(FieldType::String),
52            FieldType::IntArray => Some(FieldType::Int),
53            FieldType::FloatArray => Some(FieldType::Float),
54            FieldType::BoolArray => Some(FieldType::Bool),
55            _ => None,
56        }
57    }
58
59    pub fn is_sortable(self) -> bool {
60        matches!(
61            self,
62            FieldType::String | FieldType::Int | FieldType::Float | FieldType::Timestamp
63        )
64    }
65
66    /// A type whose value can carry its own virtual path. Whether a given field actually does is
67    /// `FieldSpec::has_virtual_path`: a `markdown` field declaring `addressable: false` is of such
68    /// a type and still has no path of its own.
69    pub fn has_virtual_path(self) -> bool {
70        matches!(self, FieldType::Markdown | FieldType::Image)
71    }
72
73    pub fn as_str(self) -> &'static str {
74        match self {
75            FieldType::String => "string",
76            FieldType::Int => "int",
77            FieldType::Float => "float",
78            FieldType::Bool => "bool",
79            FieldType::Timestamp => "timestamp",
80            FieldType::Enum => "enum",
81            FieldType::Json => "json",
82            FieldType::StringArray => "string[]",
83            FieldType::IntArray => "int[]",
84            FieldType::FloatArray => "float[]",
85            FieldType::BoolArray => "bool[]",
86            FieldType::Markdown => "markdown",
87            FieldType::Image => "image",
88        }
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
93pub enum IndexKind {
94    #[serde(rename = "exact")]
95    Exact,
96    #[serde(rename = "sorted")]
97    Sorted,
98}
99
100#[derive(Debug, Clone, PartialEq)]
101pub enum DefaultValue {
102    Now,
103    Value(Value),
104}
105
106#[derive(Debug, Clone, PartialEq)]
107pub struct FieldSpec {
108    pub field_type: FieldType,
109    pub required: bool,
110    pub unique: bool,
111    pub index: Option<IndexKind>,
112    pub fulltext: bool,
113    /// NOTE: encryption at rest — parsed/rejected today, AES-256-GCM sealing is not wired up yet.
114    pub cypher: bool,
115    /// The value is a password: stored as a salted Argon2id hash, never as the text, and checked
116    /// with `verify_password`. May be combined with `cypher`.
117    pub password_hash: bool,
118    pub values: Option<Vec<String>>,
119    pub default: Option<DefaultValue>,
120    /// Whether this field's own value is a link target — `true` for every field, and settable only
121    /// on a `markdown` one, where `addressable: false` means the content still links out but the
122    /// field holds no virtual path of its own and nothing can link to it.
123    pub addressable: bool,
124}
125
126impl FieldSpec {
127    pub fn new(field_type: FieldType) -> Self {
128        FieldSpec {
129            field_type,
130            required: false,
131            unique: false,
132            index: None,
133            fulltext: false,
134            cypher: false,
135            password_hash: false,
136            values: None,
137            default: None,
138            addressable: true,
139        }
140    }
141
142    /// Whether this field carries its own engine-managed virtual path — the index a markdown link
143    /// resolves against, and the value uniqueness is enforced on. A `markdown` field declaring
144    /// `addressable: false` answers `false`: it is not a link target, so it needs no path at all.
145    pub fn has_virtual_path(&self) -> bool {
146        self.field_type.has_virtual_path() && self.addressable
147    }
148}
149
150#[derive(Debug, Clone, PartialEq)]
151pub struct TableSchema {
152    pub table: String,
153    pub revision: u32,
154    pub primary_key: String,
155    pub partition_by: Option<String>,
156    pub fields: BTreeMap<String, FieldSpec>,
157}