Skip to content

Commit

Permalink
array apis
Browse files Browse the repository at this point in the history
  • Loading branch information
sukovanej committed Jul 14, 2024
1 parent 16c76f7 commit eb15c5c
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 43 deletions.
59 changes: 35 additions & 24 deletions packages/effect-http-node/examples/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,17 @@ const getLesnekEndpoint = Api.get("getLesnek", "/lesnek").pipe(
Api.setRequestQuery(Lesnek),
Api.setSecurity(Security.bearer({ name: "myAwesomeBearerAuth", bearerFormat: "JWT" }))
)
const getMilanEndpoint = Api.get("getMilan", "/milan").pipe(Api.setResponseBody(Schema.String))
const testEndpoint = Api.get("test", "/test").pipe(Api.setResponseBody(Standa), Api.setRequestQuery(Lesnek))
const standaEndpoint = Api.post("standa", "/standa").pipe(Api.setResponseBody(Standa), Api.setRequestBody(Standa))
const getMilanEndpoint = Api.get("getMilan", "/milan").pipe(
Api.setResponseBody(Schema.String)
)
const testEndpoint = Api.get("test", "/test").pipe(
Api.setResponseBody(Standa),
Api.setRequestQuery(Lesnek)
)
const standaEndpoint = Api.post("standa", "/standa").pipe(
Api.setResponseBody(Standa),
Api.setRequestBody(Standa)
)
const handleMilanEndpoint = Api.post("handleMilan", "/petr").pipe(
Api.setResponseBody(HumanSchema),
Api.setRequestBody(HumanSchema)
Expand All @@ -39,35 +47,38 @@ const callStandaEndpoint = Api.put("callStanda", "/api/zdar").pipe(
)

const api = Api.make({ title: "My awesome pets API", version: "1.0.0" }).pipe(
Api.addEndpoint(getLesnekEndpoint),
Api.addEndpoint(getMilanEndpoint),
Api.addEndpoint(testEndpoint),
Api.addEndpoint(standaEndpoint),
Api.addEndpoint(handleMilanEndpoint),
Api.addEndpoint(callStandaEndpoint)
Api.addEndpoints(
getLesnekEndpoint,
getMilanEndpoint,
testEndpoint,
standaEndpoint,
handleMilanEndpoint,
callStandaEndpoint
)
)

const getLesnekHandler = Handler.make(getLesnekEndpoint, ({ query }) =>
Effect.succeed(`hello ${query.name}`).pipe(
Effect.tap(() => Effect.logDebug("hello world"))
))
const handleMilanHandler = Handler.make(handleMilanEndpoint, ({ body }) =>
Effect.map(StuffService, ({ value }) => ({
...body,
randomValue: body.height + value
})))
const getLesnekHandler = Handler.make(
getLesnekEndpoint,
({ query }) => Effect.tap(Effect.succeed(`hello ${query.name}`), () => Effect.logDebug("hello world"))
)
const handleMilanHandler = Handler.make(
handleMilanEndpoint,
({ body }) => Effect.map(StuffService, ({ value }) => ({ ...body, randomValue: body.height + value }))
)
const getMilanHandler = Handler.make(getMilanEndpoint, () => Effect.succeed("Milan"))
const testHandler = Handler.make(testEndpoint, ({ query }) => Effect.succeed(query))
const standaHandler = Handler.make(standaEndpoint, ({ body }) => Effect.succeed(body))
const callStandaHandler = Handler.make(callStandaEndpoint, ({ body }) => Effect.succeed(body.zdar))

const app = RouterBuilder.make(api, { parseOptions: { errors: "all" } }).pipe(
RouterBuilder.handle(getLesnekHandler),
RouterBuilder.handle(handleMilanHandler),
RouterBuilder.handle(getMilanHandler),
RouterBuilder.handle(testHandler),
RouterBuilder.handle(standaHandler),
RouterBuilder.handle(callStandaHandler),
RouterBuilder.handle(
getLesnekHandler,
handleMilanHandler,
getMilanHandler,
testHandler,
standaHandler,
callStandaHandler
),
RouterBuilder.build
)

Expand Down
17 changes: 17 additions & 0 deletions packages/effect-http/dtslint/RouterBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Api, type Handler, RouterBuilder } from "effect-http"

const getArticleEndpoint = Api.get("getArticle", "/article")
const getBookEndpoint = Api.get("getBook", "/book")

const api = Api.make().pipe(
Api.addEndpoint(getArticleEndpoint),
Api.addEndpoint(getBookEndpoint)
)

declare const getArticleHandler: Handler.Handler<typeof getArticleEndpoint, "E1", "R1" | "R2">
declare const getBookHandler: Handler.Handler<typeof getBookEndpoint, "E2" | "E3", "R3">

