Skip to content

Support operation-scoped x-semantic-provider for a shared response schema #384

@jwulf

Description

@jwulf

Summary

Allow a producing operation to mark a scoped provider for an identifier that is carried by a shared response schema — without forcing a dedicated per-operation result DTO.

Today the only supported way to make an operation an authoritative producer of a server-minted identifier is x-semantic-provider on a schema where the identifier is a direct property. When a CRUD entity reuses one result schema across create/get/update/search, there is no supported way to say "only the create response is the canonical producer of the key".

Context / motivation: camunda/camunda-hub#25042 (annotating File/Folder/Project public-API ops). File is a server-minted-key entity (fileKey is allocated by the server and returned in the response) whose create/get/update/search operations all share a single FileResult schema. We do not want to duplicate FileResult into a FileCreateResult just to host the provider annotation.

Why the current options don't fit

OCA's convention is a dedicated *CreateResult DTO per producer (TenantCreateResult vs TenantResult, CreateProcessInstanceResult, DeploymentResult, …), so the shared-schema case has no precedent and no consumer support. The two documented escape hatches both fail here:

  1. Array-form x-semantic-provider: [fileKey] on an allOf wrapper (wrapper allOf: [$ref FileResult], scoped to the create 200 only) is silently a no-op:

    • providerProps (the array form) is consumed only for direct properties of the annotated object — semantic-graph-extractor/schema-analyzer.ts:676-693. fileKey arrives via allOf $ref, so it is never a direct property of the wrapper.
    • The allOf branch re-propagates provider only for the boolean true form, not the array form — schema-analyzer.ts:724-742.
    • The boolean-form-through-allOf fallback only resolves a top-level x-semantic-type on the $ref'd schema — schema-analyzer.ts:628-643 — which an object like FileResult (whose key is a nested property) does not have.
    • Net: FileKey is extracted with provider: false, so it never enters providerMap/produces (gated at path-analyser/src/graphLoader.ts:747-760). createFile is not registered in producersByType[FileKey], and getFile/updateFile/deleteFile (which require FileKey via the path param) become orphaned in base chains.
  2. Array-form x-semantic-provider: [fileKey] on the shared FileResult would work mechanically (direct property) but implicitly claims provider semantics for every operation that $refs it — including searchFiles (returns pre-existing items[].fileKey) — which is exactly the anti-pattern called out in the endpoint guidelines (and tracked upstream by x-semantic-provider: define and enforce the generator-vs-exposer rule camunda#52414).

Proposed enhancement

Add an operation-scoped provider declaration so a producer can name which identifier(s) of its (possibly shared) response schema it authoritatively produces. Sketch of options (open to design):

  • Option A — operation-level x-semantic-provider on the operation (or its responses.<2xx>.content), e.g.

    responses:
      "200":
        x-semantic-provider: [fileKey]   # scoped to THIS operation's response only
        content:
          application/json:
            schema: { $ref: '#/components/schemas/FileResult' }

    The extractor would resolve the named props against the (allOf/$ref-composed) response schema for that operation only, and set provider: true on those leaves for that op's responseSemanticTypes — independent of other operations sharing the schema.

  • Option B — thread array-form providerProps through allOf/$ref so the allOf-wrapper-scoped pattern in (1) above works as authored. i.e. when an object carries x-semantic-provider: [name] and name resolves to a property reachable through allOf/$ref composition (not only own properties), mark it. This keeps the annotation on a thin per-operation wrapper while reusing the shared schema.

Either way the goal is: provider scope = the producing operation, not the schema, with no duplicated DTO.

Acceptance criteria

  • A producer that $refs a shared result schema can mark a scoped provider for one or more identifiers without a dedicated result DTO.
  • The same shared schema referenced by non-producing ops (get/update/search) does not become an authoritative producer.
  • createFile ends up in producersByType[FileKey]; base chains for getFile/updateFile/deleteFile resolve createFile as the FileKey producer.
  • Regression coverage in tests/fixtures/extractor (provider extraction) and a graph-loader test asserting producersByType membership for the scoped case.
  • Endpoint-guidelines doc updated to describe the supported shared-schema pattern (currently it only offers "split the schema" or "omit provider").

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    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