1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
openapi: 3.1.0
info:
title: Example OpenAPI definition
paths:
/widgets:
post:
requestBody:
required: true
content:
application/json:
schema:
# Use the `POST` version of the `Widget` type. See below.
$interface: "Widget#Post"
responses:
201:
$ref: "#/components/responses/WidgetResponse"
/widgets/{id}:
patch:
parameters:
- name: dataset_id
in: path
description: The UUID of a dataset
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json+merge-patch:
schema:
# Use the `PATCH` version of the `Widget` type. See below.
$interface: "Widget#MergePatch"
required: true
responses:
200:
$ref: "#/components/responses/WidgetResponse"
404:
description: The requested ID was not found
content:
components:
responses:
WidgetResponse:
content:
application/json:
schema:
# Use the `GET` version of the `Widget` type. See below.
$interface: "Widget"
# We can declare normal OpenAPI / JSON Schema schemas as always.
schemas:
ResourceStatus:
type: string
description: The current state of this resource.
example: new
enum:
- new
- updating
- ready
- error
# This is a non-standard section that we add to OpenAPI. It contains
# interfaces that behave a bit more like programming language types (and less
# like a validation language). Each of these types potentially exists in
# several versions:
#
# - MyType: The base type. Returned by the server.
# - MyType#Post: Use to create new instances of MyType using POST.
# - MyType#Put: Overwrite an instance of MyType using PUT.
# - MyType#MergePatch: Update an instance of MyType using PATCH with
# application/json+merge-patch.
#
# These types will be compiled to `components.schema.MyType`,
# `components.schema.MyTypePost`, etc.
interfaces:
# Shared fields used by all resources. None of these may be passed to POST,
# PUT or PATCH, because they're not `mutable` or `initializable`.
Resource:
emit: false
members:
id:
# We specify `required` directly on the member, and not (like JSON
# Schema) in an external list. This makes it vastly easier to use
# `$merge` to combine multiple interfaces.
required: true
# We then use a perfectly normal JSON schema to define the type.
schema:
type: string
format: uuid
created_at:
required: true
schema:
type: string
format: date-time
updated_at:
required: true
schema:
type: string
format: date-time
status:
required: true
schema:
$ref: "#/components/schemas/ResourceStatus"
Widget:
$includes: "Resource"
# On optional title field, used when the resource name doesn't match the
# desired user-facing name in documentation.
title: "Widgetter"
members:
id:
schema:
# Because of how `$merge` works, we can customize things
# declared by the base interface.
example: "994fc99e-fa9f-454b-a3de-c1966ae61533"
sku:
required: true
# This type can be initialized, but not changed later.
# `initializable` defaults to the value of `mutable` if not
# specified.
initializable: true
mutable: false
schema:
type: string
name:
mutable: true
schema:
description: "The name of this widget."
type: string
vendor_id:
required: true
initializable: true
mutable: false
schema:
type: string
format: uuid
metadata:
required: true
# TODO: We need some way to specify "this field is a mandatory
# part of the base type, but if you omit it from POST, there's a
# default value we can use." This may not be a clean way to do it.
#
#defaults: true
mutable: true
schema:
# We can use the special `#SameAsInterface` fragment to say, "If
# we're defining `Widget#Post`, use `Metadata#Post`. If we're
# defining `Widget#Put`, use `Metadata#Put`". This allows generating
# complex nested types.
$interface: "Metadata#SameAsInterface"
# A `Record<string, string | number | boolean>` example.
Metadata:
# `additionalMembers` works like `additionalProperties`, but
additionalMembers:
# TODO: I don't think `required` makes sense here, but everything else
# does.
mutable: true
schema:
type:
- string
- number
- boolean