icydb_schema/build/
mod.rs

1pub mod reserved;
2pub mod validate;
3
4use crate::{
5    Error, ThisError,
6    node::{Entity, Schema, Store, VisitableNode},
7    prelude::*,
8    visit::ValidateVisitor,
9};
10use std::{
11    collections::BTreeMap,
12    sync::{LazyLock, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
13};
14
15///
16/// BuildError
17///
18
19#[derive(Debug, ThisError)]
20pub enum BuildError {
21    #[error("validation failed: {0}")]
22    Validation(ErrorTree),
23}
24
25///
26/// SCHEMA
27/// the static data structure
28///
29
30static SCHEMA: LazyLock<RwLock<Schema>> = LazyLock::new(|| RwLock::new(Schema::new()));
31
32static SCHEMA_VALIDATED: OnceLock<bool> = OnceLock::new();
33
34/// Acquire a write guard to the global schema during build-time codegen.
35pub fn schema_write() -> RwLockWriteGuard<'static, Schema> {
36    SCHEMA
37        .write()
38        .expect("schema RwLock poisoned while acquiring write lock")
39}
40
41// schema_read
42// just reads the schema directly without validation
43pub(crate) fn schema_read() -> RwLockReadGuard<'static, Schema> {
44    SCHEMA
45        .read()
46        .expect("schema RwLock poisoned while acquiring read lock")
47}
48
49/// Read the global schema, validating it exactly once per process.
50pub fn get_schema() -> Result<RwLockReadGuard<'static, Schema>, Error> {
51    let schema = schema_read();
52    validate(&schema).map_err(BuildError::Validation)?;
53
54    Ok(schema)
55}
56
57// validate
58fn validate(schema: &Schema) -> Result<(), ErrorTree> {
59    if *SCHEMA_VALIDATED.get_or_init(|| false) {
60        return Ok(());
61    }
62
63    // validate
64    let mut visitor = ValidateVisitor::new();
65    schema.accept(&mut visitor);
66    validate_entity_names(schema, &mut visitor.errors);
67    visitor.errors.result()?;
68
69    SCHEMA_VALIDATED.set(true).ok();
70
71    Ok(())
72}
73
74fn validate_entity_names(schema: &Schema, errs: &mut ErrorTree) {
75    let mut by_canister: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
76
77    for (entity_path, entity) in schema.get_nodes::<Entity>() {
78        let store = match schema.cast_node::<Store>(entity.store) {
79            Ok(store) => store,
80            Err(e) => {
81                errs.add(e);
82                continue;
83            }
84        };
85
86        let canister = store.canister.to_string();
87        let name = entity.resolved_name().to_string();
88        let entity_path = entity_path.to_string();
89
90        let entry = by_canister.entry(canister.clone()).or_default();
91
92        if let Some(prev) = entry.insert(name.clone(), entity_path.clone()) {
93            err!(
94                errs,
95                "duplicate entity name '{name}' in canister '{canister}' for '{prev}' and '{entity_path}'"
96            );
97        }
98    }
99}