Skip to content
Merged
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
40 changes: 38 additions & 2 deletions packages/shared/src/schemaYaml.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,45 @@ tags:
expect(decodeYaml("answer: 42\n")).toEqual({ answer: 42 });
});

it("rejects malformed YAML", () => {
it("reports malformed YAML with safe structural diagnostics", () => {
const decodeYaml = Schema.decodeUnknownSync(fromYaml(Schema.Unknown));
const secret = "credential=secret-value";
let error: unknown;

expect(() => decodeYaml("name: ok\n bad-indent: nope\n")).toThrow();
try {
decodeYaml(`name: ${secret}\n bad-indent: nope\n`);
} catch (cause) {
error = cause;
}

expect(Schema.isSchemaError(error)).toBe(true);
if (!Schema.isSchemaError(error)) {
throw new Error("Expected a schema error");
}
expect(error.message).toBe("Invalid YAML (code=BLOCK_AS_IMPLICIT_KEY, line=1, column=7).");
expect(error.message).not.toContain(secret);
});

it("does not expose stringify failure details", () => {
const encodeYaml = Schema.encodeSync(fromYaml(Schema.Unknown));
const secret = "credential=secret-value";
let error: unknown;

try {
encodeYaml({
toJSON() {
throw new Error(secret);
},
});
} catch (cause) {
error = cause;
}

expect(Schema.isSchemaError(error)).toBe(true);
if (!Schema.isSchemaError(error)) {
throw new Error("Expected a schema error");
}
expect(error.message).toBe("Failed to stringify YAML.");
expect(error.message).not.toContain(secret);
});
});
17 changes: 12 additions & 5 deletions packages/shared/src/schemaYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as SchemaGetter from "effect/SchemaGetter";
import * as SchemaIssue from "effect/SchemaIssue";
import * as SchemaTransformation from "effect/SchemaTransformation";
import {
YAMLParseError,
parse as parseYamlString,
stringify as stringifyYamlValue,
type CreateNodeOptions,
Expand All @@ -22,8 +23,14 @@ export type YamlStringifyOptions = DocumentOptions &
CreateNodeOptions &
ToStringOptions;

function formatYamlError(error: unknown): string {
return error instanceof Error ? error.message : String(error);
function formatYamlParseError(error: unknown): string {
if (!(error instanceof YAMLParseError)) {
return "Invalid YAML.";
}

const position = error.linePos?.[0];
const location = position === undefined ? "" : `, line=${position.line}, column=${position.col}`;
return `Invalid YAML (code=${error.code}${location}).`;
}

/**
Expand Down Expand Up @@ -56,7 +63,7 @@ export function parseYaml<E extends string>(
Effect.try({
try: () => parseYamlString(input, options) as unknown,
catch: (error) =>
new SchemaIssue.InvalidValue(Option.some(input), { message: formatYamlError(error) }),
new SchemaIssue.InvalidValue(Option.none(), { message: formatYamlParseError(error) }),
}),
);
}
Expand Down Expand Up @@ -90,8 +97,8 @@ export function stringifyYaml(
return SchemaGetter.transformOrFail((input: unknown) =>
Effect.try({
try: () => stringifyYamlValue(input, options),
catch: (error) =>
new SchemaIssue.InvalidValue(Option.some(input), { message: formatYamlError(error) }),
catch: () =>
new SchemaIssue.InvalidValue(Option.none(), { message: "Failed to stringify YAML." }),
}),
);
}
Expand Down
Loading