Skip to content

RestApiHandler emits compound ids it cannot parse back when a nullable id field is null #2716

@lsmith77

Description

@lsmith77

Description and expected behavior

RestApiHandler serialization and parsing of compound resource ids are asymmetric for nullable id fields, so a record carries an id in its own JSON:API response that the handler then rejects with HTTP 400 on GET /resource/{thatId}.

  • Serialize (makeCompoundId): idFields.map(f => item[f.name]).join(idDivider). A null segment becomes '', so a record whose second id field (Int?) is null is emitted with id "200-01|".
  • Parse (makeIdFiltercoerce): for an Int/Float field, parseInt('')NaNInvalidValueError → 400.

Net effect: any model with a nullable field in its external/compound id has records that are not addressable by the very id the API returns for them.

Steps to reproduce:

  1. Model with @@unique([a, b]) where b is nullable (Int?), exposed via REST with that pair as the external id (externalIdMapping).
  2. Create a record with b = null.
  3. GET /things → that record's id comes back as "<a>|".
  4. GET /things/<a>| → 400 (invalid Int value: ).

Expected: the round-trip succeeds — an empty segment for an optional field parses as SQL NULL, and the record is returned (findUnique matches the null row; verified that the ORM lowers null to IS NULL). At minimum, the id the handler emits must be one it can accept back.

Suggested fix: make coerce symmetric with makeCompoundId (empty segment + optional field → null), or expose a public/overridable coerce (or per-field id-encoding hook) so consumers can fix it without patching a private method. Note: "" vs. a genuine empty-string segment is ambiguous for String id fields; it is unambiguous for typed-nullable fields (Int?/Float?), the common case.

Screenshots

N/A.

Environment (please complete the following information):

  • ZenStack version: 3.7.0
  • Database type: PostgreSQL
  • Node.js/Bun version: 24.15.0
  • Package manager: npm 11.12.1

Additional context

  • Related: Invalid compound IDs lead to 400, should be 404 #2211 (Invalid compound IDs lead to 400, should be 404, open) — same makeIdFilter/coerce path, but a different case: a missing segment (/user/a for an a_b key) that should 404. This issue is a present-but-empty segment denoting null that should resolve 200. A fix should cover both; the desired outcomes differ.
  • Related: feat: support natural keys in the API #2188 (support natural keys in the API, merged) — the externalIdMapping feature this surfaces under.
  • Our interim workaround: shadow the private coerce to map empty-segment + optional-field → null. To be removed once fixed upstream.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions