| author | |
| committer | |
| log | 6888276f19d47b9a73546e92721a56abbc8ab6c8 |
| tree | 2d4bde33a3070c409063396924de0360c386512f |
| parent | e06c33c38dba362e6a9a0733542cb37d56c024a3 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>2 files changed, 50 insertions(+), 7 deletions(-)
packages/shared/src/schemaYaml.test.ts+38-2| ... | ... | @@ -50,9 +50,45 @@ tags: |
| 50 | 50 | expect(decodeYaml("answer: 42\n")).toEqual({ answer: 42 }); |
| 51 | 51 | }); |
| 52 | 52 | |
| 53 | it("rejects malformed YAML", () => { | |
| 53 | it("reports malformed YAML with safe structural diagnostics", () => { | |
| 54 | 54 | const decodeYaml = Schema.decodeUnknownSync(fromYaml(Schema.Unknown)); |
| 55 | const secret = "credential=secret-value"; | |
| 56 | let error: unknown; | |
| 55 | 57 | |
| 56 | expect(() => decodeYaml("name: ok\n bad-indent: nope\n")).toThrow(); | |
| 58 | try { | |
| 59 | decodeYaml(`name: ${secret}\n bad-indent: nope\n`); | |
| 60 | } catch (cause) { | |
| 61 | error = cause; | |
| 62 | } | |
| 63 | ||
| 64 | expect(Schema.isSchemaError(error)).toBe(true); | |
| 65 | if (!Schema.isSchemaError(error)) { | |
| 66 | throw new Error("Expected a schema error"); | |
| 67 | } | |
| 68 | expect(error.message).toBe("Invalid YAML (code=BLOCK_AS_IMPLICIT_KEY, line=1, column=7)."); | |
| 69 | expect(error.message).not.toContain(secret); | |
| 70 | }); | |
| 71 | ||
| 72 | it("does not expose stringify failure details", () => { | |
| 73 | const encodeYaml = Schema.encodeSync(fromYaml(Schema.Unknown)); | |
| 74 | const secret = "credential=secret-value"; | |
| 75 | let error: unknown; | |
| 76 | ||
| 77 | try { | |
| 78 | encodeYaml({ | |
| 79 | toJSON() { | |
| 80 | throw new Error(secret); | |
| 81 | }, | |
| 82 | }); | |
| 83 | } catch (cause) { | |
| 84 | error = cause; | |
| 85 | } | |
| 86 | ||
| 87 | expect(Schema.isSchemaError(error)).toBe(true); | |
| 88 | if (!Schema.isSchemaError(error)) { | |
| 89 | throw new Error("Expected a schema error"); | |
| 90 | } | |
| 91 | expect(error.message).toBe("Failed to stringify YAML."); | |
| 92 | expect(error.message).not.toContain(secret); | |
| 57 | 93 | }); |
| 58 | 94 | }); |
packages/shared/src/schemaYaml.ts+12-5| ... | ... | @@ -5,6 +5,7 @@ import * as SchemaGetter from "effect/SchemaGetter"; |
| 5 | 5 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 6 | 6 | import * as SchemaTransformation from "effect/SchemaTransformation"; |
| 7 | 7 | import { |
| 8 | YAMLParseError, | |
| 8 | 9 | parse as parseYamlString, |
| 9 | 10 | stringify as stringifyYamlValue, |
| 10 | 11 | type CreateNodeOptions, |
| ... | ... | @@ -22,8 +23,14 @@ export type YamlStringifyOptions = DocumentOptions & |
| 22 | 23 | CreateNodeOptions & |
| 23 | 24 | ToStringOptions; |
| 24 | 25 | |
| 25 | function formatYamlError(error: unknown): string { | |
| 26 | return error instanceof Error ? error.message : String(error); | |
| 26 | function formatYamlParseError(error: unknown): string { | |
| 27 | if (!(error instanceof YAMLParseError)) { | |
| 28 | return "Invalid YAML."; | |
| 29 | } | |
| 30 | ||
| 31 | const position = error.linePos?.[0]; | |
| 32 | const location = position === undefined ? "" : `, line=${position.line}, column=${position.col}`; | |
| 33 | return `Invalid YAML (code=${error.code}${location}).`; | |
| 27 | 34 | } |
| 28 | 35 | |
| 29 | 36 | /** |
| ... | ... | @@ -56,7 +63,7 @@ export function parseYaml<E extends string>( |
| 56 | 63 | Effect.try({ |
| 57 | 64 | try: () => parseYamlString(input, options) as unknown, |
| 58 | 65 | catch: (error) => |
| 59 | new SchemaIssue.InvalidValue(Option.some(input), { message: formatYamlError(error) }), | |
| 66 | new SchemaIssue.InvalidValue(Option.none(), { message: formatYamlParseError(error) }), | |
| 60 | 67 | }), |
| 61 | 68 | ); |
| 62 | 69 | } |
| ... | ... | @@ -90,8 +97,8 @@ export function stringifyYaml( |
| 90 | 97 | return SchemaGetter.transformOrFail((input: unknown) => |
| 91 | 98 | Effect.try({ |
| 92 | 99 | try: () => stringifyYamlValue(input, options), |
| 93 | catch: (error) => | |
| 94 | new SchemaIssue.InvalidValue(Option.some(input), { message: formatYamlError(error) }), | |
| 100 | catch: () => | |
| 101 | new SchemaIssue.InvalidValue(Option.none(), { message: "Failed to stringify YAML." }), | |
| 95 | 102 | }), |
| 96 | 103 | ); |
| 97 | 104 | } |