Skip to main content

memstead_schema/
base_metadata.rs

1//! Metadata fields every entity carries regardless of type.
2//!
3//! The loader prepends `type`, `created_date`, `last_modified` and appends
4//! `tags` to each type's declared `metadata_fields`. Authors never write
5//! these in YAML — redeclaring a base key is a load-time error.
6//!
7//! Canonical frontmatter order on write:
8//! `type, created_date, last_modified, <type-specific>, tags`.
9
10use crate::types::{FieldType, Filterable, MetadataFieldDef, Serialization};
11
12/// Fields that appear before any type-specific metadata on disk.
13pub fn prefix_fields() -> Vec<MetadataFieldDef> {
14    vec![
15        MetadataFieldDef {
16            key: "type".to_string(),
17            description: "Entity type discriminator — matches the schema type name.".to_string(),
18            field_type: FieldType::String,
19            default_value: None,
20            enum_values: None,
21            required: Some(true),
22            legacy_optional: None,
23            required_resolved: true,
24            init_timestamp: false,
25            auto_timestamp: false,
26            serialization: Serialization::Default,
27            filterable: Filterable::None,
28        },
29        MetadataFieldDef {
30            key: "created_date".to_string(),
31            description: "Date the entity was created — filled by the engine on create."
32                .to_string(),
33            field_type: FieldType::Date,
34            default_value: None,
35            enum_values: None,
36            required: Some(true),
37            legacy_optional: None,
38            required_resolved: true,
39            init_timestamp: true,
40            auto_timestamp: false,
41            serialization: Serialization::Default,
42            // Engine-stamped, always a valid ISO date — the canonical
43            // "entities created since X" axis. Range-filterable to match
44            // `last_modified` and the `memstead_search` range_filters example.
45            filterable: Filterable::Range,
46        },
47        MetadataFieldDef {
48            key: "last_modified".to_string(),
49            description: "Date of the most recent edit — filled by the engine on write."
50                .to_string(),
51            field_type: FieldType::Date,
52            default_value: None,
53            enum_values: None,
54            required: Some(true),
55            legacy_optional: None,
56            required_resolved: true,
57            init_timestamp: false,
58            auto_timestamp: true,
59            serialization: Serialization::Default,
60            filterable: Filterable::Range,
61        },
62    ]
63}
64
65/// Fields that appear after type-specific metadata on disk.
66pub fn suffix_fields() -> Vec<MetadataFieldDef> {
67    vec![MetadataFieldDef {
68        key: "tags".to_string(),
69        description: "Free-form categorization tags — comma-separated on disk.".to_string(),
70        field_type: FieldType::String,
71        default_value: None,
72        enum_values: None,
73        required: None,
74        legacy_optional: None,
75        required_resolved: false,
76        init_timestamp: false,
77        auto_timestamp: false,
78        serialization: Serialization::CsvArray,
79        filterable: Filterable::Equality,
80    }]
81}
82
83/// Every base metadata key. Used by the loader to reject YAML redeclarations
84/// and by JSON-Schema generation to surface the implicit fields.
85pub const BASE_KEYS: &[&str] = &["type", "created_date", "last_modified", "tags"];
86
87pub fn is_base_key(key: &str) -> bool {
88    BASE_KEYS.contains(&key)
89}