// $ExpectType RouterBuilder<never, "E1" | "E2" | "E3", "R1" | "R2" | "R3">
RouterBuilder.make(api).pipe(
RouterBuilder.handle(getArticleHandler, getBookHandler)
)
8 changes: 8 additions & 0 deletions packages/effect-http/src/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ export const addEndpoint: <E2 extends ApiEndpoint.ApiEndpoint.Any>(
endpoint: E2
) => <E1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api<E1>) => Api<E1 | E2> = internal.addEndpoint

/**
* @category combining
* @since 1.0.0
*/
export const addEndpoints: <Endpoints extends ReadonlyArray<ApiEndpoint.ApiEndpoint.Any>>(
...endpoint: Endpoints
) => <E1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api<E1>) => Api<E1 | Endpoints[number]> = internal.addEndpoints

/**
* @category combining
* @since 1.0.0
Expand Down
13 changes: 13 additions & 0 deletions packages/effect-http/src/ApiEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ export declare namespace ApiEndpoint {
*/
export type Any = ApiEndpoint<AnyId, ApiRequest.ApiRequest.Any, ApiResponse.ApiResponse.Any, Security.Security.Any>

/**
* Any endpoint with `Request = Request.Any` and `Response = Response.Any`.
*
* @category models
* @since 1.0.0
*/
export type Unknown = ApiEndpoint<
AnyId,
ApiRequest.ApiRequest.Unknown,
ApiResponse.ApiResponse.Unknown,
Security.Security.Unknown
>

/**
* Default endpoint spec.
*
Expand Down
8 changes: 8 additions & 0 deletions packages/effect-http/src/ApiRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ export declare namespace ApiRequest {
*/
export type Any = ApiRequest<any, any, any, any, any>

/**
* Any request with all `Body`, `Path`, `Query` and `Headers` set to `any`.
*
* @category models
* @since 1.0.0
*/
export type Unknown = ApiRequest<unknown, unknown, unknown, unknown, unknown>

/**
* Default request.
*
Expand Down
8 changes: 8 additions & 0 deletions packages/effect-http/src/ApiResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export declare namespace ApiResponse {
*/
export type Any = ApiResponse<AnyStatus, any, any, unknown>

/**
* Any request with all `Body`, `Path`, `Query` and `Headers` set to `Schema.Schema.Any`.
*
* @category models
* @since 1.0.0
*/
export type Unknown = ApiResponse<AnyStatus, unknown, unknown, unknown>

/**
* Default response.
*
Expand Down
32 changes: 32 additions & 0 deletions packages/effect-http/src/Handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,30 @@ export declare namespace Handler {
*/
export type Any = Handler<ApiEndpoint.ApiEndpoint.Any, any, any>

/**
* @category models
* @since 1.0.0
*/
export type Unknown = Handler<ApiEndpoint.ApiEndpoint.Unknown, unknown, unknown>

/**
* @category models
* @since 1.0.0
*/
export type Endpoint<H extends Handler.Any> = [H] extends [Handler<infer A, any, any>] ? A : never

/**
* @category models
* @since 1.0.0
*/
export type Error<H extends Handler.Any> = [H] extends [Handler<any, infer E, any>] ? E : never

/**
* @category models
* @since 1.0.0
*/
export type Context<H extends Handler.Any> = [H] extends [Handler<any, any, infer C>] ? C : never

