Skip to content

OpenAPI: a dictionary or list of Patch<T> describes its elements as anything, with no additionalProperties or items #232

Closed

Description

@CaffeinatedCoder

Found by a pre-release sweep of release/2.3.1 @ dbdd44e plus #216, #217 and #218 as landed (lens: framework-heuristics); reproduced in a scratch OpenAPI test.

Package

CodoMetis.TypeKit.AspNetCore

Version

2.3.1 as above. Traced to 2.3.0 and not run there: the transformers have not changed since v2.3.0.

How does it fail?

Silently. The document describes a map or list of patches as admitting anything.

Problem

ASP.NET leaves out the element schema of a collection or dictionary whose element has its own converter, as Patch<T> does. Neither TypeKit transformer puts it back for a patch:

  • PatchSchemaTransformer.TransformAsync acts only when the type itself is a patch (src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs:92).
  • The container branch of ValueObjectSchemaTransformer restores items/additionalProperties for a value-object element only (src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs:142-148).

The README and docs/merge-patches.md describe "a property typed Patch<T>", and neither names containers as a limit. A merge patch over a map ("a value sets the key, null clears it") is the shape this produces.

Scenario

public sealed record LabelsPatch(Dictionary<string, Patch<string>> Labels, List<Patch<int>> Quantities, Patch<string> Name);

app.MapPatch("/labels", int (LabelsPatch patch) => 0);   // AddOpenApi(o => o.AddTypeKit())

Measured on 2.3.1:

  • OpenAPI 3.1: labels: {"type":"object"}, quantities: {"type":"array"}, while name: {"type":["null","string"],…}.
  • OpenAPI 3.0: the same labels and quantities, and name with "nullable":true.

So a client generator types labels as Dictionary<string, object>.

Change

In the container branch, also handle an element that is a Patch<> (or a nullable one), and fill items/additionalProperties only where ASP.NET left them out. Use the argument's schema with null admitted (PatchSchemaTransformer.AdmitNull), which is what a Patch<T> property gets: a described or replaced argument, a value object inside the patch through its wrapped type, and OpenAPI 3.0's nullable form. If a Patch<T> element is not meant to be supported on the wire (an Absent element has no JSON), say so in the README and refuse it in the document rather than describe {}.

Test

Input class: the containers ASP.NET describes (List<>, arrays, Dictionary<string,>, IReadOnlyDictionary<,>) of Patch<T> over the PatchSchemaTests value catalogue, in OpenAPI 3.1, 3.0 and 2.0. The element schema accepts null and otherwise judges each value as the argument does. Reverted per verify-the-guard.

Environment

.NET SDK 10.0.4xx, Microsoft.AspNetCore.OpenApi 10; any consumer.

Found while fixing (#237)

  • Elements are supported on the wire: a value sets a key or an element, and a null reads as the Null state. An element is never Absent on read. So the element is described as a patch property is, in 3.1, 3.0 and 2.0, and nothing is rejected.
  • Sibling, same input class (Found-By: VA): a Patch<T>? property was an empty, required PatchOfstring component, and a List<Patch<T>?> had no items. Both are now the argument's schema admitting null, without the merge-patch description. System.Text.Json reads a null into a Nullable<Patch<T>> as null, not as Null, the same as a property left out. The docs say to use Patch<T>.
  • A host that inlines every schema (CreateSchemaReferenceId = _ => null) can't describe a polymorphic argument at all. ASP.NET throws for a plain property of one too, so that's not TypeKit's.

Activity

  1. added this to the 2.3.1 milestone on Oct 31, 2026
  2. added
    bugSomething isn't working
    silentCompiles or answers and does less than it appears to
    severity/lowCosmetic, message or documentation-level difference
    package/aspnetcoreAffects CodoMetis.TypeKit.AspNetCore
    on Oct 31, 2026
  3. CaffeinatedCoder commented on Oct 31, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Fixed by #237, merged into release/2.3.1 (cc72fa4); ships in 2.3.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpackage/aspnetcoreAffects CodoMetis.TypeKit.AspNetCoreseverity/lowCosmetic, message or documentation-level differencesilentCompiles or answers and does less than it appears to

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions