Skip to content

fix(jsonapi): allow opt-in client-generated IDs on POST per spec#7930

Open
abderrahimghazali wants to merge 1 commit intoapi-platform:mainfrom
abderrahimghazali:fix/jsonapi-post-id-not-required
Open

fix(jsonapi): allow opt-in client-generated IDs on POST per spec#7930
abderrahimghazali wants to merge 1 commit intoapi-platform:mainfrom
abderrahimghazali:fix/jsonapi-post-id-not-required

Conversation

@abderrahimghazali
Copy link
Copy Markdown

Q A
Branch? main
Bug fix? yes
New feature? no (opt-in flag)
Deprecations? no
Issues Closes #6738
License MIT

What's in this PR?

Two related JSON:API issues prevent valid POST requests with client-generated IDs (per JSON:API §7.3):

  1. Schema (SchemaFactory) declared data.id required for every operation, including the request body of a POST. The spec says id MAY be supplied by the client and is otherwise optional on creation.
  2. Denormalizer (ItemNormalizer) treated any incoming data.id as a hint to load an existing resource, then either threw Update is not allowed for this operation or failed to resolve the IRI when the client passed a fresh UUID.

Together they make it impossible to POST {"data":{"type":"…","id":"<uuid>","attributes":{…}}} even when the application is designed for client-generated identifiers (Doctrine UUID PK, ULID, etc.).

Fix

JsonApi\JsonSchema\SchemaFactory

  • For Schema::TYPE_INPUT on a Post operation, data.required is now ["type"]. Output schemas and non-Post operations remain ["type", "id"] (response payloads still always carry an id).
  • The active operation is captured at the start of buildDefinitionPropertiesSchema() because the relationship loop reassigns \$operation.

Before (POST request body):
```json
"required": ["type", "id"]
```
After:
```json
"required": ["type"]
```

JsonApi\Serializer\ItemNormalizer

  • New opt-in context flag ItemNormalizer::ALLOW_CLIENT_GENERATED_ID ('allow_client_generated_id').
  • On a Post, an incoming data.id no longer triggers an existing-resource lookup.
    • If the flag is off (default): throws NotNormalizableValueException with a clear message — no behaviour change for endpoints that don't expect client-generated IDs, and no risk of silently letting a client spoof an ID.
    • If the flag is on: the id is merged into the denormalized payload and applied to the new entity via the property setter. The IRI converter is not queried.
  • Existing PUT/PATCH path (load by IRI / OBJECT_TO_POPULATE) is preserved.

The flag is off by default to keep the change non-breaking and to avoid an ID-spoofing footgun on public endpoints. Applications opt in per-operation by passing the flag in the denormalization context (or via a state processor / serializer context builder).

Tests

  • SchemaFactoryTest::testBuildSchemaForPostInputDoesNotRequireId — POST input drops id from required.
  • SchemaFactoryTest::testBuildSchemaForPostOutputStillRequiresId — POST output still requires id.
  • ItemNormalizerTest::testDenormalizePostWithIdThrowsWithoutOptIn — POST with client id throws without opt-in.
  • ItemNormalizerTest::testDenormalizePostWithIdSucceedsWithOptIn — POST with client id succeeds with opt-in, IRI converter is not called, id is set on the new entity.

Full src/JsonApi/Tests suite: 57 tests / 145 assertions, all green (the 2 PHPUnit notices are pre-existing).

Spec references

Credit to @cay89 for the original analysis in #6738.

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.

For JSON:API, the ID is not required in the POST request

1 participant