/**
* @category models
* @since 1.0.0
Expand Down Expand Up @@ -170,3 +194,11 @@ export const getRoute: <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
export const getEndpoint: <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
handler: Handler<A, E, R>
) => A = internal.getEndpoint

/**
* @category refinements
* @since 1.0.0
*/
export const isHandler: (
u: unknown
) => u is Handler<ApiEndpoint.ApiEndpoint.Unknown, unknown, unknown> = internal.isHandler
14 changes: 7 additions & 7 deletions packages/effect-http/src/RouterBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,13 @@ export const handleRaw: <
* @since 1.0.0
*/
export const handle: {
<R2, E2, A extends ApiEndpoint.ApiEndpoint.Any, Endpoint extends A>(
handler: Handler.Handler<Endpoint, E2, R2>
): <R1, E1>(builder: RouterBuilder<A, E1, R1>) => RouterBuilder<
Exclude<A, Endpoint>,
E1 | Exclude<E2, HttpError.HttpError>,
| Exclude<R1 | R2, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
| ApiEndpoint.ApiEndpoint.Context<Endpoint>
<H extends ReadonlyArray<Handler.Handler.Any>>(
...handler: H
): <A extends ApiEndpoint.ApiEndpoint.Any, R1, E1>(builder: RouterBuilder<A, E1, R1>) => RouterBuilder<
Exclude<A, Handler.Handler.Endpoint<H[number]>>,
E1 | Exclude<Handler.Handler.Error<H[number]>, HttpError.HttpError>,
| Exclude<R1 | Handler.Handler.Context<H[number]>, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
| ApiEndpoint.ApiEndpoint.Context<Handler.Handler.Context<H[number]>>
>

<A extends ApiEndpoint.ApiEndpoint.Any, E2, R2, Id extends ApiEndpoint.ApiEndpoint.Id<A>>(
Expand Down
6 changes: 6 additions & 0 deletions packages/effect-http/src/Security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ export declare namespace Security {
*/
export type Any = Security<any, any, any>

/**
* @category models
* @since 1.0.0
*/
export type Unknown = Security<unknown, unknown, unknown>

/**
* @category models
* @since 1.0.0
Expand Down
7 changes: 7 additions & 0 deletions packages/effect-http/src/internal/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Iterable } from "effect"
import * as Array from "effect/Array"
import * as Pipeable from "effect/Pipeable"
import type * as Api from "../Api.js"
Expand Down Expand Up @@ -62,6 +63,12 @@ export const addEndpoint =
return new ApiImpl([...groupsWithoutDefault, newDefaultGroup], api.options) as any
}

/** @internal */
export const addEndpoints =
<A2 extends ReadonlyArray<ApiEndpoint.ApiEndpoint.Any>>(...endpoints: A2) =>
<A1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api.Api<A1>): Api.Api<A1 | A2[number]> =>
Iterable.reduce(endpoints, api as Api.Api<A1 | A2[number]>, (api, endpoint) => addEndpoint(endpoint)(api))

/** @internal */
export const addGroup = <E2 extends ApiEndpoint.ApiEndpoint.Any>(
group: ApiGroup.ApiGroup<E2>
Expand Down
4 changes: 4 additions & 0 deletions packages/effect-http/src/internal/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ export const getRoute = <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
export const getEndpoint = <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
handler: Handler.Handler<A, E, R>
): A => (handler as HandlerImpl<A, E, R>).endpoint

/** @internal */
export const isHandler = (u: unknown): u is Handler.Handler.Unknown =>
typeof u === "object" && u !== null && TypeId in u
41 changes: 29 additions & 12 deletions packages/effect-http/src/internal/router-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as HttpRouter from "@effect/platform/HttpRouter"
import type * as HttpServerRequest from "@effect/platform/HttpServerRequest"
import * as Effect from "effect/Effect"
import { dual } from "effect/Function"
import * as Iterable from "effect/Iterable"
import * as Pipeable from "effect/Pipeable"
import type * as Scope from "effect/Scope"

Expand Down Expand Up @@ -113,22 +114,38 @@ const getRemainingEndpoint = <

/** @internal */
export const handle = (...args: Array<any>) => (builder: RouterBuilder.RouterBuilder.Any): any => {
if (args.length === 2) {
const [id, handler] = args
const endpoint = getRemainingEndpoint(builder, id)
return handle(Handler.make(endpoint, handler))(builder)
if (Handler.isHandler(args[0])) {
return (args as ReadonlyArray<Handler.Handler.Any>).reduce((builder, handler) => {
const remainingEndpoints = removeRemainingEndpoint(
builder,
ApiEndpoint.getId(Handler.getEndpoint(handler))
)
const router = addRoute(builder.router, Handler.getRoute(handler))

return new RouterBuilderImpl(remainingEndpoints, builder.api, router, builder.options)
}, builder)
}

const handler = args[0] as Handler.Handler.Any
const remainingEndpoints = removeRemainingEndpoint(
builder,
ApiEndpoint.getId(Handler.getEndpoint(handler))
)
const router = addRoute(builder.router, Handler.getRoute(handler))

return new RouterBuilderImpl(remainingEndpoints, builder.api, router, builder.options)
const [id, handler] = args
const endpoint = getRemainingEndpoint(builder, id)
return handle(Handler.make(endpoint, handler))(builder)
}

/** @internal */
export const handleMany =
<A extends ApiEndpoint.ApiEndpoint.Any, H extends Handler.Handler.Any>(handlers: Iterable<H>) =>
<R1, E1>(builder: RouterBuilder.RouterBuilder<A, E1, R1>) =>
Iterable.reduce(
handlers,
builder as RouterBuilder.RouterBuilder<
Exclude<A, Handler.Handler.Endpoint<H>>,
E1 | Exclude<Handler.Handler.Error<H>, HttpError.HttpError>,
| Exclude<R1 | Handler.Handler.Context<H>, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
| ApiEndpoint.ApiEndpoint.Context<Handler.Handler.Context<H>>
>,
(builder, handler) => handle(handler)(builder)
)

/** @internal */
export const handler = <A extends Api.Api.Any, E, R, Id extends Api.Api.Ids<A>>(
api: A,
Expand Down

0 comments on commit eb15c5c

Please sign in to comment.