{"record":{"id":"72c89854d8353758","repo":"clockworklabs/SpacetimeDB","slug":"expected-moduledef-to-contain-key-but-it-does-not","errorCode":null,"errorMessage":"expected ModuleDef to contain key, but it does not","messagePattern":"expected ModuleDef to contain key, but it does not","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/schema/src/def.rs","lineNumber":590,"sourceCode":"\n    /// The `TableDef` an entity in the global namespace is stored in, if any.\n    ///\n    /// Generally, you will want to use the `lookup` method on the entity type instead.\n    pub fn stored_in_table_def(&self, name: &RawIdentifier) -> Option<&TableDef> {\n        self.stored_in_table_def\n            .get(name)\n            .and_then(|table_name| self.tables.get(table_name))\n    }\n\n    /// Lookup a definition by its key in `self`.\n    pub fn lookup<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> Option<&T> {\n        T::lookup(self, key)\n    }\n\n    /// Lookup a definition by its key in `self`, panicking if not found.\n    /// Only use this method if you are sure the key exists in the module definition.\n    pub fn lookup_expect<T: ModuleDefLookup>(&self, key: T::Key<'_>) -> &T {\n        T::lookup(self, key).expect(\"expected ModuleDef to contain key, but it does not\")\n    }\n\n    /// Convenience method to look up a table, possibly by a string.\n    pub fn table<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&TableDef> {\n        // If the string IS a valid identifier, we can just look it up.\n        self.tables.get(name)\n    }\n\n    /// Convenience method to look up a view, possibly by a string.\n    pub fn view<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<&ViewDef> {\n        // If the string IS a valid identifier, we can just look it up.\n        self.views.get(name)\n    }\n\n    /// Convenience method to look up a view, possibly by a string, returning its id as well.\n    pub fn view_full<K: ?Sized + Hash + Equivalent<Identifier>>(&self, name: &K) -> Option<(ViewFnPtr, &ViewDef)> {\n        // If the string IS a valid identifier, we can just look it up.\n        self.views.get(name).map(|def| (def.fn_ptr, def))","sourceCodeStart":572,"sourceCodeEnd":608,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/schema/src/def.rs#L572-L608","documentation":"Panic from `ModuleDef::lookup_expect`, SpacetimeDB's schema-module convenience lookup. It calls `T::lookup(self, key)` and `.expect(\"expected ModuleDef to contain key, but it does not\")` when the key (table/view/sequence/constraint/type name, possibly namespace-qualified) is absent. The API contract states: only call it when existence is guaranteed (e.g. the key came from iterating the same ModuleDef); a panic means an unverified lookup assumption.","triggerScenarios":"Calling `lookup_expect` with a key obtained from a DIFFERENT module version (old plan vs new def during migration), a name built by string concatenation that doesn't match an actual entry (submodule path join mismatch, casing), or a key validated against one collection but looked up in another (e.g. constraint name found in the wrong namespace map).","commonSituations":"Migration code that resolves names from a `DatabaseDef`/plan into a freshly published `ModuleDef` where names shifted; refactors renaming entities while stale references remain; submodule namespace-prefix inconsistencies; cross-crate code assuming another crate added a definition. Note the sibling code in update.rs:486 catches a similar case with a proper `anyhow!` error instead — the ideal fix direction.","solutions":["Replace the `lookup_expect` call with `lookup(...).ok_or_else(|| anyhow!(\"...not found...\"))?` so callers receive a real error instead of a panic (the codebase already does this nearby for AddConstraint lookups).","Verify the key's provenance: keys must come from the same ModuleDef instance or be re-validated after any publish/migration boundary.","Print/log the exact key (and its namespace path) at failure to spot join/casing mismatches between constructed names and stored identifiers.","Add a regression test covering the offending operation (e.g. the migration step) with the failing key."],"exampleFix":"// before: panics on missing key\nlet table = module_def.lookup_expect::<TableDef>(key);\n\n// after: return a typed error\nlet table = module_def\n    .lookup::<TableDef>(key)\n    .ok_or_else(|| anyhow::anyhow!(\"table `{key}` not found in module def\"))?;","handlingStrategy":"validation","validationCode":"// Replace lookup_expect with a checked lookup at the call boundary\nlet def = module_def\n    .lookup::<TableDef>(key)\n    .ok_or_else(|| anyhow::anyhow!(\"`{key}` not found in module def\"))?;","typeGuard":"// Narrow before use when the key type is dynamic\nfn has_table(md: &ModuleDef, name: &str) -> bool { md.table(name).is_some() }","tryCatchPattern":"let r = std::panic::catch_unwind(AssertUnwindSafe(|| md.lookup_expect::<T>(key)));\nmatch r { Ok(v) => v, Err(_) => return Err(SchemaError::key_not_found(key)) }","preventionTips":["Only call lookup_expect with keys obtained by iterating the SAME ModuleDef.","Prefer `lookup(...).ok_or(anyhow!(...))?` — the codebase itself uses error returns for the neighboring AddConstraint case.","Log the exact key (with namespace path) when lookups fail to catch join/casing mismatches."],"tags":["rust","spacetimedb","module-def","lookup","key-not-found","schema","panic"],"backgroundTag":"key-not-found","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}