From ab2bfcb5fb7c6aa9dff2d622c66770d04c8bb5a6 Mon Sep 17 00:00:00 2001 From: Alessandro Asoni Date: Mon, 18 May 2026 14:06:18 +0200 Subject: [PATCH 1/2] Use IndexMap for mounts and validate duplicates --- crates/schema/src/def.rs | 4 +-- crates/schema/src/def/validate/v10.rs | 48 ++++++++++++++++++++++++--- crates/schema/src/def/validate/v9.rs | 2 +- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/crates/schema/src/def.rs b/crates/schema/src/def.rs index d518f2ac5ef..9892500ad0c 100644 --- a/crates/schema/src/def.rs +++ b/crates/schema/src/def.rs @@ -153,7 +153,7 @@ pub struct ModuleDef { raw_module_def_version: RawModuleDefVersion, /// Mounted submodules, keyed by the namespace they are mounted under. - mounts: Vec<(String, ModuleDef)>, + mounts: IndexMap, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -171,7 +171,7 @@ impl ModuleDef { } /// The mounted submodules of the module definition. - pub fn mounts(&self) -> &[(String, ModuleDef)] { + pub fn mounts(&self) -> &IndexMap { &self.mounts } diff --git a/crates/schema/src/def/validate/v10.rs b/crates/schema/src/def/validate/v10.rs index c0ff11df333..e5aee3768c3 100644 --- a/crates/schema/src/def/validate/v10.rs +++ b/crates/schema/src/def/validate/v10.rs @@ -271,8 +271,8 @@ pub fn validate(def: RawModuleDefV10) -> Result { let (tables, types, reducers, procedures, views, mounts) = (tables_types_reducers_procedures_views, mounts) .combine_errors() - .map(|((tables, types, reducers, procedures, views), mounts)| { - (tables, types, reducers, procedures, views, mounts) + .and_then(|((tables, types, reducers, procedures, views), mounts)| { + validate_mount_names_are_unique(mounts).map(|mounts| (tables, types, reducers, procedures, views, mounts)) }) .map_err(|errors: ValidationErrors| errors.sort_deduplicate())?; @@ -302,6 +302,23 @@ fn validate_mount(mount: RawModuleMountV10) -> Result<(String, ModuleDef)> { Ok((mount.namespace, validate(mount.module)?)) } +fn validate_mount_names_are_unique(mounts: Vec<(String, ModuleDef)>) -> Result> { + let mut errors = vec![]; + let mut map = IndexMap::with_capacity(mounts.len()); + + for (namespace, def) in mounts { + if map.contains_key(&namespace) { + errors.push(ValidationError::DuplicateName { + name: namespace.into(), + }); + } else { + map.insert(namespace, def); + } + } + + ValidationErrors::add_extra_errors(Ok(map), errors) +} + /// Change the visibility of scheduled functions and lifecycle reducers to Internal. /// fn change_scheduled_functions_and_lifetimes_visibility( @@ -902,6 +919,7 @@ mod tests { use spacetimedb_lib::db::raw_def::*; use spacetimedb_lib::ScheduleAt; use spacetimedb_primitives::{ColId, ColList, ColSet}; + use spacetimedb_sats::raw_identifier::RawIdentifier; use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, ProductType, SumValue}; use v9::{Lifecycle, TableAccess, TableType}; @@ -1294,8 +1312,8 @@ mod tests { let mounts = def.mounts(); assert_eq!(mounts.len(), 1); - assert_eq!(mounts[0].0, "authlib"); - assert!(mounts[0].1.table(&expect_identifier("sessions")).is_some()); + let mounted = mounts.get("authlib").expect("authlib mount should exist"); + assert!(mounted.table(&expect_identifier("sessions")).is_some()); } #[test] @@ -1314,6 +1332,28 @@ mod tests { }); } + #[test] + fn duplicate_mount_namespace() { + let raw = RawModuleDefV10 { + sections: vec![RawModuleDefV10Section::Mounts(vec![ + RawModuleMountV10 { + namespace: "authlib".to_string(), + module: RawModuleDefV10::default(), + }, + RawModuleMountV10 { + namespace: "authlib".to_string(), + module: RawModuleDefV10::default(), + }, + ])], + }; + + let result: Result = raw.try_into(); + + expect_error_matching!(result, ValidationError::DuplicateName { name } => { + name == &RawIdentifier::from("authlib") + }); + } + #[test] fn invalid_unique_constraint_column_ref() { let mut builder = RawModuleDefV10Builder::new(); diff --git a/crates/schema/src/def/validate/v9.rs b/crates/schema/src/def/validate/v9.rs index 5daf738a4c0..82344b01d0c 100644 --- a/crates/schema/src/def/validate/v9.rs +++ b/crates/schema/src/def/validate/v9.rs @@ -166,7 +166,7 @@ pub fn validate(def: RawModuleDefV9) -> Result { lifecycle_reducers, procedures, raw_module_def_version: RawModuleDefVersion::V9OrEarlier, - mounts: Vec::new(), + mounts: IndexMap::new(), }) } From 3d09bdaca1f252783cb105a87bce15cf9c7d39c4 Mon Sep 17 00:00:00 2001 From: Alessandro Asoni Date: Mon, 18 May 2026 14:53:13 +0200 Subject: [PATCH 2/2] Format --- crates/schema/src/def/validate/v10.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/schema/src/def/validate/v10.rs b/crates/schema/src/def/validate/v10.rs index e5aee3768c3..2e7051de513 100644 --- a/crates/schema/src/def/validate/v10.rs +++ b/crates/schema/src/def/validate/v10.rs @@ -308,9 +308,7 @@ fn validate_mount_names_are_unique(mounts: Vec<(String, ModuleDef)>) -> Result