| 1 | import { Effect, Layer } from "effect" |
| 2 | import { HttpApiBuilder, HttpApiError } from "effect/http-api" |
| 3 | import { Api } from "../../api/Api.ts" |
| 4 | import { CurrentUser } from "../../api/Authorization.ts" |
| 5 | import { AuthorizationLayer } from "../Authorization.ts" |
| 6 | import { Users } from "../Users.ts" |
| 7 | |
| 8 | // The handlers without their dependencies provided, so tests can supply an |
| 9 | // alternative `Users` implementation. |
| 10 | export const UsersApiHandlersNoDeps = HttpApiBuilder.group( |
| 11 | Api, |
| 12 | "users", |
| 13 | Effect.fn(function*(handlers) { |
| 14 | const users = yield* Users |
| 15 | |
| 16 | return handlers.handleAll({ |
| 17 | list: ({ query }) => |
| 18 | users.list(query.search).pipe( |
| 19 | // The list endpoint expects no errors, so we convert any potential |
| 20 | // errors into a 500 Internal Server Error. |
| 21 | Effect.orDie |
| 22 | ), |
| 23 | search: Effect.fn(function*({ payload }) { |
| 24 | if (payload.search === "bad-request") { |
| 25 | // You can use the built in error types like any other |
| 26 | // Schema.TaggedError |
| 27 | return yield* new HttpApiError.RequestTimeout() |
| 28 | } |
| 29 | return yield* users.list(payload.search).pipe( |
| 30 | Effect.catchReason( |
| 31 | "UsersError", |
| 32 | "SearchQueryTooShort", |
| 33 | // Re-fail the "SearchQueryTooShort" reason |
| 34 | Effect.fail, |
| 35 | // All other reasons are unexpected, so we convert them into a 500 |
| 36 | // Internal Server Error. |
| 37 | Effect.die |
| 38 | ) |
| 39 | ) |
| 40 | }), |
| 41 | getById: ({ params }) => |
| 42 | users.getById(params.id).pipe( |
| 43 | // You can also use Effect.catchReasons to handle multiple error |
| 44 | // reasons at once |
| 45 | Effect.catchReasons("UsersError", { |
| 46 | UserNotFound: (e) => Effect.fail(e) |
| 47 | }, Effect.die) |
| 48 | ), |
| 49 | create: ({ payload }) => |
| 50 | users.create(payload).pipe( |
| 51 | Effect.orDie |
| 52 | // You could alse use Effect.unwrapReason to moves rror reasons up to |
| 53 | // the top level, so you can handle them with Effect.catch or |
| 54 | // Effect.catchTag etc. |
| 55 | // |
| 56 | // Effect.unwrapReason("UsersError"), |
| 57 | // Effect.catchTags({ |
| 58 | // UserNotFound: Effect.die, |
| 59 | // SearchQueryTooShort: Effect.die |
| 60 | // }) |
| 61 | ), |
| 62 | update: ({ params, payload }) => |
| 63 | users.update(params.id, payload).pipe( |
| 64 | Effect.catchReasons("UsersError", { |
| 65 | UserNotFound: (e) => Effect.fail(e) |
| 66 | }, Effect.die) |
| 67 | ), |
| 68 | me: () => |
| 69 | // The Authorization middleware provides the CurrentUser service, so we |
| 70 | // can access it here. |
| 71 | CurrentUser |
| 72 | }) |
| 73 | }) |
| 74 | ) |
| 75 | |
| 76 | // The handlers with all dependencies provided, ready to serve. The SQL-backed |
| 77 | // `Users.layer` keeps the database wiring out of the server entrypoint. |
| 78 | export const UsersApiHandlers = UsersApiHandlersNoDeps.pipe( |
| 79 | Layer.provide([Users.layer, AuthorizationLayer]) |
| 80 | ) |