Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ export function mongoContract(schemaPath: string, options?: MongoContractOptions
}
let schema: string;
try {
schema = await readFile(absoluteSchemaPath, 'utf-8');
const rawBuffer = await readFile(absoluteSchemaPath);
schema = new TextDecoder('utf-8', { fatal: true }).decode(rawBuffer);
} catch (error) {
const message = String(error);
const message = error instanceof Error ? error.message : String(error);
return notOk({
summary: `Failed to read Prisma schema at "${schemaPath}"`,
diagnostics: [
Expand Down
5 changes: 3 additions & 2 deletions packages/2-sql/2-authoring/contract-psl/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,10 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
}
let schema: string;
try {
schema = await readFile(absoluteSchemaPath, 'utf-8');
const rawBuffer = await readFile(absoluteSchemaPath);
schema = new TextDecoder('utf-8', { fatal: true }).decode(rawBuffer);
} catch (error) {
const message = String(error);
const message = error instanceof Error ? error.message : String(error);
return notOk({
summary: `Failed to read Prisma schema at "${schemaPath}"`,
diagnostics: [
Expand Down
33 changes: 33 additions & 0 deletions packages/2-sql/2-authoring/contract-psl/test/provider.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'node:buffer';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { applySpecifierDefaultControlPolicy } from '@internal/contract/apply-specifier-default-control-policy';
Expand Down Expand Up @@ -449,6 +450,38 @@ model Other {
expect(codes).toContain('PSL_DUPLICATE_DECLARATION');
expect(codes).toContain('PSL_UNSUPPORTED_FIELD_TYPE');
});

it('returns PSL_SCHEMA_READ_FAILED diagnostic when schema file contains invalid UTF-8 bytes', async () => {
const tempDir = await mkdtemp(join(tmpdir(), 'psl-provider-utf8-'));
tempDirs.push(tempDir);
const schemaPath = join(tempDir, 'schema.prisma');
await writeFile(
schemaPath,
Buffer.concat([
Buffer.from('model User {\n id Int @id // comment with invalid byte: ', 'utf-8'),
Buffer.from([0x97]),
Buffer.from('\n}\n', 'utf-8'),
]),
);

process.chdir(tempDir);
const contract = prismaContract('./schema.prisma', baseOptions);
const result = await contract.source.load(
createPostgresTestContext({ resolvedInputs: [schemaPath] }),
);

expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.failure.summary).toBe('Failed to read Prisma schema at "./schema.prisma"');
expect(result.failure.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({
code: 'PSL_SCHEMA_READ_FAILED',
sourceId: './schema.prisma',
}),
]),
);
});
});

describe('given namespaced extension constructors in schema', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Invalid UTF-8 Schema Handling (Issue #30198)
*
* Verifies that contract emit fails with a non-zero exit code and explicit error
* diagnostics when contract.prisma contains invalid UTF-8 byte sequences (such as a
* lone Windows-1252 0x97 em-dash byte).
*/

import { Buffer } from 'node:buffer';
import { writeFileSync } from 'node:fs';
import { join } from 'pathe';
import { describe, expect, it } from 'vitest';
import { withTempDir } from '../utils/cli-test-helpers';
import { runContractEmit, setupJourney, timeouts } from '../utils/journey-test-helpers';

withTempDir(({ createTempDir }) => {
describe('Issue #30198: Invalid UTF-8 Schema Handling', () => {
it(
'fails with exit code 1 and outputs error when schema contains non-UTF-8 bytes',
async () => {
const ctx = setupJourney({ createTempDir, contractMode: 'psl' });

const schemaPath = join(ctx.testDir, 'contract.prisma');
const invalidSchemaContent = Buffer.concat([
Buffer.from('model User {\n id Int @id // comment with CP-1252 em-dash: ', 'utf-8'),
Buffer.from([0x97]),
Buffer.from('\n}\n', 'utf-8'),
]);
writeFileSync(schemaPath, invalidSchemaContent);

const result = await runContractEmit(ctx, ['--json']);

expect(result.exitCode).toBe(1);
expect(result.stderr).toMatch(/PSL_SCHEMA_READ_FAILED|invalid UTF-8|Failed to decode/i);
},
timeouts.typeScriptCompilation,
);
});
});