Guarantees and Limitations
Guarantees and Limitations
Section titled “Guarantees and Limitations”This page records what the suite guarantees, and what it deliberately does not do. Treat it as the contract a change must not break.
Runtime guarantees
Section titled “Runtime guarantees”Result<TValue, TError>is areadonly record structwith exactly three observable states:Uninitialized(thedefaultvalue),SuccessandFailure, each observable throughIsInitialized,IsSuccessandIsFailure.- The throw-on-misuse contract holds:
ValueandErrorthrowInvalidOperationExceptionin the wrong state, andMatch,Map,BindandMapErrorthrow"The result is uninitialized."fordefault. An uninitialized result is never silently coerced into a success or a failure. ToString()staysSuccess(value)/Failure(error)/Uninitialized.IResultValueaccessors never throw; the accessor that does not describe the current state returnsnull.- The implicit conversions from
TValueandTError, and theResult.Success/Result.FailureandResult<TValue, TError>.Success/.Failurefactories, are public contract.
Dependency guarantees
Section titled “Dependency guarantees”Purview.Resultsis dependency-free.- The ZodSharp and ASP.NET Core packages depend on it, never the reverse.
Purview.Results.AspNetCoredeliberately knows nothing about ZodSharp; validation handling lives inPurview.Results.ZodSharp.AspNetCore.- The runtime packages contain no reflection, no
dynamicand no runtime type discovery. Union structure is inspected only by the source generator, through Roslyn symbols.
Union support limits
Section titled “Union support limits”[GenerateResult]is supported on union declarations only. Generic unions are unsupported (RSG1002), and so areIUnionMembersmember providers (RSG1007).- Generated implicit conversions are not possible — see Union Errors for the five compiler
rules. The per-case
AsFailure<TValue>()helper and the IDE code fix are the ergonomics the language allows; the helper-free alternative is the(TenantError)caseValuecast. - Accessibility never widens: a union or case type that is not visible produces an
internalgenerated class (RSG1003covers the case where generated code could not reference a type at all).
HTTP mapping philosophy
Section titled “HTTP mapping philosophy”- An unmapped failure is a host mapping gap, not a domain outcome. It is answered with
UnmappedStatusCode(500) and aProblemDetailscarrying theerrorTypeextension, and it is logged. ThrowOnUnmappedFailureexists for development and keeps throwingInvalidOperationException.- An uninitialized result (
default) takes the unmapped path and is logged, because an endpoint returningdefaultis a bug. IResultsFailureMapperis the way in for rules keyed by a value rather than a type, and it must not become a catch-all that answers every failure — that hides exactly the gaps the unmapped-failure response exists to expose.
ZodSharp mapping philosophy
Section titled “ZodSharp mapping philosophy”- Code rules are consulted before category rules (each in registration order), then the default validation problem, so a rule can only narrow what the host already gets.
- A rule matches when any of the failure’s errors carries its code or category: a rule a schema can silently never reach is the kind of gap this suite surfaces rather than hides.
- A factory returns
nullto decline, and matching continues. Factories see the failure’s whole error set.
Not in scope
Section titled “Not in scope”- No exception-to-result conversion: exceptions remain for exceptional circumstances.
- No runtime union inspection: matching an error case is ordinary C# pattern matching.
- No replacement for
Result<TValue, TError>: the generator only adds call-site ergonomics. - No
Task/ValueTask-specific async combinators beyondMapAsyncandBindAsync.
Related
Section titled “Related”- Core Concepts — the state contract in detail.
- Diagnostics — the rules that enforce the union limits.
- ASP.NET Core Integration — the mapping order described above.