1import { assert, describe, it } from "@effect/vitest"
2import { Context } from "effect"
3import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/http-api"
4
5describe("HttpApi", () => {
6 it("stores the supplied identifier", () => {
7 const api = HttpApi.make("Api")
8
9 assert.strictEqual(api.identifier, "Api")
10 })
11
12 it("initializes groups as a readonly record", () => {
13 const api = HttpApi.make("Api")
14
15 assert.deepStrictEqual(api.groups, {})
16 })
17
18 it("stores __proto__ group and endpoint identifiers as own properties", () => {
19 const group = HttpApiGroup.make("__proto__").add(
20 HttpApiEndpoint.get("__proto__", "/proto")
21 )
22 const child = HttpApi.make("Child").add(group)
23 const api = HttpApi.make("Api").addHttpApi(child)
24
25 assert.isTrue(Object.hasOwn(group.endpoints, "__proto__"))
26 assert.isTrue(Object.hasOwn(child.groups, "__proto__"))
27 assert.isTrue(Object.hasOwn(api.groups, "__proto__"))
28 assert.isTrue(Object.hasOwn(api.groups["__proto__"].endpoints, "__proto__"))
29
30 const reflected: Array<string> = []
31 HttpApi.reflect(api, {
32 onGroup: ({ group }) => reflected.push(group.identifier),
33 onEndpoint: ({ endpoint }) => reflected.push(endpoint.identifier)
34 })
35 assert.deepStrictEqual(reflected, ["__proto__", "__proto__"])
36 })
37
38 it("does not mutate groups from the added API", () => {
39 const group = HttpApiGroup.make("users").annotate(OpenApi.Title, "Users")
40 const child = HttpApi.make("Child")
41 .annotate(OpenApi.Description, "Child API")
42 .add(group)
43 const originalAnnotations = group.annotations
44
45 const parent = HttpApi.make("Parent").addHttpApi(child)
46
47 assert.strictEqual(group.annotations, originalAnnotations)
48 assert.strictEqual(child.groups.users, group)
49 assert.notStrictEqual(parent.groups.users, group)
50 assert.strictEqual(Context.getUnsafe(parent.groups.users.annotations, OpenApi.Title), "Users")
51 assert.strictEqual(Context.getUnsafe(parent.groups.users.annotations, OpenApi.Description), "Child API")
52 })
53
54 it("does not add inherited groups", () => {
55 const inherited = HttpApiGroup.make("inherited")
56 const child = HttpApi.make("Child").add(HttpApiGroup.make("own"))
57 Object.setPrototypeOf(child.groups, { inherited })
58
59 const parent = HttpApi.make("Parent").addHttpApi(child)
60
61 assert.isTrue(Object.hasOwn(parent.groups, "own"))
62 assert.isFalse(Object.hasOwn(parent.groups, "inherited"))
63 })
64
65 it("keeps annotations from API variants isolated", () => {
66 const group = HttpApiGroup.make("users").annotate(OpenApi.Title, "Users")
67 const child = HttpApi.make("Child").add(group)
68 const publicChild = child.annotate(OpenApi.Description, "Public API")
69 const internalChild = child.annotate(OpenApi.Description, "Internal API")
70
71 const publicParent = HttpApi.make("PublicParent").addHttpApi(publicChild)
72 const internalParent = HttpApi.make("InternalParent").addHttpApi(internalChild)
73
74 assert.strictEqual(Context.getUnsafe(publicParent.groups.users.annotations, OpenApi.Description), "Public API")
75 assert.strictEqual(Context.getUnsafe(internalParent.groups.users.annotations, OpenApi.Description), "Internal API")
76 assert.notStrictEqual(publicParent.groups.users, internalParent.groups.users)
77
78 const publicTag = OpenApi.fromApi(publicParent).tags.find((tag) => tag.name === "Users")
79 const internalTag = OpenApi.fromApi(internalParent).tags.find((tag) => tag.name === "Users")
80 assert.isDefined(publicTag)
81 assert.isDefined(internalTag)
82 assert.strictEqual(publicTag.description, "Public API")
83 assert.strictEqual(internalTag.description, "Internal API")
84 })
85
86 it("preserves group annotation precedence", () => {
87 const group = HttpApiGroup.make("users").annotate(OpenApi.Title, "Group title")
88 const child = HttpApi.make("Child")
89 .annotate(OpenApi.Title, "API title")
90 .add(group)
91
92 const parent = HttpApi.make("Parent").addHttpApi(child)
93
94 assert.strictEqual(Context.getUnsafe(parent.groups.users.annotations, OpenApi.Title), "Group title")
95 })
96})