Skip to content

Net11 extensions - #62

Merged
kieronlanning merged 118 commits into
mainfrom
net11-extensions
Nov 2, 2026
Merged

kieronlanning merged 118 commits into
mainfrom
net11-extensions

Conversation

@kieronlanning

Copy link
Copy Markdown
Collaborator

No description provided.

…age formatting

- ZodPath/ZodPathSegment: typed member/index/key segments with one renderer
  (Items[0].Name), JSON Pointer, JSONPath, configuration keys and parsing
- ValidationError: FullPath, Lower/Upper ErrorBound, MessageFormat,
  WithPathPrefix, sequence-based equality; legacy Path keeps Zod's "0" form
- ZodMessageFormat: lenient named/positional formatting that never throws;
  RuleMessage uses it and formats with the invariant culture
- ZodException lists issues with dotted paths; Merge tolerates default results
- ZodIssueCategories/ZodIssueParameters constants
- ProblemDetails keys use the canonical renderer
- CI SDK pinned to 11.0.100-rc.3 to match global.json
… by default (G1)

The generated check was inverted: the default AllowEmptyStrings=false only rejected null while
AllowEmptyStrings=true rejected empty strings. It now follows RequiredAttribute.
…converter errors

- J1/J5: map JsonException/JsonReaderException/JsonSerializationException paths via
  ZodPath.ParseJsonPath, add line/position parameters, fixed generic message naming the expected
  Zod type
- J2: single invalid_json code (category invalid_type) replaces
  json_error/deserialization_failed
- J3: map STJ NotSupportedException/InvalidOperationException raised while deserializing
- J4: ZodJsonValidationException (STJ) and ZodNewtonsoftJsonValidationException carry full
  errors under the converter's JSON path; DeserializeAndValidate unwraps them
- J6: Newtonsoft DeserializeAndValidateAsync awaits ValidateAsync
- J7-J10: JSON Schema import re-paths with typed index/key segments, mirrors ZodArray length
  failures, reports JSON kinds with Zod type names and no longer echoes literal input
Benchmarks did not exercise [ZodSchema] validation; the project now loads the merged generator
explicitly because it does not import Purview.BuildSdk.
…rules

- Re-path every element/field/catchall/record/tuple error with WithPathPrefix (A1, A2, A4)
- Separate array min/max/length messages reporting the count (A3); tuple arity bounds,
  index-prefixed element errors and null pass-through (A5, A6)
- Union issues grouped per option under unionErrors, kept by typed/native unions (A7, A8);
  discriminator errors at the discriminator path in declaration order (A9, A10)
- Shared identities: ZodIssueCodes, ZodMessages, ZodIssues, ZodTypeNames (A11-A17);
  one aggregated unrecognized_keys issue (A13); typed RefineCtx issues (A18, A19);
  intersection de-dup (A20); span rule errors via ZodRuleErrors (A21)
- Every built-in rule reports origin, Zod category, bounds with inclusivity and parameters;
  IZodFailureDescriber for direction-dependent categories (B1-B4)
- Default messages never echo the input; ZodErrorOptions.IncludeReceivedValues (B9);
  consistent UUID arguments, null custom-message default, invariant formatting (B5-B8);
  RequiredRule shares the "{displayName} is required" identity (B10)
…gistration, JSON paths)

Adds ZodValidationDepth (MaxDepth 32, M.E.V semantics),
ZodGeneratedValidation/ZodGeneratedMember (runtime message formatting, typed nested re-pathing,
reflection-free member walking), IZodJsonPathMapper, ZodSchemaRegistration +
AddZodSchemaRegistration (applied by AddZodSharpFactory), ZodAsyncMemberAttribute and
RegexRule.DefaultMatchTimeoutMilliseconds.
…ion and document transformers

