Adeynack
Adeynack

Reputation: 1270

OpenAPI 3 -- Property is optional when written but required when read

My resource has an ID (typical case).

It has also a slug, a human-readable but still unique identifier (to beautify the URLs, mainly).

This slug is optional when the resource is created. If the client provides one, it is being used; otherwise, the server generated one.

This slug is however required when the resource is being read.

We do want that distinction to be clear, so any tooling reading that OpenAPI specification knows what to expect exactly.

This could of course be achieved using a mix of different schemas linked with allOf modifiers (see example below), but I would like to avoid having to perform this composition (assuming it works with tooling in the first place).

So my question is:

Is there a way in OpenAPI >= 3.0.2 to declare a property required-readOnly and optional-writeOnly?

Solution using composition:

openapi: 3.0.2
info:
  title: Person API
  version: 1.0.0

paths: 
  '/persons/{person-slug}':
    get:
      parameters:
        - $ref: '#/components/parameters/PersonSlug'
      responses:
        200:
          description: Information on a person.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SlugRead'
                  - $ref: '#/components/schemas/Person'
    post:
      parameters:
        - $ref: '#/components/parameters/PersonSlug'
      responses:
        200:
          description: Information on a person.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SlugWrite'
                  - $ref: '#/components/schemas/Person'

components:

  parameters:

    PersonSlug:
      name: 'person-slug'
      description: Human readable unique ID of a person.
      required: true
      in: path
      schema:
        type: string

  schemas:

    SlugRead: # required
      required:
        - slug
      properties:
        slug:
          type: string
          readOnly: true

    SlugWrite: # not required
      properties:
        slug:
          type: string

    Person:
      required:
        - first_name
        - last_name
        - birth_date
      properties:
        first_name:
          type: string
        last_name:
          type: string
        birth_date:
          type: string
          format: date    

Upvotes: 4

Views: 8732

Answers (2)

Papooch
Papooch

Reputation: 1635

For whomever stumbles upon this question in the future, I solved this using a base schema and derived schemas for specific operations.

Person:
  properties:
    id:
      type: integer
      readOnly: true
    name:
      type: string
    slug:
      type: string

PersonWRITE:
  allOf: # name is required, slug is optional and id is not available
    - $ref: '#/components/schemas/Person'
    - required: [name]

PersonREAD:
  allOf: # everything is available and required
    - $ref: '#/components/schemas/Person'
    - required: [id, name, slug]

It is essentially the OP's solution, but moved into the components level so that you don't have to use the 'allOf' in the request/response definition.


EDIT: Please note that thhe following will NOT work:

PersonWRITE: #wrong
  $ref: '#/components/schemas/Person'
  required: [name]

As the documentatin states that Any sibling elements of a $ref are ignored. This is because $ref works by replacing itself and everything on its level with the definition it is pointing at.

The key is the allOf, which combines all properties of the objects in the list. Upon $ref expansion, the schema looks like this (still a valid yaml, I just jsonified it a bit to point out the that the array elements are, in fact, two objects whose propetries will be combined):

PersonWRITE:
  allOf: [
    {
      properties: {
        id:   { type: integer, readOnly: true },
        name: { type: string },
        slug: { type: string }
      }
    },
    {
      required: [name]
    }
  ]
    

Upvotes: 10

Rob Oxspring
Rob Oxspring

Reputation: 2935

Can't see how to achieve exactly what you're after is possible but I think the effect may be definable by using separate fields for the desired (write only) vs assigned (read only) slug:

Person:
  required:
    - first_name
    - last_name
    - birth_date
    - assigned_slug
  properties:
    first_name:
      type: string
    last_name:
      type: string
    birth_date:
      type: string
      format: date
    assigned_slug:
      type: string
      readOnly: true
    desired_slug:
      type: string
      writeOnly: true

No idea if any tooling would do the right thing with it though!

Upvotes: 0

Related Questions