1import * as Effect from "effect/Effect";
2import * as Schema from "effect/Schema";
3
4import * as CodexError from "../errors.ts";
5
6export const JsonRpcId = Schema.Union([Schema.Number, Schema.String]);
7
8const JsonRpcError = Schema.Struct({
9 code: Schema.Number,
10 message: Schema.String,
11 data: Schema.optional(Schema.Unknown),
12});
13
14export const JsonRpcResponseEnvelope = Schema.Struct({
15 id: JsonRpcId,
16 result: Schema.optional(Schema.Unknown),
17 error: Schema.optional(JsonRpcError),
18});
19
20export const decodeOptionalPayload = <A, I>(
21 method: string,
22 schema: Schema.Codec<A, I> | undefined,
23 raw: unknown,
24): Effect.Effect<A, CodexError.CodexAppServerRequestError> => {
25 if (!schema) {
26 if (raw === undefined) {
27 return Effect.sync(() => undefined as A);
28 }
29 return Effect.fail(
30 CodexError.CodexAppServerRequestError.unexpectedPayload(method, "decode-payload", raw),
31 );
32 }
33
34 return Schema.decodeUnknownEffect(schema)(raw).pipe(
35 Effect.mapError((error) =>
36 CodexError.CodexAppServerRequestError.invalidPayload(method, "decode-payload", error),
37 ),
38 );
39};
40
41export const encodeOptionalPayload = <A, I>(
42 method: string,
43 schema: Schema.Codec<A, I> | undefined,
44 payload: A,
45): Effect.Effect<I | undefined, CodexError.CodexAppServerRequestError> => {
46 if (!schema) {
47 if (payload === undefined) {
48 return Effect.undefined;
49 }
50 return Effect.fail(
51 CodexError.CodexAppServerRequestError.unexpectedPayload(method, "encode-payload", payload),
52 );
53 }
54
55 return Schema.encodeEffect(schema)(payload).pipe(
56 Effect.mapError((error) =>
57 CodexError.CodexAppServerRequestError.invalidPayload(method, "encode-payload", error),
58 ),
59 );
60};
61
62export const decodeNotificationPayload = <A, I>(
63 method: string,
64 schema: Schema.Codec<A, I> | undefined,
65 raw: unknown,
66): Effect.Effect<A, CodexError.CodexAppServerProtocolParseError> =>
67 decodeOptionalPayload(method, schema, raw).pipe(
68 Effect.mapError((error) =>
69 CodexError.CodexAppServerProtocolParseError.fromRequestError(
70 "decode-notification-payload",
71 method,
72 error,
73 ),
74 ),
75 );
76
77export const runHandler = Effect.fnUntraced(function* <A, B>(
78 handler: ((payload: A) => Effect.Effect<B, CodexError.CodexAppServerError>) | undefined,
79 payload: A,
80 method: string,
81) {
82 if (!handler) {
83 return yield* CodexError.CodexAppServerRequestError.methodNotFound(method);
84 }
85
86 return yield* handler(payload).pipe(
87 Effect.mapError((error) =>
88 CodexError.CodexAppServerRequestError.fromAppServerError(error, method),
89 ),
90 );
91});