Options Validation Hooks
The
AddOptions<T>().Validate(...)wiring exists on every options type today, but its predicate is a no-op stub.
Status: ๐ง Partial ยท Visibility: Public ยท Level: ๐ฃ Advanced ยท Category: Hosting
๐ฏ What it solves
.NET's Options pattern supports fail-fast validation: AddOptions<T>().Validate(predicate) runs
the predicate on first IOptions<T>.Value access and throws OptionsValidationException before
bad configuration reaches a service constructor. Typhon attaches this hook to every options type
(DatabaseEngineOptions, PagedMMFOptions/ManagedPagedMMFOptions, MemoryAllocatorOptions,
ResourceRegistryOptions) so real validation logic has one designated place to land per
subsystem, without changing any Add*() call signature when it does.
โ๏ธ How it works (in brief)
Every Add* extension that accepts a configure delegate attaches
optionsBuilder.Validate(_ => { /* TODO */ return true; }) right after .Configure(configure).
All four sites currently return true unconditionally โ the hook fires but never rejects
anything. Two option types separately expose their own real, standalone validation you can call
yourself: ResourceOptions.Validate() (throws InvalidOperationException if page cache + WAL +
shadow-buffer sizing exceeds TotalMemoryBudgetBytes) and PagedMMFOptions.IsValid /
Validate(bool silent, out string) (checks DatabaseName, DatabaseDirectory, and
DatabaseCacheSize well-formedness). Neither is wired into the Add*()/DI path โ invoking them
is on you.
๐ป Usage
var resourceOptions = new ResourceOptions
{
PageCachePages = 262144, // 2 GB
MaxActiveTransactions = 1000,
WalRingBufferSizeBytes = 8 << 20, // 8 MB
};
resourceOptions.Validate(); // throws InvalidOperationException if over budget โ call it yourself
var mmfOptions = new ManagedPagedMMFOptions { DatabaseName = "MyGame", DatabaseCacheSize = 4096 };
if (!mmfOptions.IsValid)
{
mmfOptions.Validate(silent: false, out _); // throws with a readable message
}
services
.AddManagedPagedMMF(o =>
{
o.DatabaseName = mmfOptions.DatabaseName;
o.DatabaseCacheSize = mmfOptions.DatabaseCacheSize;
})
.AddDatabaseEngine(o => o.Resources = resourceOptions);
// AddOptions<T>().Validate(...) still runs for both calls above, but its predicate
// always returns true โ it will not catch a bad DatabaseCacheSize or budget overrun.
โ ๏ธ Guarantees & limits
- The
.Validate(...)hook attached insideAdd*()is wired but non-functional โ its predicate is_ => true(marked// TODOin source, four separate sites). It never throwsOptionsValidationException, regardless of how invalid the configured values are. - The hook is only attached when a
configuredelegate is passed to theAdd*()call โ calling it with no delegate skips even the stub. ResourceOptions.Validate()andPagedMMFOptions.IsValid/Validate(bool, out string)are real, independent, and callable today โ but nothing in theAdd*()/DI path calls them for you.- Do not rely on
BuildServiceProvider()orGetRequiredService<DatabaseEngine>()to surface a configuration mistake (oversized cache, invalid database name, WAL sizing over budget) โ none of these are caught before the engine attempts to open its backing files. - Until the stubs are implemented, calling
ResourceOptions.Validate()/PagedMMFOptions.IsValidyourself โ before or inside yourconfiguredelegate โ is the only fail-fast path available.
๐งช Tests
- ResourceOptionsTests โ the real, callable
ResourceOptions.Validate(): passes on defaults/large budgets, throwsInvalidOperationExceptionwith a readable message when fixed allocations exceedTotalMemoryBudgetBytes; no test exercises theAdd*()-wired.Validate(_ => true)stub itself (there is nothing to assert โ it never rejects)
๐ Related
- Source:
TyphonBuilderExtensions.cs(ConfigureMemoryAllocatorOptions,ConfigureResourceRegistryOptions,AddPagedMMF,AddDatabaseEngineโ the four.Validate(_ => true)sites),ResourceOptions.csโValidate(),PagedMMFOptions.csโIsValid/Validate - Parent feature: Engine Options Configuration Surface
- Sibling: DI Engine Bootstrap Chain โ the
Add*()calls whoseconfiguredelegate this stubbed hook should fail-fast on.