| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Schema from "effect/Schema"; |
| 3 | import * as SchemaGetter from "effect/SchemaGetter"; |
| 4 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 5 | import * as SchemaTransformation from "effect/SchemaTransformation"; |
| 6 | import { |
| 7 | YAMLParseError, |
| 8 | parse as parseYamlString, |
| 9 | stringify as stringifyYamlValue, |
| 10 | type CreateNodeOptions, |
| 11 | type DocumentOptions, |
| 12 | type ParseOptions, |
| 13 | type SchemaOptions, |
| 14 | type ToJSOptions, |
| 15 | type ToStringOptions, |
| 16 | } from "yaml"; |
| 17 | |
| 18 | export type YamlParseOptions = ParseOptions & DocumentOptions & SchemaOptions & ToJSOptions; |
| 19 | export type YamlStringifyOptions = DocumentOptions & |
| 20 | SchemaOptions & |
| 21 | ParseOptions & |
| 22 | CreateNodeOptions & |
| 23 | ToStringOptions; |
| 24 | |
| 25 | function formatYamlParseError(error: unknown): string { |
| 26 | if (!(error instanceof YAMLParseError)) { |
| 27 | return "Invalid YAML."; |
| 28 | } |
| 29 | |
| 30 | const position = error.linePos?.[0]; |
| 31 | const location = position === undefined ? "" : `, line=${position.line}, column=${position.col}`; |
| 32 | return `Invalid YAML (code=${error.code}${location}).`; |
| 33 | } |
| 34 | |
| 35 | /** Parses YAML during decoding, reporting parse failures as InvalidValue issues. */ |
| 36 | function parseYaml<E extends string>(options?: YamlParseOptions): SchemaGetter.Getter<unknown, E> { |
| 37 | return SchemaGetter.transformEffect((input: E) => |
| 38 | Effect.try({ |
| 39 | try: () => parseYamlString(input, options) as unknown, |
| 40 | catch: (error) => new SchemaIssue.InvalidValue({ message: formatYamlParseError(error) }), |
| 41 | }), |
| 42 | ); |
| 43 | } |
| 44 | |
| 45 | /** Serializes YAML during encoding, reporting stringify failures as InvalidValue issues. */ |
| 46 | function stringifyYaml(options?: YamlStringifyOptions): SchemaGetter.Getter<string, unknown> { |
| 47 | return SchemaGetter.transformEffect((input: unknown) => |
| 48 | Effect.try({ |
| 49 | try: () => stringifyYamlValue(input, options), |
| 50 | catch: () => new SchemaIssue.InvalidValue({ message: "Failed to stringify YAML." }), |
| 51 | }), |
| 52 | ); |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * Decodes a YAML string and encodes a value as YAML text. |
| 57 | * |
| 58 | * **When to use** |
| 59 | * |
| 60 | * Use when you need a schema transformation to decode YAML stored or |
| 61 | * transmitted as a string before validating the parsed structure. |
| 62 | * |
| 63 | * **Details** |
| 64 | * |
| 65 | * Decode and encode failures become `InvalidValue` schema issues. |
| 66 | * |
| 67 | * **Example** (Parsing YAML) |
| 68 | * |
| 69 | * ```ts |
| 70 | * import * as Schema from "effect/Schema" |
| 71 | * import { fromYamlString } from "@t3tools/shared/schemaYaml" |
| 72 | * |
| 73 | * const schema = Schema.String.pipe(Schema.decodeTo(Schema.Unknown, fromYamlString)) |
| 74 | * ``` |
| 75 | */ |
| 76 | export const fromYamlString = new SchemaTransformation.Transformation<unknown, string>( |
| 77 | parseYaml(), |
| 78 | stringifyYaml(), |
| 79 | ); |
| 80 | |
| 81 | /** |
| 82 | * Build a schema that decodes a YAML string into `A`. |
| 83 | * |
| 84 | * Decode parses the input as YAML before validating the parsed value with the |
| 85 | * provided schema. Encode validates the value and serializes it as YAML text. |
| 86 | */ |
| 87 | export const fromYaml = <S extends Schema.Top>(schema: S) => |
| 88 | Schema.String.pipe(Schema.decodeTo(schema, fromYamlString)); |