Skip to content

Commit

Permalink
added schema composition
Browse files Browse the repository at this point in the history
  • Loading branch information
philsturgeon committed Jul 24, 2024
1 parent e4d3342 commit 934a8af
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Schemas and Data Types
authors: phil
excerpt: "Learn about the most important part of OpenAPI: schemas, and data types."
date: 2024-07-10
date: 2024-07-18
---

One of the most important parts of OpenAPI is the `schema` object. Schema objects are used to describe HTTP request and response bodies, parameters, headers, and all sorts of other data, whether its JSON, XML, or primitive types like integers and strings.
Expand Down Expand Up @@ -142,7 +142,87 @@ You can also define your own custom formats, which tooling will not understand,

## Validation

In addition to defining data types and formats, JSON Schema provides several validation keywords to enforce specific constraints on the data. Here are a few popular validation keywords:
In addition to defining data types and formats, [JSON Schema](./json-schema.md) provides several validation keywords to enforce specific constraints on the data. Here are a few popular validation keywords:

## const & enum

Restricting a value down to one or more potential values can be done with the `const` or `enum` keywords.

First, a look at `enum`, as that keyword has been around longer and is more used:

```yaml
type: string
enum:
- pending
- fulfilled
- archived
```

This says the string can't just be any old string, it has to be one of the approved values listed in `enum`.

> Learn more about const on [JSON-Schema.org: Enumerated Values](https://json-schema.org/understanding-json-schema/reference/enum).
{: .info }

OpenAPI v3.1 gained the `const` keyword added in modern JSON Schema, which helps with describing something that can only ever be one value.

The JSON Schema tutorial uses the example of having a country field where you only support shipping to the United States for export reasons:

```yaml
properties:
country:
const: United States of America
```

That's one way to use it, but another is to act as a switch in a `oneOf`.

```yaml
oneOf:
- title: Card
properties:
object:
type: string
const: card
number:
type: string
cvc:
type: integer
exp_month:
type: integer
exp_year:
type: integer
- title: Bank Account
type: object
properties:
object:
const: bank_account
type: string
number:
type: string
sort_code:
type: string
```

In this example the `object` could be `card` or `bank_account`, but instead of defining that as an enum and the other properties all have to figure out whether they relate to cards or bank accounts, we use the `const` to help match the subschema.

> Learn more about `const` on [JSON-Schema.org: Constant Values](https://json-schema.org/understanding-json-schema/reference/const), and read our guide on [Schema Composition](./schema-composition.md) to learn more about `oneOf`.
{: .info }


## default

Setting a `default` lets people and code know what to do when a value has not been provided.

```
type: string
enum:
- pending
- fulfilled
- archived
```
### minimum and maximum
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: Schema Composition
authors: phil
excerpt: Use oneOf, anyOf, and allOf in OpenAPI & JSON Schema for polymorphism and composition.
date: 2024-07-24
---

In OpenAPI v3.1 and JSON Schema, you can use `oneOf`, `allOf`, and `anyOf` keywords to handle composition, which is the concept of combining multiple schemas and subschemas in various ways to handle polymorphism, or "extending" other schemas to add more criteria.

## What are oneOf, anyOf, and allOf?

- **allOf:** (AND) Must be valid against all of the subschemas.
- **anyOf:** (OR) Must be valid against any of the subschemas.
- **oneOf:** (XOR) Must be valid against exactly one of the subschemas.

All of these keywords must be an array, where each item is a schema. Be careful with recursive schemas as they can exponentially increase processing times.

### oneOf

The `oneOf` keyword is used when you want to specify that a value should match one of the given schemas exactly. It's useful when you have different possible data structures or types for a particular field, like accepting bank account or card payments, or having train tickets and tram tickets, which are similar but a little different.

The validation will pass if the value matches exactly one of the schemas defined in `oneOf`.

This can be done for a single value:

```yaml
properties:
timestamp:
oneOf:
- type: string
format: date-time
examples:
- '2024-07-21T17:32:28Z'
- type: integer
examples:
- 1721820298
```
In this example the `timestamp` property could be either a RFC 3339 date time (e.g. `2017-07-21T17:32:28Z`) or a unix timestamp (e.g. `1721820298`).

That shows how it works for a single property, but `oneOf` can also be used with whole objects:

```yaml
properties:
source:
oneOf:
- title: Card
properties:
number:
type: string
cvc:
type: integer
exp_month:
type: integer
format: int64
exp_year:
type: integer
format: int64
required:
- number
- cvc
- exp_month
- exp_year
- title: Bank Account
type: object
properties:
number:
type: string
sort_code:
type: string
account_type:
type: string
enum:
- individual
- company
required:
- number
- account_type
```

In the above example, the `source` property will be an object either way, but the properties contained within can be in one of two combinations. Either `number`, `cvc`, `exp_month`, and `exp_year` are valid and in good form (a card payment), or `number`, `sort_code`, and `account_type` will be sent (a bank account). These are the only two outcomes which will return a valid result in a validator, because if neither subscheme match it will be invalid, and if multiple subschemas match that will also be invalid.

### anyOf

The `anyOf` keyword is very similar to `oneOf` but a little less restrictive. `oneOf` is more like a XOR in programming, where one or the other can match, but never both. anyOf is more like a regular OR, which allows one or another or both.

Just like `oneOf`, you can use `anyOf` when you have multiple valid options for a particular field. The validation will pass if the value matches one or more of the listed subschemas.

```yaml
oneOf:
- type: number
multipleOf: 5
- type: number
multipleOf: 3
```

The values *1, 2, 4, 7, 8, 11, 13, 14* would all be rejected for not being multiples of either 3 or 5.

The values *3, 5, 6, 9, 10, 12* would be valid for being multiples of 3 and 5.

The value *15* would be rejected because it is multiples of both 3 and 5, and `oneOf` doesn't like that.

### allOf

The `allOf` keyword is used when you want to specify that a value should match all of the given schemas. It is useful when you want to combine multiple schemas together. The validation will pass if the value matches all of the schemas defined in `allOf`.

```yaml
allOf:
- type: object
properties:
name:
type: string
- type: object
properties:
age:
type: integer
minimum: 0
```

In the above example, the value should be an object that has both a `name` property of type string and an `age` property of type integer with a minimum value of 0.

## References in Composition

All of these keywords can contain a list of subschemas that are defined directly inside them, or a `$ref` can point to a schema defined elsewhere.

```yaml
schema:
oneOf:
- $ref: '#/components/schemas/Card'
- $ref: '#/components/schemas/BankAccount'
```

This says that the schema can be either one of these schemas stored as [shared components](../understanding-structure/components.md).

Due to the way `allOf` works, you can essentially reference multiple schemas and say "I want all of the validation rules and criteria from all of these schemas to apply here", providing a sort of merge-like functionality.

```yaml
schema:
allOf:
- $ref: '#/components/schemas/PaymentMethod'
- $ref: '#/components/schemas/BankAccount'
```

This basically declares a schema which has a lot of generic payment fields, then adds specific fields from the bank account type, to avoid declaring generic fields like "name" and "number" in both.

These schema composition keywords provide flexibility and allow you to define complex data structures and validation rules in OpenAPI v3.1 and JSON Schema, which becomes more useful as you start to [improve reuse across one or more API](../advanced/splitting-documents-with-ref.md).

0 comments on commit 934a8af

Please sign in to comment.