Repository navigation
fix: describe a container of patches, and a nullable patch, as a patch property - #237
Merged
CaffeinatedCoder merged 4 commits intoOct 31, 2026
Merged
Conversation
…patch property
ASP.NET leaves out items and additionalProperties for a converter-backed
element, and PatchSchemaTransformer acted only on a Patch<T> itself. So a
Dictionary<string, Patch<string>> was {"type":"object"} and a
List<Patch<int>> {"type":"array"}, admitting anything, in 3.1, 3.0 and
2.0, while a Patch<T> property was its argument's schema admitting null.
The container branch now puts the patch's own schema back where ASP.NET
left it out, asked for through the context, so it is filled as a property
is: a described or replaced argument, a value object through its wrapped
type, 3.0's nullable form.
Input class: where a Patch<T> sits. Catalogue: PatchSchemaTests'
arguments (12 kinds, now 14 with Lines[] and Dictionary<string, int>, a
list of a component and a dictionary) in 6 positions (property, list,
array, Dictionary<string,>, IReadOnlyDictionary<string,>, a list in a
dictionary) plus a list as the whole body, in a minimal API and an MVC
controller, over OpenAPI 3.1, 3.0 and 2.0 and the three value-object
placements (components, InlineValueObjects, inlined by the host). A host
that inlines everything has no polymorphic argument: ASP.NET refuses a
plain property of one too. A wire test reads a map and a list of patches
as the document says they are read. The nullable patch the catalogue
found is the next commit.
Reverted per verify-the-guard: the 11 new document rows fail; the
dictionary arm alone fails them too.
Closes #232
Defect-Class: FCI
Found-By: CA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an empty required component Found running the container fix over its input class: a Patch<T>? property was an empty PatchOfstring component, admitting anything, and required, and a List<Patch<T>?> had no items. The transformer asked only whether a type was Patch<T> itself; it now sees through Nullable<T>, so a nullable patch is inlined, optional, and its argument's schema admitting null, as an element and as a property. It is not described as a merge-patch field: System.Text.Json reads a null into a Nullable<Patch<T>> as null, never the Null state, the same as an absent property (measured by a request in the wire test). The docs say so and point to Patch<T>. Two quirks of the same cause: the serializer gives a Nullable<T> the element type T. So the container check read a nullable patch as a container, and now reads the kind first; and ASP.NET visits a nullable patch's items and additionalProperties as the patch, which made a Patch<int[]>?'s numbers admit null. Those schemas are recorded and skipped. A registration of a reference's target no test caught was measured unneeded (the Lines component stays clean) and dropped. Catalogue: the previous commit's, with a nullable element and a nullable property (8 positions). Reverted per verify-the-guard: 11 rows fail; each of the nullable argument, the kind check, the description and the guard's items and additionalProperties arms fails 9 or 11. Refs #232 Defect-Class: FCI Found-By: VA Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fix of #232 turned on two facts of System.Text.Json: a Nullable<T> contract reports T as its element type, which Microsoft.AspNetCore.OpenApi walks items and additionalProperties with, and a JSON null read into a T? is null without asking T's converter, even one that handles null. The second is what the merge-patches page now tells a reader of Patch<T>?. KB057 characterizes both against a plain T as the control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #232
Problem
ASP.NET leaves out
itemsandadditionalPropertiesfor a converter-backed element, andPatchSchemaTransformeracted only on aPatch<T>itself. So in 3.1, 3.0 and 2.0 aDictionary<string, Patch<string>>was{"type":"object"}and aList<Patch<int>>was{"type":"array"}, and a client generator typed a map of patches as a map of anything. APatch<T>property was described properly.Running the fix over its input class found a sibling with another cause. A
Patch<T>?property was an emptyPatchOfstringcomponent ({}), and required. AList<Patch<T>?>had noitems.Change
src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs, in two commits:Found-By: CA). A list, an array or a dictionary of patches gets the patch's own schema back where ASP.NET left it out. The schema is asked for through the context, so it's filled as a property's is: a described or replaced argument, a value object through its wrapped type, 3.0'snullable.Found-By: VA). A patch is nowPatch<T>or itsNullable, so aPatch<T>?is inlined, optional, and its argument's schema admitting null. It isn't described as a merge-patch field: System.Text.Json reads anullthere as anullpatch, the same as a property left out (measured by a request). The docs say so and point toPatch<T>. The serializer gives aNullable<T>the element typeT, and that caused two more problems, both fixed. The container check reads the kind first. ASP.NET's second visit to a nullable patch'sitems/additionalPropertiesas the patch is skipped; before, aPatch<int[]>?'s numbers admitted null.Docs:
docs/merge-patches.mdand the AspNetCore README say how a container of patches and a nullable patch are described.design/patch.md's row, a CHANGELOG line under 2.3.1 "Fixed", 5 claims intest/oracles/claims.json(all held byPatchSchemaTests), andPatchSchemaTestsinaxes.mdfor the version and placement axes.Tests
PatchSchemaTests:A_patch_in_any_position_is_described_as_a_patch_property_is: the catalogue's 14 arguments (now withLines[]andDictionary<string, int>) in 8 positions (property, list, array,Dictionary<string,>,IReadOnlyDictionary<string,>, a nullable element, a list in a dictionary, a nullable property), plus a list as the whole body, and an MVC controller. It runs over OpenAPI 3.1, 3.0 and 2.0 × components /InlineValueObjects/ inlined by the host (9 rows). A host that inlines everything has no polymorphic argument, because ASP.NET rejects a plain property of one too.A_patch_in_a_container_accepts_null_and_otherwise_exactly_what_its_argument_accepts: a JSON Schema validator judges each element against the argument's own schema (3.1, 3.0).A_map_of_patches_reads_a_value_and_a_null_as_the_document_says: a real request, so the document's words match what the endpoint reads.Reverted per
verify-the-guard: with the fix reverted, the 11 new document rows fail. Seeded one at a time, the dictionary arm, the nullable argument, the kind check, the nullable description and the guard's items and additionalProperties arms each fail 9 or 11. A registration of a component target that no seed caught was measured unneeded and dropped.Locally through
tools/BuildVerdict: AspNetCore 2088 passed, Conventions 2016, Samples 150.🤖 Generated with Claude Code