Skip to content

Commit

Permalink
HttpApi annotation with additional schemas (#3939)
Browse files Browse the repository at this point in the history
  • Loading branch information
KhraksMamtsov authored Nov 15, 2024
1 parent c138fa7 commit 3cc6514
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 20 deletions.
32 changes: 32 additions & 0 deletions .changeset/modern-toes-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
"@effect/platform": patch
---

Added the ability to annotate the `HttpApi` with additional schemas
Which will be taken into account when generating `components.schemas` section of `OpenApi` schema

```ts
import { Schema } from "effect"
import { HttpApi } from "@effect/platform"

HttpApi.empty.annotate(HttpApi.AdditionalSchemas, [
Schema.Struct({
contentType: Schema.String,
length: Schema.Int
}).annotations({
identifier: "ComponentsSchema"
})
])
/**
{
"openapi": "3.0.3",
...
"components": {
"schemas": {
"ComponentsSchema": {...},
...
},
...
}
*/
```
11 changes: 11 additions & 0 deletions packages/platform-node/test/HttpApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,17 @@ class Api extends HttpApi.empty
.add(UsersApi.prefix("/users"))
.addError(GlobalError, { status: 413 })
.annotateContext(OpenApi.annotations({ title: "API", summary: "test api summary" }))
.annotate(
HttpApi.AdditionalSchemas,
[
Schema.Struct({
contentType: Schema.String,
length: Schema.Int
}).annotations({
identifier: "ComponentsSchema"
})
]
)
{}

// impl
Expand Down
106 changes: 86 additions & 20 deletions packages/platform-node/test/fixtures/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"paths": {
"/groups/{id}": {
"get": {
"tags": ["groups"],
"tags": [
"groups"
],
"operationId": "groups.findById",
"parameters": [
{
Expand Down Expand Up @@ -60,7 +62,9 @@
},
"/groups": {
"post": {
"tags": ["groups"],
"tags": [
"groups"
],
"operationId": "groups.create",
"parameters": [],
"security": [],
Expand Down Expand Up @@ -104,7 +108,9 @@
"application/json": {
"schema": {
"type": "object",
"required": ["name"],
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
Expand All @@ -120,7 +126,9 @@
},
"/users/{id}": {
"get": {
"tags": ["Users API"],
"tags": [
"Users API"
],
"operationId": "users.findById",
"parameters": [
{
Expand Down Expand Up @@ -173,7 +181,9 @@
},
"/users": {
"post": {
"tags": ["Users API"],
"tags": [
"Users API"
],
"operationId": "users.create",
"parameters": [
{
Expand Down Expand Up @@ -234,7 +244,9 @@
"application/json": {
"schema": {
"type": "object",
"required": ["name"],
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
Expand Down Expand Up @@ -318,7 +330,9 @@
},
"/users/upload": {
"post": {
"tags": ["Users API"],
"tags": [
"Users API"
],
"operationId": "users.upload",
"parameters": [],
"security": [
Expand All @@ -333,7 +347,10 @@
"application/json": {
"schema": {
"type": "object",
"required": ["contentType", "length"],
"required": [
"contentType",
"length"
],
"properties": {
"contentType": {
"type": "string"
Expand Down Expand Up @@ -375,7 +392,9 @@
"multipart/form-data": {
"schema": {
"type": "object",
"required": ["file"],
"required": [
"file"
],
"properties": {
"file": {
"type": "array",
Expand Down Expand Up @@ -407,9 +426,30 @@
],
"components": {
"schemas": {
"ComponentsSchema": {
"additionalProperties": false,
"properties": {
"contentType": {
"type": "string"
},
"length": {
"description": "an integer",
"title": "Int",
"type": "integer"
}
},
"required": [
"contentType",
"length"
],
"type": "object"
},
"Group": {
"type": "object",
"required": ["id", "name"],
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "integer",
Expand All @@ -430,13 +470,21 @@
},
"HttpApiDecodeError": {
"type": "object",
"required": ["issues", "message", "_tag"],
"required": [
"issues",
"message",
"_tag"
],
"properties": {
"issues": {
"type": "array",
"items": {
"type": "object",
"required": ["_tag", "path", "message"],
"required": [
"_tag",
"path",
"message"
],
"properties": {
"_tag": {
"enum": [
Expand Down Expand Up @@ -474,7 +522,9 @@
"type": "string"
},
"_tag": {
"enum": ["HttpApiDecodeError"]
"enum": [
"HttpApiDecodeError"
]
}
},
"additionalProperties": false,
Expand All @@ -483,10 +533,14 @@
},
"GlobalError": {
"type": "object",
"required": ["_tag"],
"required": [
"_tag"
],
"properties": {
"_tag": {
"enum": ["GlobalError"]
"enum": [
"GlobalError"
]
}
},
"additionalProperties": false,
Expand All @@ -495,7 +549,11 @@
},
"User": {
"type": "object",
"required": ["id", "name", "createdAt"],
"required": [
"id",
"name",
"createdAt"
],
"properties": {
"id": {
"type": "integer",
Expand All @@ -519,10 +577,14 @@
},
"UserError": {
"type": "object",
"required": ["_tag"],
"required": [
"_tag"
],
"properties": {
"_tag": {
"enum": ["UserError"]
"enum": [
"UserError"
]
}
},
"additionalProperties": false,
Expand All @@ -531,10 +593,14 @@
},
"NoStatusError": {
"type": "object",
"required": ["_tag"],
"required": [
"_tag"
],
"properties": {
"_tag": {
"enum": ["NoStatusError"]
"enum": [
"NoStatusError"
]
}
},
"additionalProperties": false,
Expand Down
9 changes: 9 additions & 0 deletions packages/platform/src/HttpApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,12 @@ const extractMembers = (
}
return members
}

/**
* @since 1.0.0
* @category tags
*/
export class AdditionalSchemas extends Context.Tag("@effect/platform/HttpApi/AdditionalSchemas")<
AdditionalSchemas,
ReadonlyArray<Schema.Schema.All>
>() {}
3 changes: 3 additions & 0 deletions packages/platform/src/OpenApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ export const fromApi = <A extends HttpApi.HttpApi.Any>(self: A): OpenAPISpec =>
const scheme = makeSecurityScheme(security)
spec.components!.securitySchemes![name] = scheme
}
Option.map(Context.getOption(api.annotations, HttpApi.AdditionalSchemas), (componentSchemas) => {
componentSchemas.forEach((componentSchema) => makeJsonSchemaOrRef(componentSchema))
})
Option.map(Context.getOption(api.annotations, Description), (description) => {
spec.info.description = description
})
Expand Down

0 comments on commit 3cc6514

Please sign in to comment.