Describes what ZodSharp enforces in ASP.NET Core 11 OpenAPI documents. Core JSON Schema export
gains method-based vendor extensions, ConvertUntyped, discriminated-union oneOf export,
inner-schema unwrapping for validator adapters, and JSON Schema mappings for the IP, CIDR, JWT,
duration, base64, date, date-time and time string rules.
# Conflicts:
#	src/src/ZodSharp/Rules/Base64StringRule.cs
#	src/src/ZodSharp/Rules/CidrRule.cs
#	src/src/ZodSharp/Rules/DateStringRule.cs
#	src/src/ZodSharp/Rules/DatetimeStringRule.cs
#	src/src/ZodSharp/Rules/DurationRule.cs
#	src/src/ZodSharp/Rules/IPAddressRule.cs
#	src/src/ZodSharp/Rules/JWTRule.cs
#	src/src/ZodSharp/Rules/TimeStringRule.cs
#	src/src/ZodSharp/Schemas/ZodDiscriminatedUnion.cs
- ZodSchemaOptionsValidator<T> implements IAsyncValidateOptions<T> on .NET 11;
  ValidateOnStart runs async and service-aware rules in a scope, synchronous
  access checks the synchronous rules only (the platform contract).
- OptionsBuilder<T>.AddZodSchemaValidator registers through Validate<TValidator>()
  on .NET 11 and validates only the builder's options name; new overload with
  validateOnStart and configurationSection.
- Failures read "{name}: {location}: {message} ({code})", using configuration
  keys (Smtp:Servers:0:Host) when a section is recorded (defect O1);
  ZodValidateOptionsResult keeps the full errors.
- Core IZodMessageFormatter / DefaultZodMessageFormatter localization hook and
  the shared ZodPathNaming enum.
- Microsoft.Extensions.Options 11 on net11.0; TestHost versions per TFM.
…and typed results

- ZodProblemDetailsWriter is the single implementation behind the endpoint
  filter, ZodExceptionHandler, ZodTypedResults and every To*ProblemDetails /
  ToValidationProblem overload; a contract test proves byte-identical bodies.
- Keys from FullPath.ToDottedString with ZodPathNaming; messages through
  IZodMessageFormatter in the request culture; ErrorType templates only when
  complete; FormatMessages honoured; StatusCodeSelector/StatusCodeOverride win;
  reserved members protected; per-issue camel-cased safe parameters; full MVC
  copies; traceId everywhere; safe ValidationIssue DTO with segments, typed
  bounds and unionErrors (defects D1-D10).
- WithZodSharpValidation() on any IEndpointConventionBuilder (route groups
  included) via an endpoint filter factory: build-time parameter selection and
  validator resolution, every argument validated, build-time missing-validator
  failures, .NET 11 binding-failure pass-through or conversion, and 400
  problem+json response metadata (defects D6, D11, D12).
- ZodTypedResults.ValidationProblem/Problem, ToValidationProblem(HttpContext),
  options overloads, Apply() for merging foreign errors (for the
  Microsoft.Extensions.Validation bridge), source-generated JSON metadata for
  Native AOT, and BindAndValidateWithZod(section).
- Skill and package README updated; CHANGELOG records the behaviour changes.
…osoft.Extensions.Validation

New net11.0 package that registers ZodSharp as the first .NET 11 validation resolver, so minimal
APIs, Blazor Server and Blazor WebAssembly validate ZodSharp types with ZodSharp.

- AddZodSharpValidation(): registers the factory and ZodValidatableInfoResolver, and places the
  resolver through IPostConfigureOptions<ValidationOptions>, so it is first whether
  AddValidation() is called before, after or not at all (proved against the real M.E.V generator
  interceptor).
- Type, property and parameter adapters: Container/Name resolution (IZodMemberResolver,
  reflection fallback, dictionary-backed schemas), CurrentValidationPath prefixing, MaxDepth
  parity, sync Validate throwing on async-only rules, M.E.V parameter path conventions, union
  case following.
