Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/wiki/Arrays-and-Other-Schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Arrays and Other Schemas

## Arrays

`ZodArray<T>` (namespace `ZodSharp.Schemas`) validates `T[]` values. Each element is validated against the element schema; failures carry index paths such as `["0"]`. A rebuilt array is produced only when a transform changed an element.

```csharp
using ZodSharp;

var numbers = Z.Array(Z.Number()).Min(1).Max(10);
var result = numbers.Validate(new[] { 1.0, 2.0, 3.0 });
```

| Method | Behaviour |
|---|---|
| `Min(int minLength, string? message)` | `too_small` when count < min |
| `Max(int maxLength, string? message)` | `too_big` when count > max |
| `Length(int length, string? message)` | exact length (both bounds) |
| `NonEmpty(string? message)` | `minLength = 1` |

## Boolean

`ZodBoolean` validates `bool`; there are no fluent methods.

```csharp
var schema = Z.Boolean();
```

## Null

`ZodNull` succeeds only for `null` input, and implements `IAcceptsNull` so it can serve as an object field accepting `null`.

```csharp
var schema = Z.Null();
```

## Enum (string values)

`ZodEnum` validates against a set of allowed strings. Failure produces `invalid_enum_value`.

```csharp
var schema = Z.Enum("admin", "user", "guest");
```

## Native enum

`ZodNativeEnum<TEnum>` validates a native `System.Enum` using `Enum.IsDefined`. Failure produces `invalid_enum_value`.

```csharp
var schema = Z.Enum<Color>(); // Color : struct, Enum
```

## Literal

`ZodLiteral<T>` (where `T : IEquatable<T>`) accepts exactly one value. Failure produces `invalid_literal`.

```csharp
var schema = Z.Literal("active");
var schema2 = Z.Literal(42);
```

## Record

`ZodRecord<TValue>` validates `Dictionary<string, TValue>`, validating every value against the value schema with key-prefixed error paths. It always produces a fresh validated dictionary.

```csharp
var schema = Z.Record(Z.Number());
```

## Tuple

`ZodTuple` validates fixed-length tuples. Two- and three-element overloads exist; input is `object?[]`.

```csharp
var schema = Z.Tuple(Z.String(), Z.Number());

var result = schema.Validate(new object?[] { "John", 30.0 });
// (string, double) success value
```

Failures: `invalid_type` on `null`, `invalid_tuple_length` on wrong length, and per-index `invalid_type` with paths like `["[0]"]`.

## Lazy

`ZodLazy<T>` defers schema construction to first use, enabling recursive and circular schemas. The inner `Schema` is resolved lazily and thread-safely.

```csharp
var categorySchema = Z.Lazy<Dictionary<string, object?>>(() =>
Z.Object()
.Field("name", Z.String())
.Field("subcategories", Z.Array(categorySchema))
.Build());
```

## Optional / Nullable

`Z.Optional<T>(schema)` (`T : class`) accepts `null` or a value matching the inner schema. `Z.Nullable<T>(schema)` (`T : struct`) is the value-type counterpart.

```csharp
var optional = Z.Optional(Z.String());
optional.Validate(null); // Success
optional.Validate("value"); // Success
```
66 changes: 66 additions & 0 deletions docs/wiki/AspNetCore-Integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# ASP.NET Core Integration

The `Purview.ZodSharp.AspNetCore` package converts failed validation results into standard `ProblemDetails` / `HttpValidationProblemDetails` payloads while preserving the structured validation issues. It also registers ZodSharp schema resolution into your application's dependency injection container.

## Install

```bash
dotnet add package Purview.ZodSharp.AspNetCore
```

## ProblemDetails

```csharp
using ZodSharp.AspNetCore;

var result = BasketSchema.Validate(basket);

if (!result.IsSuccess)
{
var problem = result.ToHttpValidationProblemDetails();
return Results.ValidationProblem(
problem.Errors,
extensions: new Dictionary<string, object?>
{
["issues"] = problem.Extensions["issues"],
});
}
```

