icydb_schema/build/
mod.rs1pub 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#[derive(Debug, ThisError)]
20pub enum BuildError {
21 #[error("validation failed: {0}")]
22 Validation(ErrorTree),
23}
24
25static SCHEMA: LazyLock<RwLock<Schema>> = LazyLock::new(|| RwLock::new(Schema::new()));
31
32static SCHEMA_VALIDATED: OnceLock<bool> = OnceLock::new();
33
34pub fn schema_write() -> RwLockWriteGuard<'static, Schema> {
36 SCHEMA
37 .write()
38 .expect("schema RwLock poisoned while acquiring write lock")
39}
40
41pub(crate) fn schema_read() -> RwLockReadGuard<'static, Schema> {
44 SCHEMA
45 .read()
46 .expect("schema RwLock poisoned while acquiring read lock")
47}
48
49pub 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
57fn validate(schema: &Schema) -> Result<(), ErrorTree> {
59 if *SCHEMA_VALIDATED.get_or_init(|| false) {
60 return Ok(());
61 }
62
63 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}