1

Which syntax is correct - the first one, the second one, or both?

components:
  schemas:
    FileContent:
      allOf:
        - $ref: '#/components/schemas/FileInfo'
        - type: object
          properties:
            storageMethod:
              $ref: '#/components/schemas/StorageMethod'
            contentRange:
              type: string
              nullable: true
            # ... other properties ...
          additionalProperties: false
components:
  schemas:
    FileContent:
      type: object
      allOf:
        - $ref: '#/components/schemas/FileInfo'
      properties:
        storageMethod:
          $ref: '#/components/schemas/StorageMethod'
        contentRange:
          type: string
          nullable: true
        # ... other properties ...
      additionalProperties: false
Helen
  • 87,344
  • 17
  • 243
  • 314
jenvy xu
  • 11
  • 2
  • 1
    Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Aug 25 '22 at 08:36

2 Answers2

2

In terms of allOf syntax, both versions are correct and technically equivalent:

allOf:
  - $ref: '#/components/schemas/Foo'
  - properties:
      # other properties
      # ...
allOf:
  - $ref: '#/components/schemas/Foo'
properties:
  # other properties
  # ...

In OpenAPI 3.1 (which uses JSON Schema 2020-12 by default), there's even no need for allOf if you only have one $ref because $ref now allows sibling keywords. (But you still need allOf to "combine" multiple $refs.)

# openapi: 3.1.0

$ref: '#/components/schemas/Foo'
properties:
  # other properties
  # ...

The error in your examples is elsewhere - it's the presence of additionalProperties: false. This keyword is problematic because it only knows about its immediate sibling properties and has no visibility into allOf/oneOf/anyOf subschemas or "inherited" schemas. For your examples, this means that properties defined in the FileInfo schema won't actually be allowed in the composed schema.

Here are some more examples to illustrate that additionalProperties: false doesn't work the way one would expect:

allOf:
  - $ref: '#/components/schemas/Foo'
  - $ref: '#/components/schemas/Bar'
additionalProperties: false

# Expected: Only the properties defined in Foo and Bar are allowed
# Actual: No properties are allowed
allOf:
  - $ref: '#/components/schemas/Foo'
  - properties:
      prop:
        type: string
    additionalProperties: false

# Expected: The allowed properties are `prop` and those defined in the Foo schema
# Actual: Only the `prop` property is allowed
allOf:
  - $ref: '#/components/schemas/Foo'
properties:
  prop:
    type: string
additionalProperties: false

# Expected: The allowed properties are `prop` and those defined in the Foo schema
# Actual: Only the `prop` property is allowed
Foo:
  type: object
  properties:
    foo:
      type: string
  additionalProperties: false

Bar:
  allOf:
    - $ref: '#/components/schemas/Foo'
    - properties:
        prop:
          type: string

# Expected: The Bar schema allows properties from Foo + the `prop` property
# Actual: The Bar schema allows only properties from Foo

This is solved in OpenAPI 3.1 / JSON Schema 2019-09+ by the new unevaluatedProperties: false keyword. So the following will work the way you expect:

# openapi: 3.1.0

$ref: '#/components/schemas/Foo'
properties:
  prop:
    type: string
unevaluatedProperties: false
Helen
  • 87,344
  • 17
  • 243
  • 314
0

I think the second one: for a long time, schema keywords adjacent to $ref were ignored in json schemas. Later they changed it in the spec (AFAIK), but I'm not sure if all the tooling & implementations caught up with that.

Yet my personal preference is putting both the parent schema and child schema props under an allOf, like

  schemas:
    FileContent:
      allOf:
        - $ref: '#/components/schemas/FileInfo'
        - type: object
          properties:
            storageMethod:
              $ref: '#/components/schemas/StorageMethod'
            contentRange:
              type: string
              nullable: true
            totalLength:
              type: integer
              format: int64
erosb
  • 2,943
  • 15
  • 22