cultcache-rs: pin the global flag surviving a refused re-registration; correct the changelog's decode claim
All checks were successful
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Successful in 21m6s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 1m8s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Libby 2026-11-11 07:42:54 +00:00
commit 4897457074
2 changed files with 34 additions and 3 deletions

View file

@ -13,7 +13,7 @@ only in git history.
- `#[cultcache(index)]` means unique, as in C# `RefuseIndexSharing`: within one type and one alias no two records hold one non-blank value. A put, batch, push or load that would make two records hold a value is refused, naming both keys and the alias, and a store that already holds a duplicate does not open. A grouping index is no longer expressible; a consumer that relied on shared values must drop the `index` and look up by another way.
- `bool` index and lookup text renders `True` / `False`, as C# does, where it rendered `true` / `false`.
- `DatabaseEntry::UNIQUE_INDEXES` replaces `cult_indexes` as the one index declaration and changes type (its members are the unique aliases); a hand implementation overriding `cult_indexes` no longer compiles.
- `register_entry_type` over a type that already holds records re-judges them under the new declaration (unique indexes and the global rule) and refuses, changing nothing, when they violate it or cannot be decoded.
- `register_entry_type` over a type that already holds records re-judges them under the new declaration (unique indexes and the global rule) and refuses, changing nothing, when they violate it. A declaration with an index also refuses when a held record cannot be decoded as the new type; a declaration without one does not read the records, so it does not.
### Added

View file

@ -241,14 +241,45 @@ fn registering_a_type_global_over_two_held_records_is_refused_and_changes_nothin
assert_eq!(cache.get_all_with_keys::<LooseSettings>().unwrap().len(), 3, "still ordinary");
}
// One held record may become the global; dropping the rule again restores the ordinary type.
// One held record may become the global; a second is then refused as a second global.
#[test]
fn registering_a_type_global_over_one_held_record_is_admitted() {
let mut cache = CultCache::new();
cache.register_entry_type::<LooseSettings>().unwrap();
cache.put("a", &LooseSettings { value: "one".into() }).unwrap();
cache.register_entry_type::<Settings>().unwrap();
assert!(cache.put("b", &Settings { value: "two".into() }).is_err(), "a second global is refused");
let refusal = cache.put("b", &Settings { value: "two".into() }).unwrap_err();
assert!(refusal.downcast_ref::<CultCacheSecondGlobal>().is_some(), "{refusal:#}");
}
/// A global with an indexed member, and the same type and schema declaring that member as an integer.
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "indexed_global", schema = "tests.indexed_global", global)]
struct IndexedGlobal {
#[cultcache(key = 0, index = "V")]
v: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "indexed_global", schema = "tests.indexed_global", global)]
struct RetypedGlobal {
#[cultcache(key = 0, index = "V")]
v: i64,
}
// A refused re-registration over an already-global type leaves it global: the rule keeps refusing a second record.
#[test]
fn a_refused_reregistration_leaves_a_global_type_global() {
let mut cache = CultCache::new();
cache.register_entry_type::<IndexedGlobal>().unwrap();
cache.put("a", &IndexedGlobal { v: "x".into() }).unwrap();
assert!(cache.register_entry_type::<RetypedGlobal>().is_err(), "the held text cannot be decoded as an integer");
let refusal = cache.put("b", &IndexedGlobal { v: "y".into() }).unwrap_err();
assert!(refusal.downcast_ref::<CultCacheSecondGlobal>().is_some(), "{refusal:#}");
assert_eq!(cache.get_global::<IndexedGlobal>().unwrap(), Some(IndexedGlobal { v: "x".into() }));
assert_eq!(cache.get_by_index::<IndexedGlobal>("V", "x").unwrap(), cache.get::<IndexedGlobal>("a").unwrap());
}
#[test]