Skip to content

Commit 3cc6514

Browse files
HttpApi annotation with additional schemas (#3939)
1 parent c138fa7 commit 3cc6514

File tree

5 files changed

+141
-20
lines changed

5 files changed

+141
-20
lines changed

.changeset/modern-toes-count.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@effect/platform": patch
3+
---
4+
5+
Added the ability to annotate the `HttpApi` with additional schemas
6+
Which will be taken into account when generating `components.schemas` section of `OpenApi` schema
7+
8+
```ts
9+
import { Schema } from "effect"
10+
import { HttpApi } from "@effect/platform"
11+
12+
HttpApi.empty.annotate(HttpApi.AdditionalSchemas, [
13+
Schema.Struct({
14+
contentType: Schema.String,
15+
length: Schema.Int
16+
}).annotations({
17+
identifier: "ComponentsSchema"
18+
})
19+
])
20+
/**
21+
{
22+
"openapi": "3.0.3",
23+
...
24+
"components": {
25+
"schemas": {
26+
"ComponentsSchema": {...},
27+
...
28+
},
29+
...
30+
}
31+
*/
32+
```

packages/platform-node/test/HttpApi.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,17 @@ class Api extends HttpApi.empty
305305
.add(UsersApi.prefix("/users"))
306306
.addError(GlobalError, { status: 413 })
307307
.annotateContext(OpenApi.annotations({ title: "API", summary: "test api summary" }))
308+
.annotate(
309+
HttpApi.AdditionalSchemas,
310+
[
311+
Schema.Struct({
312+
contentType: Schema.String,
313+
length: Schema.Int
314+
}).annotations({
315+
identifier: "ComponentsSchema"
316+
})
317+
]
318+
)
308319
{}
309320

310321
// impl

packages/platform-node/test/fixtures/openapi.json

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"paths": {
99
"/groups/{id}": {
1010
"get": {
11-
"tags": ["groups"],
11+
"tags": [
12+
"groups"
13+
],
1214
"operationId": "groups.findById",
1315
"parameters": [
1416
{
@@ -60,7 +62,9 @@
6062
},
6163
"/groups": {
6264
"post": {
63-
"tags": ["groups"],
65+
"tags": [
66+
"groups"
67+
],
6468
"operationId": "groups.create",
6569
"parameters": [],
6670
"security": [],
@@ -104,7 +108,9 @@
104108
"application/json": {
105109
"schema": {
106110
"type": "object",
107-
"required": ["name"],
111+
"required": [
112+
"name"
113+
],
108114
"properties": {
109115
"name": {
110116
"type": "string"
@@ -120,7 +126,9 @@
120126
},
121127
"/users/{id}": {
122128
"get": {
123-
"tags": ["Users API"],
129+
"tags": [
130+
"Users API"
131+
],
124132
"operationId": "users.findById",
125133
"parameters": [
126134
{
@@ -173,7 +181,9 @@
173181
},
174182
"/users": {
175183
"post": {
176-
"tags": ["Users API"],
184+
"tags": [
185+
"Users API"
186+
],
177187
"operationId": "users.create",
178188
"parameters": [
179189
{
@@ -234,7 +244,9 @@
234244
"application/json": {
235245
"schema": {
236246
"type": "object",
237-
"required": ["name"],
247+
"required": [
248+
"name"
249+
],
238250
"properties": {
239251
"name": {
240252
"type": "string"
@@ -318,7 +330,9 @@
318330
},
319331
"/users/upload": {
320332
"post": {
321-
"tags": ["Users API"],
333+
"tags": [
334+
"Users API"
335+
],
322336
"operationId": "users.upload",
323337
"parameters": [],
324338
"security": [
@@ -333,7 +347,10 @@
333347
"application/json": {
334348
"schema": {
335349
"type": "object",
336-
"required": ["contentType", "length"],
350+
"required": [
351+
"contentType",
352+
"length"
353+
],
337354
"properties": {
338355
"contentType": {
339356
"type": "string"
@@ -375,7 +392,9 @@
375392
"multipart/form-data": {
376393
"schema": {
377394
"type": "object",
378-
"required": ["file"],
395+
"required": [
396+
"file"
397+
],
379398
"properties": {
380399
"file": {
381400
"type": "array",
@@ -407,9 +426,30 @@
407426
],
408427
"components": {
409428
"schemas": {
429+
"ComponentsSchema": {
430+
"additionalProperties": false,
431+
"properties": {
432+
"contentType": {
433+
"type": "string"
434+
},
435+
"length": {
436+
"description": "an integer",
437+
"title": "Int",
438+
"type": "integer"
439+
}
440+
},
441+
"required": [
442+
"contentType",
443+
"length"
444+
],
445+
"type": "object"
446+
},
410447
"Group": {
411448
"type": "object",
412-
"required": ["id", "name"],
449+
"required": [
450+
"id",
451+
"name"
452+
],
413453
"properties": {
414454
"id": {
415455
"type": "integer",
@@ -430,13 +470,21 @@
430470
},
431471
"HttpApiDecodeError": {
432472
"type": "object",
433-
"required": ["issues", "message", "_tag"],
473+
"required": [
474+
"issues",
475+
"message",
476+
"_tag"
477+
],
434478
"properties": {
435479
"issues": {
436480
"type": "array",
437481
"items": {
438482
"type": "object",
439-
"required": ["_tag", "path", "message"],
483+
"required": [
484+
"_tag",
485+
"path",
486+
"message"
487+
],
440488
"properties": {
441489
"_tag": {
442490
"enum": [
@@ -474,7 +522,9 @@
474522
"type": "string"
475523
},
476524
"_tag": {
477-
"enum": ["HttpApiDecodeError"]
525+
"enum": [
526+
"HttpApiDecodeError"
527+
]
478528
}
479529
},
480530
"additionalProperties": false,
@@ -483,10 +533,14 @@
483533
},
484534
"GlobalError": {
485535
"type": "object",
486-
"required": ["_tag"],
536+
"required": [
537+
"_tag"
538+
],
487539
"properties": {
488540
"_tag": {
489-
"enum": ["GlobalError"]
541+
"enum": [
542+
"GlobalError"
543+
]
490544
}
491545
},
492546
"additionalProperties": false,
@@ -495,7 +549,11 @@
495549
},
496550
"User": {
497551
"type": "object",
498-
"required": ["id", "name", "createdAt"],
552+
"required": [
553+
"id",
554+
"name",
555+
"createdAt"
556+
],
499557
"properties": {
500558
"id": {
501559
"type": "integer",
@@ -519,10 +577,14 @@
519577
},
520578
"UserError": {
521579
"type": "object",
522-
"required": ["_tag"],
580+
"required": [
581+
"_tag"
582+
],
523583
"properties": {
524584
"_tag": {
525-
"enum": ["UserError"]
585+
"enum": [
586+
"UserError"
587+
]
526588
}
527589
},
528590
"additionalProperties": false,
@@ -531,10 +593,14 @@
531593
},
532594
"NoStatusError": {
533595
"type": "object",
534-
"required": ["_tag"],
596+
"required": [
597+
"_tag"
598+
],
535599
"properties": {
536600
"_tag": {
537-
"enum": ["NoStatusError"]
601+
"enum": [
602+
"NoStatusError"
603+
]
538604
}
539605
},
540606
"additionalProperties": false,

packages/platform/src/HttpApi.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,12 @@ const extractMembers = (
349349
}
350350
return members
351351
}
352+
353+
/**
354+
* @since 1.0.0
355+
* @category tags
356+
*/
357+
export class AdditionalSchemas extends Context.Tag("@effect/platform/HttpApi/AdditionalSchemas")<
358+
AdditionalSchemas,
359+
ReadonlyArray<Schema.Schema.All>
360+
>() {}

packages/platform/src/OpenApi.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ export const fromApi = <A extends HttpApi.HttpApi.Any>(self: A): OpenAPISpec =>
174174
const scheme = makeSecurityScheme(security)
175175
spec.components!.securitySchemes![name] = scheme
176176
}
177+
Option.map(Context.getOption(api.annotations, HttpApi.AdditionalSchemas), (componentSchemas) => {
178+
componentSchemas.forEach((componentSchema) => makeJsonSchemaOrRef(componentSchema))
179+
})
177180
Option.map(Context.getOption(api.annotations, Description), (description) => {
178181
spec.info.description = description
179182
})

0 commit comments

Comments
 (0)