diff --git a/src/GameCult.Networking/CultNetDatabase.cs b/src/GameCult.Networking/CultNetDatabase.cs index 5fcddecf..6e1ed41f 100644 --- a/src/GameCult.Networking/CultNetDatabase.cs +++ b/src/GameCult.Networking/CultNetDatabase.cs @@ -941,9 +941,9 @@ namespace GameCult.Networking if (message == null) throw new ArgumentNullException(nameof(message)); var key = new CultRecordKey(message.RecordKey); - var shard = ResolveShardInternal(message.SchemaId, key); - EnsurePrimary(shard, message.SchemaId, key, message.ShardEpoch); - var descriptor = _cache.Registry.GetRequiredBySchemaId(message.SchemaId); + var descriptor = _documents.ResolveDescriptorForSchemaId(message.SchemaId); + var shard = ResolveShardInternal(descriptor, key); + EnsurePrimary(shard, descriptor.SchemaId, key, message.ShardEpoch); var previous = _cache.Get(key); if (previous == null) { diff --git a/src/GameCult.Networking/CultNetDocumentRegistry.cs b/src/GameCult.Networking/CultNetDocumentRegistry.cs index 1da69fae..e2633768 100644 --- a/src/GameCult.Networking/CultNetDocumentRegistry.cs +++ b/src/GameCult.Networking/CultNetDocumentRegistry.cs @@ -396,7 +396,7 @@ namespace GameCult.Networking { try { - return _documents.GetRequiredBySchemaId(document.SchemaId); + return ResolveDescriptorForSchemaId(document.SchemaId); } catch (InvalidOperationException) when (TryResolveDescriptorByPayloadSchema(document.Payload) is { } descriptor) { @@ -404,6 +404,40 @@ namespace GameCult.Networking } } + internal CultDocumentDescriptor ResolveDescriptorForSchemaId(string schemaId) + { + try + { + return _documents.GetRequiredBySchemaId(schemaId); + } + catch (InvalidOperationException) when (TryResolveDescriptorBySchemaAlias(schemaId) is { } descriptor) + { + return descriptor; + } + } + + private CultDocumentDescriptor? TryResolveDescriptorBySchemaAlias(string schemaId) + { + if (string.IsNullOrWhiteSpace(schemaId)) + return null; + + var candidates = ResolvePayloadSchemaCandidates().ToArray(); + var descriptor = candidates.FirstOrDefault(candidate => + string.Equals(candidate.SchemaId, schemaId, StringComparison.Ordinal) || + string.Equals(candidate.SchemaName, schemaId, StringComparison.Ordinal) || + string.Equals(candidate.SchemaVersion, schemaId, StringComparison.Ordinal) || + candidate.ToCatalogEntry().CompatibleSchemaIds.Any(compatible => + string.Equals(compatible, schemaId, StringComparison.Ordinal))); + if (descriptor != null) + return descriptor; + + var schemaName = InferSchemaName(schemaId); + return string.IsNullOrWhiteSpace(schemaName) + ? null + : candidates.FirstOrDefault(candidate => + string.Equals(candidate.SchemaName, schemaName, StringComparison.Ordinal)); + } + private CultDocumentDescriptor? TryResolveDescriptorByPayloadSchema(byte[] payload) { var schemaVersion = TryReadSchemaVersion(payload); diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index a7f0f3b5..95282196 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -1951,6 +1951,55 @@ namespace GameCult.Networking.Tests Assert.That(database.GetMutationLog("wrong-network-notes"), Is.Empty); } + [Test] + public async Task CultNetDatabase_Applies_Delete_BySchemaAlias() + { + var cache = new CultCache(); + var registry = new CultNetDocumentRegistry(cache.Registry) + .Register(CultNetDocumentBinding.ForDocument(cache.Registry)); + var database = new CultNetDatabase(cache, new CultNetDatabaseOptions + { + DocumentRegistry = registry, + Shards = + [ + new CultNetShardDescriptor( + "wrong-network-notes", + "runtime-b", + epoch: 1, + isPrimary: false, + schemaIds: ["other.note.v1"], + keyPrefix: "network-note:"), + new CultNetShardDescriptor( + "network-notes", + "runtime-a", + epoch: 2, + isPrimary: true, + schemaIds: ["tests.networking_note.v1"], + keyPrefix: "network-note:") + ] + }); + var key = new CultRecordKey("network-note:alias-delete"); + await database.PutAsync(key, new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "delete through alias", + Revision = 13 + }); + + await database.ApplyDeleteAsync(new CultNetDocumentDeleteMessage + { + MessageId = "delete-alias", + SchemaId = "tests.networking_note.v1", + RecordKey = key.Value, + ShardId = "network-notes", + ShardEpoch = 2 + }); + + Assert.That(cache.Get(key), Is.Null); + Assert.That(database.GetMutationLog("network-notes").Last().Kind, Is.EqualTo(CultNetDatabaseChangeKind.Removed)); + Assert.That(database.GetMutationLog("wrong-network-notes"), Is.Empty); + } + [Test] public async Task CultNetDatabase_Predicts_ClientOwnedInput_ThroughSchemaAliasScope() {