Skip to content

nil_fields: nullable-and-optional default, per-call option; null list elements; doc and message fixes - #204

Merged
andreashasse merged 11 commits into
mainfrom
andreashasse/nil-fields-per-call
Oct 31, 2026
Merged

andreashasse merged 11 commits into
mainfrom
andreashasse/nil-fields-per-call

Conversation

@andreashasse

Copy link
Copy Markdown
Owner

Draft: waiting for user feedback before merge and release. This is one of four aligned PRs. The Elixir ones depend on the others through git branches, so the whole set can be tried together. Nothing is released yet; the git dependencies go back to Hex versions after feedback.

nil_fields has three values. It applies to a field whose type can be missing (T | undefined, or Elixir T | nil).

{spectra, [{nil_fields, ...}]} Schema Encoding of the missing value Allowed per-call {nil_fields, _}
omit_or_null (new default) not required, allows null left out null or omit
omit (the old default) not required, no null, the old schema left out omit
null (0.16) required, allows null null null
  • Per-call option: encode/5 accepts {nil_fields, null | omit}, which changes only that call's encoding, nested values included. Schemas and OpenAPI always follow the global setting. Any one type therefore has one schema, and a type shared by an API response and a webhook payload needs no second component.
  • Rejected combinations: an option that would break the schema raises badarg with error_info, which Elixir sees as ArgumentError. The raised error does not carry the encoded data, so it cannot end up in logs.
  • Upgrading: with no config change, encoding output stays the same. Schemas of fields that can be missing gain null, which matches what the decoder already accepted. Set nil_fields to omit to keep the old schema.
  • Null placement: a missing value at the top level, or as a list element such as [integer() | nil], now encodes as null instead of "nil" or "undefined". Its schema allows null, except under omit. allow_null is idempotent, so a field typed through a nullable alias gets one null, not two.

Fixes:

  • A documented parameter or response header no longer repeats description and deprecated inside its schema.
  • Error ctx messages are always binaries.

make build-test passes (833 tests, eqwalizer, lint, dialyzer and the rest), as do make doc and rebar3 proper. The encoded output is validated against the generated schema with jesse, and the OpenAPI output with openapi-spec-validator.

🤖 Generated with Claude Code

andreashasse and others added 6 commits October 31, 2026 01:08
The nil_fields setting gets a new default, omit_or_null: the schema of a
field that can be missing allows null but does not require it, and the
encoder leaves the field out unless the call passes {nil_fields, null}.
omit keeps the pre-0.16 schema (no null) and rejects {nil_fields, null}
with badarg, formatted via spectra:format_error/2 so Elixir gets an
ArgumentError with a clear message. null is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An element such as nil in [integer() | nil] was encoded as the string
"nil". It is now null, which decodes back to the missing value, and the
JSON Schema of such a list item allows null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A documented parameter or response header got its description and
deprecated flag both on itself and inside its inline schema. They are now
removed from the schema when lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several error contexts carried their message as a charlist. They are now
binaries, and the ctx type in spectra.hrl says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…allow null in nullable union schemas

- {nil_fields, omit} with the nil_fields setting null now raises badarg,
  since the schema requires the fields it would leave out.
- The nil_fields badarg is raised with the arity instead of the arguments,
  so the encoded data does not end up in logged stack traces.
- A union with nil/undefined, such as a top-level body or OpenAPI
  component, gets a schema that allows null unless nil_fields is omit.
  allow_null is idempotent, so fields and list items typed with such a
  union get a single null.
- Test the per-call option with a type info value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
andreashasse and others added 5 commits October 31, 2026 01:33
A parameter or header value is never JSON null, and required: false
already says it can be left out. A union with nil/undefined used as a
path, query, header or cookie parameter or as a response header now gets
the schema 0.16.0 gave it, under every nil_fields setting. A component
that only parameters use gets that schema too; a type that is also used
as a body or field keeps null there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Give a list payload's item type no Nullable copy, pass the mixed refs to
generate_components, use one error cause shape, test that webhook
responses keep the plain schema, and place the README exception after
the badarg rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@andreashasse
andreashasse marked this pull request as ready for review October 31, 2026 08:01
@andreashasse
andreashasse merged commit 2df3b9a into main Oct 31, 2026
3 checks passed
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