Skip to content

Commit e6d4a37

Browse files
authored
add template literal api for defining HttpApiEndpoint path schema (#4035)
1 parent c907ac0 commit e6d4a37

File tree

6 files changed

+216
-83
lines changed

6 files changed

+216
-83
lines changed

.changeset/lemon-eagles-reply.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@effect/platform-node": patch
3+
"@effect/platform": patch
4+
---
5+
6+
add template literal api for defining HttpApiEndpoint path schema

packages/platform-node/examples/api.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,7 @@ export class Authentication extends HttpApiMiddleware.Tag<Authentication>()("Aut
4040

4141
class UsersApi extends HttpApiGroup.make("users")
4242
.add(
43-
HttpApiEndpoint.get("findById", "/:id")
44-
.setPath(Schema.Struct({
45-
id: Schema.NumberFromString
46-
}))
43+
HttpApiEndpoint.get("findById")`/${HttpApiSchema.param("id", Schema.NumberFromString)}`
4744
.addSuccess(User)
4845
.setHeaders(Schema.Struct({
4946
page: Schema.NumberFromString.pipe(

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -282,14 +282,11 @@ class Authorization extends HttpApiMiddleware.Tag<Authorization>()("Authorizatio
282282

283283
class GroupsApi extends HttpApiGroup.make("groups")
284284
.add(
285-
HttpApiEndpoint.get("findById", "/:id")
286-
.setPath(Schema.Struct({
287-
id: Schema.NumberFromString
288-
}))
285+
HttpApiEndpoint.get("findById")`/${HttpApiSchema.param("id", Schema.NumberFromString)}`
289286
.addSuccess(Group)
290287
)
291288
.add(
292-
HttpApiEndpoint.post("create", "/")
289+
HttpApiEndpoint.post("create")`/`
293290
.setPayload(Schema.Union(
294291
Schema.Struct(Struct.pick(Group.fields, "name")),
295292
Schema.Struct({ foo: Schema.String }).pipe(
@@ -309,14 +306,11 @@ class GroupsApi extends HttpApiGroup.make("groups")
309306

310307
class UsersApi extends HttpApiGroup.make("users")
311308
.add(
312-
HttpApiEndpoint.get("findById", "/:id")
313-
.setPath(Schema.Struct({
314-
id: Schema.NumberFromString
315-
}))
309+
HttpApiEndpoint.get("findById")`/${HttpApiSchema.param("id", Schema.NumberFromString)}`
316310
.addSuccess(User)
317311
)
318312
.add(
319-
HttpApiEndpoint.post("create", "/")
313+
HttpApiEndpoint.post("create")`/`
320314
.setPayload(Schema.Struct(Struct.omit(
321315
User.fields,
322316
"id",
@@ -329,7 +323,7 @@ class UsersApi extends HttpApiGroup.make("users")
329323
.addError(UserError)
330324
)
331325
.add(
332-
HttpApiEndpoint.get("list", "/")
326+
HttpApiEndpoint.get("list")`/`
333327
.setHeaders(Schema.Struct({
334328
page: Schema.NumberFromString.pipe(
335329
Schema.optionalWith({ default: () => 1 })
@@ -342,7 +336,7 @@ class UsersApi extends HttpApiGroup.make("users")
342336
.annotateContext(OpenApi.annotations({ identifier: "listUsers" }))
343337
)
344338
.add(
345-
HttpApiEndpoint.post("upload", "/upload")
339+
HttpApiEndpoint.post("upload")`/upload`
346340
.setPayload(HttpApiSchema.Multipart(Schema.Struct({
347341
file: Multipart.SingleFileSchema
348342
})))

packages/platform/README.md

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Let's define a simple CRUD API for managing users. First, we need to make an
3131
`HttpApiGroup` that contains our endpoints.
3232

3333
```ts
34-
import { HttpApiEndpoint, HttpApiGroup } from "@effect/platform"
34+
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform"
3535
import { Schema } from "effect"
3636

3737
// Our domain "User" Schema
@@ -44,17 +44,16 @@ class User extends Schema.Class<User>("User")({
4444
const usersApi = HttpApiGroup.make("users")
4545
.add(
4646
// each endpoint has a name and a path
47-
HttpApiEndpoint.get("findById", "/users/:id")
47+
// You can use a template string to define path parameter schemas
48+
HttpApiEndpoint.get(
49+
"findById"
50+
)`/users/${HttpApiSchema.param("id", Schema.NumberFromString)}`
4851
// the endpoint can have a Schema for a successful response
4952
.addSuccess(User)
50-
// and here is a Schema for the path parameters
51-
.setPath(
52-
Schema.Struct({
53-
id: Schema.NumberFromString
54-
})
55-
)
5653
)
5754
.add(
55+
// you can also pass the path as a string and use `.setPath` to define the
56+
// path parameter schema
5857
HttpApiEndpoint.post("create", "/users")
5958
.addSuccess(User)
6059
// and here is a Schema for the request payload / body
@@ -68,9 +67,15 @@ const usersApi = HttpApiGroup.make("users")
6867
)
6968
)
7069
// by default, the endpoint will respond with a 204 No Content
71-
.add(HttpApiEndpoint.del("delete", "/users/:id"))
7270
.add(
73-
HttpApiEndpoint.patch("update", "/users/:id")
71+
HttpApiEndpoint.del(
72+
"delete"
73+
)`/users/${HttpApiSchema.param("id", Schema.NumberFromString)}`
74+
)
75+
.add(
76+
HttpApiEndpoint.patch(
77+
"update"
78+
)`/users/${HttpApiSchema.param("id", Schema.NumberFromString)}`
7479
.addSuccess(User)
7580
.setPayload(
7681
Schema.Struct({
@@ -85,7 +90,9 @@ We will use this API style in the following examples:
8590

8691
```ts
8792
class UsersApi extends HttpApiGroup.make("users").add(
88-
HttpApiEndpoint.get("findById", "/users/:id")
93+
HttpApiEndpoint.get(
94+
"findById"
95+
)`/users/${HttpApiSchema.param("id", Schema.NumberFromString)}`
8996
// ... same as above
9097
) {}
9198
```
@@ -117,7 +124,9 @@ import { OpenApi } from "@effect/platform"
117124

118125
class UsersApi extends HttpApiGroup.make("users")
119126
.add(
120-
HttpApiEndpoint.get("findById", "/users/:id")
127+
HttpApiEndpoint.get(
128+
"findById"
129+
)`/users/${HttpApiSchema.param("id", Schema.NumberFromString)}`
121130
// ... same as above
122131
)
123132
// add an OpenApi title & description
@@ -170,11 +179,12 @@ class Unauthorized extends Schema.TaggedError<Unauthorized>()(
170179

171180
class UsersApi extends HttpApiGroup.make("users")
172181
.add(
173-
HttpApiEndpoint.get("findById", "/users/:id")
182+
HttpApiEndpoint.get(
183+
"findById"
184+
)`/users/${HttpApiSchema.param("id", Schema.NumberFromString)}`
174185
// here we are adding our error response
175186
.addError(UserNotFound, { status: 404 })
176187
.addSuccess(User)
177-
.setPath(Schema.Struct({ id: Schema.NumberFromString }))
178188
)
179189
// or we could add an error to the group
180190
.addError(Unauthorized, { status: 401 }) {}
@@ -195,7 +205,7 @@ shape of the multipart request.
195205
import { HttpApiSchema, Multipart } from "@effect/platform"
196206

197207
class UsersApi extends HttpApiGroup.make("users").add(
198-
HttpApiEndpoint.post("upload", "/users/upload").setPayload(
208+
HttpApiEndpoint.post("upload")`/users/upload`.setPayload(
199209
HttpApiSchema.Multipart(
200210
Schema.Struct({
201211
// add a "files" field to the schema
@@ -215,7 +225,7 @@ Here is an example of changing the encoding to text/csv:
215225

216226
```ts
217227
class UsersApi extends HttpApiGroup.make("users").add(
218-
HttpApiEndpoint.get("csv", "/users/csv").addSuccess(
228+
HttpApiEndpoint.get("csv")`/users/csv`.addSuccess(
219229
Schema.String.pipe(
220230
HttpApiSchema.withEncoding({
221231
kind: "Text",
@@ -248,7 +258,8 @@ import {
248258
HttpApi,
249259
HttpApiBuilder,
250260
HttpApiEndpoint,
251-
HttpApiGroup
261+
HttpApiGroup,
262+
HttpApiSchema
252263
} from "@effect/platform"
253264
import { DateTime, Effect, Layer, Schema } from "effect"
254265

@@ -260,13 +271,11 @@ class User extends Schema.Class<User>("User")({
260271
}) {}
261272

262273
class UsersApi extends HttpApiGroup.make("users").add(
263-
HttpApiEndpoint.get("findById", "/users/:id")
264-
.addSuccess(User)
265-
.setPath(
266-
Schema.Struct({
267-
id: Schema.NumberFromString
268-
})
269-
)
274+
HttpApiEndpoint.get(
275+
"findById"
276+
)`/users/${HttpApiSchema.param("id", Schema.NumberFromString)}`.addSuccess(
277+
User
278+
)
270279
) {}
271280

272281
class MyApi extends HttpApi.empty.add(UsersApi) {}
@@ -428,7 +437,7 @@ class Logger extends HttpApiMiddleware.Tag<Logger>()("Http/Logger", {
428437
// apply the middleware to an `HttpApiGroup`
429438
class UsersApi extends HttpApiGroup.make("users")
430439
.add(
431-
HttpApiEndpoint.get("findById", "/:id")
440+
HttpApiEndpoint.get("findById")`/${Schema.NumberFromString}`
432441
// apply the middleware to a single endpoint
433442
.middleware(Logger)
434443
)
@@ -492,7 +501,7 @@ class Authorization extends HttpApiMiddleware.Tag<Authorization>()(
492501
// apply the middleware to an `HttpApiGroup`
493502
class UsersApi extends HttpApiGroup.make("users")
494503
.add(
495-
HttpApiEndpoint.get("findById", "/:id")
504+
HttpApiEndpoint.get("findById")`/${Schema.NumberFromString}`
496505
// apply the middleware to a single endpoint
497506
.middleware(Authorization)
498507
)

0 commit comments

Comments
 (0)