- Rich-error side channel: ValidateContext.ZodReport and IZodValidationReportSink.
- StringLocalizerZodMessageFormatter, auto-enabled with an IStringLocalizerFactory.
- ValidationOptions.ValidateAsync<T> for non-HTTP hosts.
- IZodMessageFormatter in ZodSharp.Core.
# Conflicts:
#	Directory.Packages.props
#	src/src/ZodSharp/Core/IZodMessageFormatter.cs
…add OpenAPI test suite

Adds AspNetCore.OpenApi.UnitTests (net11.0): every keyword mapping row for OpenAPI 3.1 and 3.2,
conflict policy and Development logging, unknown types untouched, polymorphic oneOf with
resolvable case references, C# union cases constrained without restructuring, operation
responses (body, [AsParameters], union, WithZodSharpValidation, ProducesValidationProblem merge,
422 from the registry) and a JsonSchema.Net cross-check of fixture payloads against the emitted
document. Registers the package in the solution, pack validation and the changelog.
# Conflicts:
#	.config/purview-build.json
#	.csharpierignore
#	CHANGELOG.md
…, async graph, member lookup

Change 1: Validate calls one ValidateMember_X method per member. Change 2:
ValidateMember/ValidateMemberAsync/IsValidatableMember (no nested recursion). Change 3:
TryResolveMember (reflection-free). Change 4: Paths constants, typed index/key segments,
WithPathPrefix, JsonNames + ToJsonNamedPath, MessageFormat + parameters formatted at runtime
with ZodMessageFormat. Change 5: per-assembly Register/AddZodSharp{Assembly}Schemas, adapters
implement IZodUntypedValidator, depth guard. G11 async graph, G23
IValidatableObject/IAsyncValidatableObject/AsyncValidationAttribute, Change 9 service-aware
custom method + [ZodAsyncMember], Change 7 union/closed dispatch, G21 options failures.
…ation pipeline

On net11.0 AddZodSharpProblemDetails() registers a scoped ZodRequestValidationReport as the
bridge's IZodValidationReportSink and post-configures ProblemDetailsOptions.CustomizeProblemDetails
so the ZodSharp callback runs before any application callback. Pipeline responses are rewritten
with ZodProblemDetailsWriter.Apply (DataAnnotations entries merged, ErrorType/selector/override
status mirrored to the response), and DataAnnotations-only failures gain only the issues projection.

WithZodSharpValidation() skips parameters the bridge already validates on endpoints the pipeline
covers, logged once at Debug. The AspNetCore package references Extensions.Validation for
net11.0 only. The contract test now covers Mode A alongside the filter, exception and manual paths.
…ion context and tests

ZODSGEN047-052 (+ code fix for 047), G22 ErrorType.Origin and MessageFormat,
CreateValidationContext without reflection, a JSON Schema rule-construction fix for
argument-less rules, and tests: one per G defect, ValidateMember parity, TryResolveMember round
trip, registration/untyped validators, service-aware and member-targeted async, depth guard,
.NET 11 async DataAnnotations, unions, closed hierarchies, IAsyncValidateOptions and pipeline
caching. Benchmarks add member validation and member resolution.
scripts/test-packed-consumers.ps1 (just smoke-packed-consumers) packs every shipping
package to an isolated feed and package cache, then publishes and runs consumers that
reference packages only: a Native AOT console, a Blazor WebAssembly app (trimmed, and
RunAOTCompilation when wasm-tools is installed) and a Native AOT minimal API.

scripts/assert-no-compiler-host-assemblies.ps1 scans build and publish output by file
name and by deps.json, runtimeconfig.json and boot manifest content. Every publish job
and recipe now runs it, and the new packed-consumers PR job runs the gate and the
forbidden-content rule proof. It fails first, with what to do, if
Purview.SourceGeneratorFramework cannot be restored.

