-
Notifications
You must be signed in to change notification settings - Fork 9
/
main.tsp
277 lines (221 loc) · 6.1 KB
/
main.tsp
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi3";
import "@typespec/openapi";
import "@typespec/json-schema";
using Http;
using JsonSchema;
@service({
title: "Todo App",
})
@useAuth(BearerAuth | ApiKeyAuth<ApiKeyLocation.cookie, "session-id">)
@jsonSchema
namespace Todo;
@jsonSchema
model User {
/** An autogenerated unique id for the user */
@key
@visibility("read")
id: safeint;
/** The user's username */
@minLength(2)
@maxLength(50)
username: string;
/** The user's email address */
// @format("email") - crashes emitters for now
email: string;
/**
* The user's password, provided when creating a user
* but is otherwise not visible (and hashed by the backend)
*/
@visibility("create")
password: string;
/** Whether the user is validated. Never visible to the API. */
@visibility("none") validated: boolean;
}
@jsonSchema
model TodoItem {
/** The item's unique id */
@visibility("read") @key id: safeint;
/** The item's title */
@maxLength(255)
title: string;
/** User that created the todo */
@visibility("read") createdBy: User.id;
/** User that the todo is assigned to */
assignedTo?: User.id;
/** A longer description of the todo item in markdown format */
description?: string;
/** The status of the todo item */
status: "NotStarted" | "InProgress" | "Completed";
/** When the todo item was created. */
@visibility("read") createdAt: utcDateTime;
/** When the todo item was last updated */
@visibility("read") updatedAt: utcDateTime;
/** When the todo item was makred as completed */
@visibility("read") completedAt?: utcDateTime;
// Want the read form to be normalized to TodoLabelRecord[], but can't
// https://github.com/microsoft/typespec/issues/2926
labels?: TodoLabels;
// hack to get a different schema for create
// (fastify glue doesn't support readonly)
@visibility("create") _dummy?: string;
}
@jsonSchema
union TodoLabels {
string,
string[],
TodoLabelRecord,
TodoLabelRecord[],
}
@jsonSchema
model TodoLabelRecord {
name: string;
@pattern("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")
color?: string;
}
union TodoAttachment {
file: TodoFileAttachment,
url: TodoUrlAttachment,
}
@jsonSchema
model TodoUrlAttachment {
/** A description of the URL */
description: string;
/** The url */
url: url;
}
@jsonSchema
model TodoFileAttachment {
/** The file name of the attachment */
@maxLength(255)
filename: string;
/** The media type of the attachment */
mediaType: string;
/** The contents of the file */
contents: bytes;
}
@jsonSchema
@error
model ApiError {
/** A machine readable error code */
code: string;
/** A human readable message */
// https://github.com/microsoft/OpenAPI/blob/main/extensions/x-ms-primary-error-message.md
@OpenAPI.extension("x-ms-primary-error-message", true)
message: string;
}
/**
* Something is wrong with you.
*/
model Standard4XXResponse extends ApiError {
@minValue(400)
@maxValue(499)
@statusCode
statusCode: int32;
}
/**
* Something is wrong with me.
*/
model Standard5XXResponse extends ApiError {
@minValue(500)
@maxValue(599)
@statusCode
statusCode: int32;
}
alias WithStandardErrors<T> = T | Standard4XXResponse | Standard5XXResponse;
@useAuth(NoAuth)
namespace Users {
// would prefer to extend
// https://github.com/microsoft/typespec/issues/2922
model UserCreatedResponse {
...User;
...OkResponse;
/** The token to use to construct the validate email address url */
token: string;
}
/** The user already exists */
model UserExistsResponse extends ApiError {
...ConflictResponse;
code: "user-exists";
}
/** The user is invalid (e.g. forgot to enter email address) */
model InvalidUserResponse extends ApiError {
@statusCode statusCode: 422;
code: "invalid-user";
}
@route("/users")
@post
op create(
@body user: User,
): WithStandardErrors<UserCreatedResponse | UserExistsResponse | InvalidUserResponse>;
}
@route("items")
namespace TodoItems {
model PaginationControls {
/** The limit to the number of items */
@query limit?: int32 = 50;
/** The offset to start paginating at */
@query offset?: int32 = 0;
}
model TodoPage {
/** The items in the page */
@pageItems items: TodoItem[];
pagination: {
/** The number of items returned in this page */
pageSize: int32;
/** The total number of items */
totalSize: int32;
...PaginationControls;
/** A link to the previous page, if it exists */
prevLink?: url;
/** A link to the next page, if it exists */
nextLink?: url;
};
}
// deeply annoying that I have to copy/paste this...
model TodoItemPatch {
/** The item's title */
title?: TodoItem.title;
/** User that the todo is assigned to */
assignedTo?: TodoItem.assignedTo | null;
/** A longer description of the todo item in markdown format */
description?: TodoItem.description | null;
/** The status of the todo item */
status?: "NotStarted" | "InProgress" | "Completed";
}
model InvalidTodoItem extends ApiError {
@statusCode statusCode: 422;
}
model Page<T> {
@pageItems items: T[];
}
@list op list(...PaginationControls): WithStandardErrors<TodoPage>;
@post
op create(
@header contentType: "application/json",
item: TodoItem,
attachments?: TodoAttachment[],
): WithStandardErrors<TodoItem | InvalidTodoItem>;
@get op get(@path id: TodoItem.id): TodoItem | NotFoundResponse;
@patch op update(
@header contentType: "application/merge-patch+json",
@path id: TodoItem.id,
@body patch: TodoItemPatch,
): TodoItem;
@delete op delete(
@path id: TodoItem.id,
): WithStandardErrors<NoContentResponse | NotFoundResponse>;
@route("{itemId}/attachments")
namespace Attachments {
@list op list(
@path itemId: TodoItem.id,
): WithStandardErrors<Page<TodoAttachment> | NotFoundResponse>;
@sharedRoute
@post
op createAttachment(
@path itemId: TodoItem.id,
@body contents: TodoAttachment,
): WithStandardErrors<NoContentResponse | NotFoundResponse>;
}
}