import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; 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, type DocumentOptions, type ParseOptions, type SchemaOptions, type ToJSOptions, type ToStringOptions, } from "yaml"; export type YamlParseOptions = ParseOptions & DocumentOptions & SchemaOptions & ToJSOptions; export type YamlStringifyOptions = DocumentOptions & SchemaOptions & ParseOptions & CreateNodeOptions & ToStringOptions; 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}).`; } /** * Parses a YAML string into a value. * * **When to use** * * Use when you need a schema getter to parse a present encoded YAML string * during decoding. * * **Details** * * Parse failures become `SchemaIssue.InvalidValue` values. * * **Example** (Parse YAML) * * ```ts * import { parseYaml } from "@t3tools/shared/schemaYaml" * * const parse = parseYaml() * // Getter * ``` * * @see {@link stringifyYaml} for the inverse operation */ export function parseYaml( options?: YamlParseOptions, ): SchemaGetter.Getter { return SchemaGetter.transformOrFail((input: E) => Effect.try({ try: () => parseYamlString(input, options) as unknown, catch: (error) => new SchemaIssue.InvalidValue(Option.none(), { message: formatYamlParseError(error) }), }), ); } /** * Stringifies a present value as YAML. * * **When to use** * * Use when you need a schema getter to serialize a present decoded value to * YAML text during encoding. * * **Details** * * Stringify failures become `SchemaIssue.InvalidValue` values. * * **Example** (Stringify YAML) * * ```ts * import { stringifyYaml } from "@t3tools/shared/schemaYaml" * * const stringify = stringifyYaml() * // Getter * ``` * * @see {@link parseYaml} for the inverse operation */ export function stringifyYaml( options?: YamlStringifyOptions, ): SchemaGetter.Getter { return SchemaGetter.transformOrFail((input: unknown) => Effect.try({ try: () => stringifyYamlValue(input, options), catch: () => new SchemaIssue.InvalidValue(Option.none(), { message: "Failed to stringify YAML." }), }), ); } /** * Decodes a YAML string and encodes a value as YAML text. * * **When to use** * * Use when you need a schema transformation to decode YAML stored or * transmitted as a string before validating the parsed structure. * * **Details** * * Decode and encode failures become `InvalidValue` schema issues. * * **Example** (Parsing YAML) * * ```ts * import * as Schema from "effect/Schema" * import { fromYamlString } from "@t3tools/shared/schemaYaml" * * const schema = Schema.String.pipe(Schema.decodeTo(Schema.Unknown, fromYamlString)) * ``` */ export const fromYamlString = new SchemaTransformation.Transformation( parseYaml(), stringifyYaml(), ); /** * Build a schema that decodes a YAML string into `A`. * * Decode parses the input as YAML before validating the parsed value with the * provided schema. Encode validates the value and serializes it as YAML text. */ export const fromYaml = (schema: S) => Schema.String.pipe(Schema.decodeTo(schema, fromYamlString));