| Member | Behaviour |
|---|---|
| `ToHttpValidationProblemDetails<T>(ValidationResult<T> result, int statusCode = 400)` | `HttpValidationProblemDetails`; error paths flattened to dotted keys (`user.email`, array indexes as `[0]`) |
| `ToValidationProblemDetails<T>(ValidationResult<T> result, int statusCode = 400)` | `ValidationProblemDetails` |

Both throw `InvalidOperationException` when the result `IsSuccess`. The structured metadata is preserved in the `issues` extension as a `ValidationIssue[]`:

```csharp
public sealed class ValidationIssue
{
public required string Code { get; init; }
public string? Origin { get; init; }
public int? Minimum { get; init; }
public int? Maximum { get; init; }
public bool? Inclusive { get; init; }
public required string[] Path { get; init; }
public required string Message { get; init; }
}
```

## Dependency injection

```csharp
builder.Services.AddZodSharp(options =>
{
options.ScanAssemblies.Add(typeof(UserDto).Assembly);
});
```

`AddZodSharp(Action<ZodSchemaFactoryOptions>? configure)` registers `IZodSchemaFactory` as a singleton, applies `options.ConfigureFactory`, and calls `factory.RegisterFromAssembly(assembly)` for each entry in `options.ScanAssemblies`. This auto-discovers source-generated `[assembly: ZodSchemaGenerated(typeof(...))]` registrations.

`ZodSchemaFactoryOptions`:

- `List<Assembly> ScanAssemblies` — assemblies to scan for generated schemas.
- `Action<IZodSchemaFactory>? ConfigureFactory` — additional factory configuration.

See [Dependency Injection](Dependency-Injection.md) for the underlying factory and options-validation wiring.
48 changes: 48 additions & 0 deletions docs/wiki/Compiled-Validators-and-Caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Compiled Validators and Caching

## CompiledValidator

`CompiledValidator` (namespace `ZodSharp.Expressions`) compiles a schema into an expression tree and a delegate.

```csharp
using ZodSharp;
using ZodSharp.Expressions;

var compiled = CompiledValidator.Compile(schema);
var result = compiled(value); // ValidationResult<T>

var parser = CompiledValidator.CompileParser(schema);
var value = parser(input); // T, throws ZodException on failure
```

| Member | Signature | Returns |
|---|---|---|
| `Compile<T>` | `Func<T, ValidationResult<T>> Compile<T>(IZodSchema<T, T> schema)` | compiled validation delegate |
| `CompileParser<T>` | `Func<T, T> CompileParser<T>(IZodSchema<T, T> schema)` | returns the value or throws `ZodException` |

The expression tree calls `IZodSchema<T, T>.Validate` on the schema bound as a constant, removing interface dispatch overhead.

## SchemaCache

`SchemaCache` (namespace `ZodSharp.Core`) is a `ConcurrentDictionary<string, object>`-backed cache for expensive schema construction.

```csharp
using ZodSharp.Core;

var schema = SchemaCache.GetOrCreate("user", () =>
Z.Object().Field("name", Z.String()).Build());
```

| Member | Behaviour |
|---|---|
| `GetOrCreate<T>(string key, Func<T> factory)` | returns the cached instance or creates and stores it (`T : class`) |
| `TryGet<T>(string key, out T value)` | typed lookup |
| `Remove(string key)` | removes an entry |
| `Count` | number of cached entries |
| `Clear()` | empties the cache |

Schemas are immutable and shareable, so caching identical definitions avoids repeated construction cost across request boundaries.

## The source generator alternative

For the highest performance, prefer the compile-time source generator: `[ZodSchema]` emits a static validator with no runtime compilation or dispatch overhead. See [Source Generator](Source-Generator.md).
109 changes: 109 additions & 0 deletions docs/wiki/Composition-and-Transforms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Composition and Transforms

