Skip to content

Commit c9cb0c3

Browse files
juliusmarmingecodex
andcommitted
fix(codex): return typed schema collection failures
Co-authored-by: codex <[email protected]>
1 parent c27e679 commit c9cb0c3

2 files changed

Lines changed: 59 additions & 12 deletions

File tree

‎packages/effect-codex-app-server/scripts/generate.test.ts‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ const isGeneratorSchemaDocumentDecodeError = Schema.is(
1313
Generator.GeneratorSchemaDocumentDecodeError,
1414
);
1515
const isGeneratorFormatExitError = Schema.is(Generator.GeneratorFormatExitError);
16+
const isGeneratorSchemaNameParseError = Schema.is(Generator.GeneratorSchemaNameParseError);
17+
const isGeneratorSchemaValueDeclarationMissingError = Schema.is(
18+
Generator.GeneratorSchemaValueDeclarationMissingError,
19+
);
1620

1721
const httpClient = (response: Response) =>
1822
HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, response)));
@@ -56,7 +60,10 @@ describe("Codex schema generator errors", () => {
5660
expect(error.stage).toBe("request");
5761
expect(error.cause).toBeDefined();
5862
const { cause: _, ...directDiagnostics } = error;
59-
expect(JSON.stringify(directDiagnostics)).not.toMatch(
63+
expect(directDiagnostics).not.toHaveProperty("url");
64+
expect(directDiagnostics.urlProtocol).toBe("https:");
65+
expect(directDiagnostics.urlHostname).toBe("example.test");
66+
expect(error.message).not.toMatch(
6067
/generator-user|generator-password|private|token|generator-secret|fragment/,
6168
);
6269
}),
@@ -132,4 +139,29 @@ describe("Codex schema generator errors", () => {
132139
expect(spawned?.args).toEqual(["fmt", generatedDir, "--write"]);
133140
}).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner));
134141
});
142+
143+
it.effect("returns malformed generated schema declarations as typed failures", () =>
144+
Effect.gen(function* () {
145+
const missingValueError = yield* Generator.collectSchemaEntries(
146+
"export type Session = string;",
147+
).pipe(Effect.flip);
148+
assert(isGeneratorSchemaValueDeclarationMissingError(missingValueError));
149+
expect(missingValueError).toMatchObject({
150+
lineIndex: 0,
151+
typeDeclarationLength: 29,
152+
nextLinePresent: false,
153+
});
154+
expect(missingValueError).not.toHaveProperty("typeDeclaration");
155+
156+
const nameParseError = yield* Generator.collectSchemaEntries(
157+
"export type @ = string;\nexport const invalid = Schema.String;",
158+
).pipe(Effect.flip);
159+
assert(isGeneratorSchemaNameParseError(nameParseError));
160+
expect(nameParseError).toMatchObject({
161+
lineIndex: 0,
162+
typeDeclarationLength: 23,
163+
});
164+
expect(nameParseError).not.toHaveProperty("typeDeclaration");
165+
}),
166+
);
135167
});

‎packages/effect-codex-app-server/scripts/generate.ts‎

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,27 @@ export class GeneratorFormatExitError extends Schema.TaggedErrorClass<GeneratorF
149149

150150
export class GeneratorSchemaValueDeclarationMissingError extends Schema.TaggedErrorClass<GeneratorSchemaValueDeclarationMissingError>()(
151151
"GeneratorSchemaValueDeclarationMissingError",
152-
{ typeDeclaration: Schema.String },
152+
{
153+
lineIndex: Schema.Number,
154+
typeDeclarationLength: Schema.Number,
155+
nextLinePresent: Schema.Boolean,
156+
nextLineLength: Schema.optional(Schema.Number),
157+
},
153158
) {
154159
override get message(): string {
155-
return `Generated schema type declaration has no following value declaration: ${this.typeDeclaration}`;
160+
return `Generated schema type declaration at line ${this.lineIndex + 1} has no following value declaration.`;
156161
}
157162
}
158163

159164
export class GeneratorSchemaNameParseError extends Schema.TaggedErrorClass<GeneratorSchemaNameParseError>()(
160165
"GeneratorSchemaNameParseError",
161-
{ typeDeclaration: Schema.String },
166+
{
167+
lineIndex: Schema.Number,
168+
typeDeclarationLength: Schema.Number,
169+
},
162170
) {
163171
override get message(): string {
164-
return `Could not extract a schema name from generated declaration: ${this.typeDeclaration}`;
172+
return `Could not extract a schema name from generated declaration at line ${this.lineIndex + 1}.`;
165173
}
166174
}
167175

@@ -383,9 +391,7 @@ export const formatGeneratedFiles = Effect.fn("formatGeneratedFiles")(function*
383391
}
384392
});
385393

386-
function collectSchemaEntries(
387-
chunk: string,
388-
): ReadonlyArray<{ readonly name: string; readonly code: string }> {
394+
export const collectSchemaEntries = Effect.fn("collectSchemaEntries")(function* (chunk: string) {
389395
const lines = chunk
390396
.split("\n")
391397
.map((line) => line.trim())
@@ -400,12 +406,20 @@ function collectSchemaEntries(
400406

401407
const constLine = lines[index + 1];
402408
if (!constLine?.startsWith("export const ")) {
403-
throw new GeneratorSchemaValueDeclarationMissingError({ typeDeclaration: typeLine });
409+
return yield* new GeneratorSchemaValueDeclarationMissingError({
410+
lineIndex: index,
411+
typeDeclarationLength: typeLine.length,
412+
nextLinePresent: constLine !== undefined,
413+
...(constLine === undefined ? {} : { nextLineLength: constLine.length }),
414+
});
404415
}
405416

406417
const match = /^export type ([A-Za-z0-9_]+)/.exec(typeLine);
407418
if (!match?.[1]) {
408-
throw new GeneratorSchemaNameParseError({ typeDeclaration: typeLine });
419+
return yield* new GeneratorSchemaNameParseError({
420+
lineIndex: index,
421+
typeDeclarationLength: typeLine.length,
422+
});
409423
}
410424

411425
entries.push({
@@ -416,7 +430,7 @@ function collectSchemaEntries(
416430
}
417431

418432
return entries;
419-
}
433+
});
420434

421435
function normalizeNullableTypes(value: Schema.Json): Schema.Json {
422436
if (Array.isArray(value)) {
@@ -811,7 +825,8 @@ const generateFiles = Effect.fn("generateFiles")(function* () {
811825
const generatedEntries = new Map<string, string>();
812826
const output = generator.generate("openapi-3.1", aggregateSchemas as never, false).trim();
813827
if (output.length > 0) {
814-
for (const entry of collectSchemaEntries(output)) {
828+
const schemaEntries = yield* collectSchemaEntries(output);
829+
for (const entry of schemaEntries) {
815830
if (!generatedEntries.has(entry.name)) {
816831
generatedEntries.set(entry.name, entry.code);
817832
}

0 commit comments

Comments
 (0)