OpenAPI: type object fields as an open map, not an empty object #24

Closed
opened 2026-06-03 21:16:47 +00:00 by logaritmisk · 0 comments
Owner

Context

AdminObjectView.fields (and the analogous flexible-field maps on PublicView and the ObjectCreateRequest/set_fields request body) are annotated on the Rust side with #[schema(value_type = Object)]. utoipa emits that as a bare {"type":"object"} with no additionalProperties, which openapi-typescript narrows to Record<string, never> — i.e. the generated TS type says the object can have no keys.

This surfaced while building the frontend SPA (milestone 1): the object-detail view must read flexible field values out of fields, but the generated type makes that impossible without a cast. The interim workaround in the web client is Object.entries(object.fields as Record<string, unknown>) in web/src/objects/object-detail.tsx, plus test fixtures that return fields as plain object literals.

What to do

Annotate the flexible-field maps so the OpenAPI document describes an open string→any map (additionalProperties present), so openapi-typescript generates { [key: string]: unknown } / Record<string, unknown> instead of Record<string, never>. Likely options in utoipa: use #[schema(value_type = HashMap<String, serde_json::Value>)] (or an equivalent that yields additionalProperties) on the fields field of AdminObjectView, PublicView, and any request DTO carrying a free-form field map. Verify the emitted schema has additionalProperties and regenerate web/src/api/schema.d.ts.

Acceptance

  • The OpenAPI JSON shows fields as an object with additionalProperties (not an empty closed object).
  • Regenerated schema.d.ts types fields as an open map; the as Record<string, unknown> cast in object-detail.tsx (and any fixture workarounds) can be removed.
  • Backend tests stay green.

Source: frontend SPA milestone 1 implementation (the typed client consumes this contract).

## Context `AdminObjectView.fields` (and the analogous flexible-field maps on `PublicView` and the `ObjectCreateRequest`/`set_fields` request body) are annotated on the Rust side with `#[schema(value_type = Object)]`. utoipa emits that as a bare `{"type":"object"}` with no `additionalProperties`, which **`openapi-typescript` narrows to `Record<string, never>`** — i.e. the generated TS type says the object can have *no* keys. This surfaced while building the frontend SPA (milestone 1): the object-detail view must read flexible field values out of `fields`, but the generated type makes that impossible without a cast. The interim workaround in the web client is `Object.entries(object.fields as Record<string, unknown>)` in `web/src/objects/object-detail.tsx`, plus test fixtures that return `fields` as plain object literals. ## What to do Annotate the flexible-field maps so the OpenAPI document describes an **open string→any map** (`additionalProperties` present), so `openapi-typescript` generates `{ [key: string]: unknown }` / `Record<string, unknown>` instead of `Record<string, never>`. Likely options in utoipa: use `#[schema(value_type = HashMap<String, serde_json::Value>)]` (or an equivalent that yields `additionalProperties`) on the `fields` field of `AdminObjectView`, `PublicView`, and any request DTO carrying a free-form field map. Verify the emitted schema has `additionalProperties` and regenerate `web/src/api/schema.d.ts`. ## Acceptance - The OpenAPI JSON shows `fields` as an object with `additionalProperties` (not an empty closed object). - Regenerated `schema.d.ts` types `fields` as an open map; the `as Record<string, unknown>` cast in `object-detail.tsx` (and any fixture workarounds) can be removed. - Backend tests stay green. _Source: frontend SPA milestone 1 implementation (the typed client consumes this contract)._
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#24