Every schema derives from `ZodType<TOutput, TInput>`, so the composition methods below are available on every schema (guarded so they only apply when input equals output). Each method returns a **new** wrapping schema; the original is unchanged.

## Transform

```csharp
var schema = Z.String().Transform(s => s.ToUpperInvariant());
var result = schema.Validate("hello"); // "HELLO"
```

`ZodTransform<TInput, TOutput>` validates the input schema first, then runs the transform. If the transform throws, the exception is caught into a `transform_error` `ValidationError` (`"Transform failed: {message}"`).

Transforms chain:

```csharp
var schema = Z.String().Transform(s => s.Trim()).Transform(s => s.ToUpperInvariant());
```

`ZodString` ships convenience transforms: `.ToLower()`, `.ToUpper()`, and `.Trim()`.

## Refine

`ZodRefinement<T>` runs the base schema first, then a predicate. A failing predicate produces code `refinement_failed` (custom `message` or `"Custom validation failed"`).

```csharp
var even = Z.Number().Refine(n => n % 2 == 0, "Must be even");
```

## SuperRefine

`ZodSuperRefinement<T>` takes an `Action<RefineCtx<T>>` and can emit multiple, path-located issues.

```csharp
var password = Z.String().SuperRefine(ctx =>
{
if (!ctx.Value.Any(char.IsUpper))
ctx.AddIssue("Must contain an uppercase letter", new[] { "uppercase" });
if (ctx.Value.Length < 8)
ctx.AddIssue("too_short", "Must be at least 8 characters", new[] { "length" });
});
```

`RefineCtx<T>` exposes:

- `T Value` — the value being refined.
- `ImmutableArray<string> Path` — the base path.
- `AddIssue(string code, string message, string[]? path)` — appends to the base path; throws `ArgumentException` for null/whitespace code or message.
- `AddIssue(string message, string[]? path)` — shorthand with code `refinement_failed`.
- `Issues` / `HasIssues`.

## Pipe

`ZodPipe<TSourceOutput, TTargetOutput>` runs the source schema, then validates its output against a target schema.

```csharp
var schema = Z.String().Pipe(Z.String().Min(10));
```

## Catch

`ZodCatch<T>` swallows inner failures and returns a fallback as a **successful** result. The fallback can be a constant or a factory that receives the input and the errors.

```csharp
var withFallback = Z.String().Catch("n/a");
var computed = Z.Number().Catch((value, errors) => 0);
```

## Default

`ZodDefault<T>` substitutes a value when input is `null` and reports `IsOptional = true` and `ProvidesValueOnMissing = true`. The default is **not** re-validated.

```csharp
var schema = Z.String().Default("unknown");
var result = schema.Validate(null); // "unknown"
```

## Prefault

`ZodPrefault<T>` substitutes a value when the input equals `default(T)`, then **still validates** the substituted value through the inner schema.

```csharp
var schema = Z.Number().Prefault(1);
```

## And / Or

- `.And(other)` → `ZodIntersection<T>` — both must succeed (see [Unions and Discriminated Unions](Unions-and-Discriminated-Unions.md)).
- `.Or<TOther>(other)` → `ZodTypedUnion<T, TOther>` — either may succeed.

## Example: layered validation

```csharp
var schema = Z.String()
.Min(3)
.Transform(s => s.Trim())
.Refine(s => s.StartsWith("PUR-", StringComparison.Ordinal), "Must start with PUR-")
.Default("PUR-UNKNOWN");
```

## Behavioral differences at a glance

| Wrapper | Trigger | Re-validates substituted value |
|---|---|---|
| `Default(value)` | `null` input | No |
| `Prefault(value)` | input equals `default(T)` | Yes |
| `Catch(value\|factory)` | inner schema fails | No (returns fallback as success) |
| `Refine(predicate)` | predicate returns false | — |
| `SuperRefine(action)` | issues added to context | — |
Loading