Release-Flow.md lists every gate and a pre-release checklist; AGENTS.md matches.
The leak scan must catch each kind of leak it claims to (file names, fingerprinted and
compressed WebAssembly files, deps.json, runtimeconfig.json and boot manifests), must
fail when it has nothing to scan, and -Allow must not hide a leak elsewhere. Every
workflow job that publishes must run the scan, the packed-consumers job must run the
gate and the rule proof, and ForbiddenContent must keep every rule.
ZodType<TOutput, TInput>.Or returns ZodTypedUnion<TOutput, TOther>, which derives from
ZodType<Union<TOutput, TOther>, object>, and Union is a struct, so rooting expands
Union<Union<...>> without bound. Every change that breaks the cycle breaks the 2.4.2 API:
sealing is CP0009 and, measured, still leaves 32 IL3054 warnings. The tolerance stays,
scoped to the rooted mode.
…Transitive

PrivateAssets="none" on Microsoft.AspNetCore.OpenApi flowed its build/ targets into
in-repo ProjectReference consumers as well, where its XML-comment target fed duplicate
doc IDs to the generator and every AspNetCore.OpenApi unit test failed with a 500.

Revert that and ship buildTransitive/Purview.ZodSharp.AspNetCore.OpenApi.targets instead:
it adds only the Microsoft.AspNetCore.OpenApi.Generated interceptor namespace, which is
what a package-only consumer lacked (CS9137). A direct Microsoft.AspNetCore.OpenApi
reference still builds; the namespace is then listed twice, which is harmless.
…le proof input

publish-aot-sample, verify-trim and wasm-size now leak-scan the project's bin/Release as
well as the publish folder, as the CI jobs do. verify-pack-rules reads artifacts/, which
pipeline-local-release empties into the local feed, so say to pass -PackagesFolder there.
…f generating them

The generators emitted ZodSchemaAttribute and ErrorTypeAttribute, public and documented, into every
compilation as post-initialization output. Every project in a solution then documented the same
type IDs, and the ASP.NET Core OpenAPI XML-comment generator - which reads an app's own public and
internal types plus each referenced project's XML file - added the same key twice and threw:
"An item with the same key has already been added. Key: T:ZodSharp.Core.ErrorTypeAttribute".

[Embedded] already prevented CS0436, and internal copies would still have collided, so both
attributes are now ordinary public types in Purview.ZodSharp, with the members the package already
exposed as generated [Embedded] copies, and the generators emit no marker types at all. 2.x is
affected too (reproduced with 2.4.2 on net10.0).
…umer gate

Consumer (d) is a [ZodSchema]/[ErrorType] class library referenced by a minimal API that references
Microsoft.AspNetCore.OpenApi directly, with XML documentation on in both projects. The gate
publishes and starts it, checks the OpenAPI comment cache has no duplicate documentation ID and no
ZodSharp marker attribute, and asserts /openapi/v1.json carries both projects' schemas, the ZodSharp
constraints and the XML-comment descriptions, plus the library's issue paths and ErrorType at
runtime. Against the previous packages it fails with the duplicate-key crash.
BuildSdk 1.2.0 rewrites its managed .editorconfig and adds PRBLZ0001: every project a Blazor
WebAssembly client references must be browser-safe. Purview.ZodSharp, Extensions.Validation, Blazor
and Examples.Shared run in the browser, so they set PurviewBrowserSafe=true, which turns the
browser-unsupported API, trim and AOT diagnostics into errors; all four build clean under them.
… bind

