Skip to content

ZodSharp Integration

Preview Results Reviewed 2026-09-30 purview-dev/results Star on GitHub 1 aspnetcore c-sharp csharp discriminated-unions dotnet error-handling minimal-apis nuget problem-details result-type roslyn source-generator union-types zodsharp

Purview.Results.ZodSharp bridges ZodSharp ValidationResult<T> values into results, so validation outcomes flow through the same result pipeline as every other expected outcome instead of throwing.

Terminal window
dotnet add package Purview.Results.ZodSharp

ZodSharp validation never throws: Validate returns a ValidationResult<T> carrying the validated value on success and every ValidationError on failure. That is already a result-shaped value, but it is not Result<TValue, TError> — the failure type is fixed rather than chosen by the caller. ToResult closes that gap.

return RepositoryReconciliationResultSchema
.Validate(reconciliationResult)
.ToResult<RepositoryReconciliationResult, ReconciliationError>(errors =>
new ReconciliationResultInvalid(reconciliationResult, errors)
);

ToResult names both type arguments explicitly — including the error type — because a union case does not carry the union type that contains it:

.ToResult<RepositoryReconciliationResult, ReconciliationError>(...)

When the success type differs from the validated type

Section titled “When the success type differs from the validated type”

If the surrounding method’s success value is not the validated value, produce the failure with the generated AsFailure<TValue>() helper instead, because the validated value cannot be carried forward:

var validated = ProviderConnectionIdSchema.Validate(providerConnectionId);
if (!validated.IsSuccess)
return new ProviderConnectionInvalid(providerConnectionId, validated.Errors)
.AsFailure<RepositoryReconciliationResult>();
return await ReconcileCoreAsync(validated.Value, repositories, cancellationToken);

Carrying validation errors in the error value

Section titled “Carrying validation errors in the error value”

IValidationErrorCarrier is implemented by an error value that carries ZodSharp validation errors, so an HTTP layer can turn it into a validation problem without knowing the error type:

public readonly record struct TenantInputInvalid(TenantInput Input, ImmutableArray<ValidationError> Errors)
: IValidationErrorCarrier
{
public ImmutableArray<ValidationError> ValidationErrors => Errors;
}

IValidationErrorCarrier exposes a single ValidationErrors member, preserving each ValidationError’s code, category, path and parameters.

ZodSharp Problem Details consumes the interface; without it, a host would have to map every validation-carrying error individually.

MemberPurpose
ValidationResult<TValue>.ToResult<TValue, TError>(Func<ImmutableArray<ValidationError>, TError> onFailure)Success carries the validated value; failure carries the created error. onFailure runs only when validation failed, so a successful validation allocates no error
IValidationErrorCarrierImplemented by an error value that carries ZodSharp validation errors

A factory returning a result rather than an error is deliberately not offered: for a lambda returning Result<TValue, TError> the compiler prefers a Func<..., TError> parameter and would silently nest the results. Naming the error type explicitly keeps the intent unambiguous.

Terminal window
dotnet run --project src/examples/Examples.Zod

Examples.Zod validates a [ZodSchema] TenantInput and turns the outcome into a Result<Tenant, TenantError>, with the rejection carrying its reported ValidationErrors.