Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.1.1 discriminator improvements #2618

Merged
merged 12 commits into from
Apr 4, 2024
51 changes: 32 additions & 19 deletions versions/3.1.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -2332,7 +2332,7 @@ The OpenAPI Specification's base vocabulary is comprised of the following keywor

Field Name | Type | Description
---|:---:|---
<a name="schemaDiscriminator"></a>discriminator | [Discriminator Object](#discriminatorObject) | Adds support for polymorphism. The discriminator is an object name that is used to differentiate between other schemas which may satisfy the payload description. See [Composition and Inheritance](#schemaComposition) for more details.
<a name="schemaDiscriminator"></a>discriminator | [Discriminator Object](#discriminatorObject) | Adds support for polymorphism. The discriminator is used to determine which of a set of schemas a payload is expected to satisfy. See [Composition and Inheritance](#schemaComposition) for more details.
<a name="schemaXml"></a>xml | [XML Object](#xmlObject) | This MAY be used only on properties schemas. It has no effect on root schemas. Adds additional metadata to describe the XML representation of this property.
<a name="schemaExternalDocs"></a>externalDocs | [External Documentation Object](#externalDocumentationObject) | Additional external documentation for this schema.
<a name="schemaExample"></a>example | Any | A free-form property to include an example of an instance for this schema. To represent examples that cannot be naturally represented in JSON or YAML, a string value can be used to contain the example with escaping where necessary.<br><br>**Deprecated:** The `example` property has been deprecated in favor of the JSON Schema `examples` keyword. Use of `example` is discouraged, and later versions of this specification may remove it.
Expand All @@ -2345,13 +2345,8 @@ The OpenAPI Specification allows combining and extending model definitions using
`allOf` takes an array of object definitions that are validated *independently* but together compose a single object.

While composition offers model extensibility, it does not imply a hierarchy between the models.
To support polymorphism, the OpenAPI Specification adds the `discriminator` field.
When used, the `discriminator` will be the name of the property that decides which schema definition validates the structure of the model.
As such, the `discriminator` field MUST be a required field.
There are two ways to define the value of a discriminator for an inheriting instance.
- Use the schema name.
- Override the schema name by overriding the property with a new value. If a new value exists, this takes precedence over the schema name.
As such, inline schema definitions, which do not have a given id, *cannot* be used in polymorphism.
To support polymorphism, the OpenAPI Specification adds the `discriminator` keyword.
When used, the `discriminator` will indicate the name of the property that hints which schema definition is expected to validate the structure of the model.
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

###### XML Modeling

Expand Down Expand Up @@ -2698,14 +2693,23 @@ components:

#### <a name="discriminatorObject"></a>Discriminator Object

When request bodies or response payloads may be one of a number of different schemas, a `discriminator` object can be used to aid in serialization, deserialization, and validation. The discriminator is a specific object in a schema which is used to inform the consumer of the document of an alternative schema based on the value associated with it.
When request bodies or response payloads may be one of a number of different schemas, a `discriminator` object can be used to aid in serialization, deserialization, and validation. The `discriminator` keyword is used to inform the consumer of the document which of the alternatives is expected or preferred.
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

When using the discriminator, _inline_ schemas will not be considered.
`discriminator` uses a schema's "name" to automatically map a property value to
a schema. The schema's "name" is the property name used when declaring the
schema as a component in an OpenAPI document. For example, the name of the
schema at `#/components/schemas/Cat` is "Cat". Therefore, when using
`discriminator`, _inline_ schemas will not be considered because they don't have
a "name".

Schema names are scoped to the OpenAPI document they are defined in. That means
if OpenAPI document "A" references a schema in OpenAPI document "B", the schema
names in "A" aren't considered when evaluating the referenced schema in "B".
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main part that's controversial. In Swagger 2.0, the references were strongly implied to be scoped to document "A", and many tools implement that behavior. There are other component name connections that have similar ambiguities and we should address them all at once. (FWIW, I strongly agree with what you've written here, but it would be a de-facto breaking change for some and we recently decided not to do "clarifications" that are de-facto breaking changes).


##### Fixed Fields
Field Name | Type | Description
---|:---:|---
<a name="propertyName"></a>propertyName | `string` | **REQUIRED**. The name of the property in the payload that will hold the discriminator value.
<a name="propertyName"></a>propertyName | `string` | **REQUIRED**. The name of the property in the payload that will hold the discriminator value. This property MUST be required in the payload schema.
<a name="discriminatorMapping"></a> mapping | Map[`string`, `string`] | An object to hold mappings between payload values and schema names or references.

This object MAY be extended with [Specification Extensions](#specificationExtensions).
Expand All @@ -2722,8 +2726,7 @@ MyResponseType:
- $ref: '#/components/schemas/Lizard'
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved
```

which means the payload _MUST_, by validation, match exactly one of the schemas described by `Cat`, `Dog`, or `Lizard`. In this case, a discriminator MAY act as a "hint" to shortcut validation and selection of the matching schema which may be a costly operation, depending on the complexity of the schema. We can then describe exactly which field tells us which schema to use:

which means the payload _MUST_, by validation, match exactly one of the schemas described by `Cat`, `Dog`, or `Lizard`. Evaluating a `oneOf` can be a costly operation, so `discriminator` MAY be used as a "hint" to improve the efficiency of selection of the matching schema. The `discriminator` keyword cannot change the validation result of the `oneOf`, it can only help make the evaluation more efficient and provide better error messaging. We can then describe exactly which field tells us which schema is expected to match the instance:
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

```yaml
MyResponseType:
Expand All @@ -2744,7 +2747,7 @@ The expectation now is that a property with name `petType` _MUST_ be present in
}
```

Will indicate that the `Cat` schema be used in conjunction with this payload.
Will indicate that the `Cat` schema is the alternative that is expected to match this payload.
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

In scenarios where the value of the discriminator field does not match the schema name or implicit mapping is not possible, an optional `mapping` definition MAY be used:

Expand All @@ -2762,9 +2765,9 @@ MyResponseType:
monster: 'https://gigantic-server.com/schemas/Monster/schema.json'
```

Here the discriminator _value_ of `dog` will map to the schema `#/components/schemas/Dog`, rather than the default (implicit) value of `Dog`. If the discriminator _value_ does not match an implicit or explicit mapping, no schema can be determined and validation SHOULD fail. Mapping keys MUST be string values, but tooling MAY convert response values to strings for comparison.
Here the discriminator _value_ of `dog` will map to the schema `#/components/schemas/Dog`, rather than the default (implicit) `#/components/schemas/dog`. If the discriminator _value_ does not match an implicit or explicit mapping, no schema can be determined and validation SHOULD fail. Mapping keys MUST be string values, but tooling MAY convert response values to strings for comparison.
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

When used in conjunction with the `anyOf` construct, the use of the discriminator can avoid ambiguity where multiple schemas may satisfy a single payload.
When used in conjunction with the `anyOf` construct, the use of the discriminator can avoid ambiguity for serializers/deserializers where multiple schemas may satisfy a single payload.

In both the `oneOf` and `anyOf` use cases, all possible schemas MUST be listed explicitly. To avoid redundancy, the discriminator MAY be added to a parent schema definition, and all schemas comprising the parent schema in an `allOf` construct may be used as an alternate schema.

Expand All @@ -2773,6 +2776,11 @@ For example:
```yaml
components:
schemas:
MyResponseType:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Lizard'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that the "allOf" form specifically exists to avoid having to list the subschemas in a "oneOf" or "anyOf", so this should not be changed. I don't personally understand how this even works, and I'm not the only one. But it's related to the controversial thing- if all schema names resolve to the Components Object of what we now call the "entry document", you can just search that one list. I guess? Apparently we need to figure that out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem I was trying to solve here is that the example payloads never say which schema is being used to evaluate those payloads. Based on my understanding of the allOf usage, none of those schemas could produce the results the documentation says it does, so I introduced something that could.

At this point I can only assume that the "Pet" schema is what these payloads are intended to be evaluated against and the behavior described is correct in that case. So, I've reverted my change other than to specify that "Pet" is the schema these payloads are evaluated against. Let me know if that's the right thing to do. I can't see how that makes sense, so I wouldn't be surprised if it's still not correct. Let me know.

Pet:
type: object
required:
Expand Down Expand Up @@ -2810,7 +2818,12 @@ components:
type: boolean
```

a payload like this:
The `MyResponseType` schema will use the discriminator defined by the `Pet`
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved
schema because it is part of the `Cat`, `Dog`, and `Lizard` schemas in the
`oneOf`. The behavior if not all schemas define a `discriminator` and they are
not all the same is undefined.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need to be removed if the oneOf addition to the example is removed.


Validated against the `MyResponseType` schema, a payload like this:

```json
{
Expand All @@ -2819,7 +2832,7 @@ a payload like this:
}
```

will indicate that the `Cat` schema be used. Likewise this schema:
will indicate that the `#/components/schemas/Cat` schema is expected to match. Likewise this payload:

```json
{
Expand All @@ -2828,7 +2841,7 @@ will indicate that the `Cat` schema be used. Likewise this schema:
}
```

will map to `Dog` because of the definition in the `mapping` element.
will map to `#/components/schemas/Dog` because the `dog` entry in the `mapping` element maps to `Dog` which is the schema name for `#/components/schemas/Dog`.


#### <a name="xmlObject"></a>XML Object
Expand Down