Store reader: a record whose schema the catalog does not publish is unreadable
C# read such a store on flush and commit and refused it on open, with an untyped InvalidOperationException from the cache. The store reader now refuses it with CultStoreUnreadableException, so open, flush and commit give one verdict, as Rust, TypeScript and Python already do. The readability open test builds a cache with a codec, so a variant store opens as it would in use. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fdafa15022
commit
67575c4ec4
2 changed files with 24 additions and 8 deletions
|
|
@ -180,7 +180,8 @@ public static class CultDocumentMessagePackSerialization
|
|||
/// <summary>
|
||||
/// Reads a store file: exactly one complete MessagePack array of three slots (header, schema catalog, records), or an empty
|
||||
/// array, which is an empty store. A truncated file, bytes after the array, a value that is not an array, a first slot that
|
||||
/// is not a header string, a store with fewer or more slots, or a body that does not decode are refused with
|
||||
/// is not a header string, a store with fewer or more slots, a record whose schema the catalog does not publish, or a body
|
||||
/// that does not decode are refused with
|
||||
/// <see cref="CultStoreUnreadableException"/>, so a reader never takes part of a file for the whole and never skips a slot
|
||||
/// it does not understand. The rewriting paths use this same reader as their verdict on whether a file may be replaced.
|
||||
/// </summary>
|
||||
|
|
@ -224,6 +225,7 @@ public static class CultDocumentMessagePackSerialization
|
|||
snapshot.Records[index] = ReadPersistedRecord(ref reader);
|
||||
}
|
||||
|
||||
RequirePublishedSchemas(snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
catch (CultStoreUnreadableException)
|
||||
|
|
@ -236,6 +238,25 @@ public static class CultDocumentMessagePackSerialization
|
|||
}
|
||||
}
|
||||
|
||||
// A record whose schema the store's catalog does not publish cannot be read by any runtime that has only the file.
|
||||
private static void RequirePublishedSchemas(CultPersistedStoreSnapshot snapshot)
|
||||
{
|
||||
var published = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var entry in snapshot.SchemaCatalog)
|
||||
{
|
||||
published.Add(entry.SchemaId);
|
||||
foreach (var compatible in entry.CompatibleSchemaIds)
|
||||
published.Add(compatible);
|
||||
}
|
||||
|
||||
var unpublished = snapshot.Records.FirstOrDefault(record => !published.Contains(record.SchemaId));
|
||||
if (unpublished != null)
|
||||
{
|
||||
throw new CultStoreUnreadableException(
|
||||
$"Record '{unpublished.Key}' names schema '{unpublished.SchemaId}', which the store's schema catalog does not publish.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void RequireOneArray(byte[] bytes)
|
||||
{
|
||||
var whole = new MessagePackReader(bytes);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace GameCult.Caching.Tests
|
|||
public class StoreReadabilityTests
|
||||
{
|
||||
private const int CSharp = 1;
|
||||
private static readonly CultDocumentRegistry Registry = CultDocumentRegistry.ForTypes(new[] { typeof(IdDeck) });
|
||||
private static readonly CultDocumentRegistry Registry = CultDocumentRegistry.ForTypes(new[] { typeof(IdDeck), typeof(VectorItem) });
|
||||
private string _directory = "";
|
||||
|
||||
[SetUp]
|
||||
|
|
@ -66,12 +66,7 @@ namespace GameCult.Caching.Tests
|
|||
var path = Path.Combine(_directory, "open.cc");
|
||||
File.WriteAllBytes(path, File.ReadAllBytes(Path.Combine(VectorRoot(), vector)));
|
||||
|
||||
using var cache = new CultCache();
|
||||
void Open()
|
||||
{
|
||||
cache.AddBackingStore(new SingleFileMessagePackBackingStore(path));
|
||||
cache.PullAllBackingStoresAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
void Open() => CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry }).Dispose();
|
||||
|
||||
if (reads)
|
||||
Assert.DoesNotThrow(Open);
|
||||
|
|
|
|||
Loading…
Reference in a new issue