The generator emitted value.Member == null and != null. A [Required] Nullable<T> member whose type
defines ==(string?, T) plus an implicit conversion to string (every string-backed value object)
failed with CS9342, and a reference type with a ==(T?, string?) overload failed the same way. Every
emitted null test is now is null / is not null. 2.4.2 emitted the same comparison.
An adapted or required rule's identity was emitted as (object)rule as IZodRule, a runtime type test
on a boxed struct that crashed the Mono AOT compiler (0xC0000005) for a scalar with a type-level
[ZodRule]. The generator knows whether the rule implements IZodRule, so it emits (IZodRule)rule, or
null when it does not. The packed Blazor WebAssembly consumer now carries such a scalar.
An error inside a value object (Email.Value) resolved to a boxed copy of the struct, which cannot
be a FieldIdentifier model, so DataAnnotationsValidator and Server-side ZodValidator dropped the
message and WebAssembly showed it only in the summary. The generated TryResolveMember, the
validation bridge and the Blazor field locator now attribute it to the nearest reference-type
owner and its member. A nested scalar's messages also use the owning member's display name.
The 2.x overloads that take only a status code, a registry or a lookup (and ToValidationProblem())
cannot see the ZodProblemDetailsOptions an application registers, so their body diverged from the
exception handler. There is no ambient options instance to read without a request, so they keep
their 2.x output, are marked [Obsolete] with the exactly equivalent options, and point to the
HttpContext and options overloads.
…ibutes

ZodSharp 2 generated [Embedded] copies of ZodSchemaAttribute and ErrorTypeAttribute into every
compilation, so a library rebuilt against ZodSharp 3 loses them and package validation against a
2.x baseline reports CP0001. The upgrade note explains why the removal is safe and gives the
suppression command and entries.
…ation

The .NET 11 endpoint filter validates every parameter a registered resolver claims, so registering
the bridge from AddZodSharpBlazor answered invalid requests before handlers ran. AddValidation()
alone adds nothing. AddZodSharpBlazor now registers type validation only;
AddZodSharpValidation(configure, validateParameters) and ZodValidationOptions.ValidateParameters
choose explicitly, and any registration that asks for parameters wins.
…issues

Generated validators attach declaringType (a full CLR type name) and member to every error for
localization, and issues[].parameters sent both to API clients. They are now omitted by default and
stay on ValidationError.Parameters; ZodProblemDetailsOptions.IncludeDiagnosticParameters writes
them again.
Every generated schema now has ValidateAsync, so CA1849 reports each Validate call in async code.
The generated Validate remarks now say whether the type requires async validation, and the upgrade
notes recommend awaiting ValidateAsync, which completes synchronously for such types, instead of
suppressing the analyzer.
….claude copy

The skills installed from other repositories were committed twice, once under .agents/skills/ and
once under .claude/skills/. The two trees were identical, so the .claude copy is removed and
/.claude/ is now gitignored. This repository is not agent-specific.

The installed set was refreshed with the skills CLI, into the universal location only:

    npx -y skills@1 add purview-dev/build-sdk -s '*' -a universal -y
    npx -y skills@1 add purview-dev/sourcegenerator-framework -s '*' -a universal -y
    npx -y skills@1 add purview-dev/engineering-playbook -s '*' -a universal -y

The six purview-sourcegenerator-* skills are new; AGENTS.md already told contributors to load
them, but they were never installed. The build-sdk and engineering-playbook skills are updated to
their current content.

src/Directory.Build.props sets PurviewSkillsAgents to universal, so the SDK's InstallPurviewSkills
target no longer defaults to writing a copy for every agent. InstalledSkillTests checks that the
lockfile and .agents/skills/ agree, that no authored skill is copied there, and that neither a
.claude/skills/ folder nor a CLAUDE.md exists.
AGENTS.md said .agents/skills/ was gitignored. It is committed, together with skills-lock.json,
and is written only by the skills CLI with -a universal.

A new Installed skills section lists each skill, its source and when to load it, gives the
commands to add, update and restore them, and states the rule that no .claude/ folder or
CLAUDE.md is committed. The scattered npx skills add hints now point at that section, and the
copilot instructions say the same.
…r installed here

AGENTS.md now says plainly that skills/ is authored for consumers of the packages, so it is
never installed into this repository's .agents/skills/; InstalledSkillTests enforces it.
@kieronlanning
kieronlanning merged commit cfdfc83 into main Nov 2, 2026
7 checks passed
@kieronlanning
kieronlanning deleted the net11-extensions branch November 2, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant