1import * as Effect from "effect/Effect";
2import * as Schema from "effect/Schema";
3import * as SchemaGetter from "effect/SchemaGetter";
4import * as SchemaIssue from "effect/SchemaIssue";
5import * as SchemaTransformation from "effect/SchemaTransformation";
6import {
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
18export type YamlParseOptions = ParseOptions & DocumentOptions & SchemaOptions & ToJSOptions;
19export type YamlStringifyOptions = DocumentOptions &
20 SchemaOptions &
21 ParseOptions &
22 CreateNodeOptions &
23 ToStringOptions;
24
25function 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. */
36function 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. */
46function 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 */
76export 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 */
87export const fromYaml = <S extends Schema.Top>(schema: S) =>
88 Schema.String.pipe(Schema.decodeTo(schema, fromYamlString));