History

Tighten HTTP error schemas, keep rich causes internally (#137)

* Tighten HTTP error schemas, keep rich causes internally Establish a clear rule: Schema.TaggedError is HTTP wire-facing — every field must be a safe public field. Data.TaggedError is internal-only; it can carry rich cause: unknown for logging/debugging and can't be accidentally wired to an HttpApi endpoint because it isn't a Schema. - OpenApiParseError (HTTP-exposed): drop error: Schema.Defect field - GraphqlIntrospectionError (HTTP-exposed): drop error: Schema.Defect field; log underlying causes via Effect.logError at construction sites in introspect.ts - OpenApiInvocationError, GraphqlInvocationError, GoogleDiscoveryParseError, GoogleDiscoveryInvocationError, KeychainError, ToolInvocationError: convert Schema.TaggedError -> Data.TaggedError, rename error -> cause - Cloud auth (UserStoreError, WorkOSError): keep empty schema + 500 annotation, log full cause via Effect.tapErrorCause at service wrappers * Extract withServiceLogging helper + errors.test.ts contract Collapse the repetitive tapErrorCause + mapError + withSpan ceremony at each service wrapper into one helper in auth/errors.ts. Add a real HttpApi integration test that pins down the contract: - Wire response contains only declared tagged-error fields - Server-side logs capture the full Cause chain (drizzle query, params, nested pg Error.cause) The test uses a real HttpApi + HttpApiBuilder.toWebHandler + fetch round-trip with a capturing Logger to verify both sides without mocking. Validates Option C from the earlier discussion: service-level logging is the only pattern that preserves the full cause because mapError discards it before any edge middleware could see it. * Remove errors.test.ts * Simplify graphql introspection error handling Drop ad-hoc Effect.logError sprinkles on branch-level failures (bad status, returned errors, missing __schema) — the tagged error's message already carries the useful info and there's no Cause chain to tap. Keep tapErrorCause + mapError inline for the two cases where an upstream error exists (HTTP request failure, JSON parse failure).

Rhys Sullivan committed 7 months ago

Commit d2ce9d71 parent 0ccccd914 files · +93 −107Browse files

Showing the changes to packages/plugins/openapi/src (3 of 14 files). Show all

Unified · Split

3 files
  1. packages/plugins/openapi/src/sdk/errors.ts +8 −9
  2. packages/plugins/openapi/src/sdk/invoke.ts +1 −3
  3. packages/plugins/openapi/src/sdk/parse.ts +0 −1
packages/plugins/openapi/src/sdk/errors.ts+8−9
@@ -1,10 +1,10 @@
1−import { Schema } from "effect";
1+import { Data, Schema } from "effect";
2+import type { Option } from "effect";
23
34 export class OpenApiParseError extends Schema.TaggedError<OpenApiParseError>()(
45 "OpenApiParseError",
56 {
67 message: Schema.String,
7− error: Schema.Defect,
88 },
99 ) {}
1010
@@ -15,11 +15,10 @@ export class OpenApiExtractionError extends Schema.TaggedError<OpenApiExtraction · 4 unmodified lines
1515 },
1616 ) {}
1717
18−export class OpenApiInvocationError extends Schema.TaggedError<OpenApiInvocationError>()(
18+export class OpenApiInvocationError extends Data.TaggedError(
1919 "OpenApiInvocationError",
20− {
21− message: Schema.String,
22− statusCode: Schema.optionalWith(Schema.Number, { as: "Option" }),
23− error: Schema.Defect,
24− },
25−) {}
20+)<{
21+ readonly message: string;
22+ readonly statusCode: Option.Option<number>;
23+ readonly cause?: unknown;
24+}> {}
packages/plugins/openapi/src/sdk/invoke.ts+1−3
@@ -76,7 +76,6 @@ const resolvePath = Effect.fn("OpenApi.resolvePath")(function* ( · 75 unmodified lines
7676 return yield* new OpenApiInvocationError({
7777 message: `Missing required path parameter: ${param.name}`,
7878 statusCode: Option.none(),
79− error: undefined,
8079 });
8180 }
8281 continue;
@@ -111,7 +110,6 @@ const resolvePath = Effect.fn("OpenApi.resolvePath")(function* ( · 28 unmodified lines
111110 return yield* new OpenApiInvocationError({
112111 message: `Unresolved path parameters: ${[...new Set(unresolved)].join(", ")}`,
113112 statusCode: Option.none(),
114− error: undefined,
115113 });
116114 }
117115
@@ -245,7 +243,7 @@ export const invoke = Effect.fn("OpenApi.invoke")(function* ( · 127 unmodified lines
245243 new OpenApiInvocationError({
246244 message: `HTTP request failed: ${err.message}`,
247245 statusCode: Option.none(),
248− error: err,
246+ cause: err,
249247 }),
250248 ),
251249 );
packages/plugins/openapi/src/sdk/parse.ts+0−1
@@ -20,7 +20,6 @@ export const parse = Effect.fn("OpenApi.parse")(function* (input: string) { · 19 unmodified lines
2020 catch: (error) =>
2121 new OpenApiParseError({
2222 message: `Failed to parse OpenAPI document: ${error instanceof Error ? error.message : String(error)}`,
23− error,
2423 }),
2524 });
2625