/** * Runs schemas against real values. * * Schema parsers construct values from schema input, check whether a value * matches a schema, decode encoded input, and encode decoded values back to * their external form. This module exposes those operations through several * result styles, including `Effect`, `Promise`, `Exit`, `Option`, `Result`, and * synchronous functions that throw. It also contains the lower-level runner that * walks a schema AST and reports schema failures as `SchemaIssue.Issue` values. * * @stability stable * @since 4.0.0 */ import * as Cause from "./Cause.ts" import * as Effect from "./Effect.ts" import * as Exit from "./Exit.ts" import { effectIsExit } from "./internal/effect.ts" import * as InternalSchemaCause from "./internal/schema/cause.ts" import * as CompilerRegistry from "./internal/schema/compilerRegistry.ts" import * as InternalParser from "./internal/schema/parser.ts" import * as Option from "./Option.ts" import * as Result from "./Result.ts" import type * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" import * as SchemaIssue from "./SchemaIssue.ts" /** * Creates an effectful maker for the schema's decoded type side. * * **When to use** * * Use to construct decoded schema values in `Effect` while preserving * construction failures as `SchemaIssue.Issue` values in the error channel. * * **Details** * * The returned function accepts constructor input, applies constructor defaults, * runs type-side validation unless checks are disabled, and fails with a * `SchemaIssue.Issue` when construction fails. * * @stability stable * @category constructors * @since 4.0.0 */ export function makeEffect(schema: S) { const ast = schema.ast let parser: ReturnType> return (input: S["~type.make.in"], options?: Schema.MakeOptions): Effect.Effect => { return (parser ??= runWithCompiler(constructorCompiler, SchemaAST.toType(ast)))( input, options?.disableChecks ? options?.parseOptions ? { ...options.parseOptions, disableChecks: true } : { disableChecks: true } : options?.parseOptions ) } } /** * Creates a synchronous maker that returns `Option.some` with the constructed * value on success, or `Option.none` when construction fails with schema issues. * * **When to use** * * Use when you need to validate schema constructor input and only care whether * construction succeeds, without exposing `SchemaIssue.Issue` details. * * **Gotchas** * * Only causes made entirely of schema issues are converted to `Option.none`. * Causes that contain defects, interruptions, or asynchronous work at this * synchronous boundary throw an `Error` whose cause is the underlying `Cause`. * * @stability stable * @category constructors * @since 4.0.0 */ export function makeOption(schema: S) { const parser = makeEffect(schema) return (input: S["~type.make.in"], options?: Schema.MakeOptions): Option.Option => { const exit = Effect.runSyncExit(parser(input, options)) if (Exit.isSuccess(exit)) { return Option.some(exit.value) } InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Option adapter can only return none for schema issues") return Option.none() } } /** * Creates a synchronous maker for the schema's decoded type side. * * **When to use** * * Use to construct decoded schema values synchronously when invalid input * should throw an `Error` whose cause is `SchemaIssue.Issue`. * * **Details** * * The returned function constructs a value from constructor input and throws an * `Error` with the `SchemaIssue.Issue` in its `cause` when construction fails. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or asynchronous work at this * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, * instead of being converted to a schema validation error. * * @stability stable * @category constructors * @since 4.0.0 */ export function make(schema: S) { return makeConstructorSync(SchemaAST.toType(schema.ast)) } /** * Creates a type guard that checks whether an input satisfies the schema's decoded * type side. * * **When to use** * * Use to build a type guard for checking the decoded side of a schema without * exposing issue details. * * **Details** * * The guard returns `true` on successful validation and `false` when validation * fails only with schema issues, without exposing issue details. * * **Gotchas** * * Only causes made entirely of schema issues are converted to `false`. Causes * that contain defects, interruptions, or asynchronous work at this synchronous * boundary throw an `Error` whose cause is the underlying `Cause`. * * @stability stable * @category guards * @since 3.10.0 */ export function is(schema: S): (input: I) => input is I & S["Type"] { return _is(schema.ast) } function makeIs(ast: SchemaAST.AST): (input: I) => input is I & T { if (!CompilerRegistry.compilerAdaptersEnabled) { const parser = asExit(run(ast)) return (input: I): input is I & T => { const exit = parser(input, SchemaAST.defaultParseOptions) if (Exit.isSuccess(exit)) return true InternalSchemaCause.getSchemaIssueOrThrow( exit.cause, "Type guard adapter can only return false for schema issues" ) return false } } const entry = CompilerRegistry.resolve(ast) const guard = entry.is if (guard !== undefined) { return (input: I): input is I & T => { try { return guard(input, SchemaAST.defaultParseOptions) } catch (error) { InternalSchemaCause.getSchemaIssueOrThrow( Cause.die(error), "Type guard adapter can only return false for schema issues" ) return false } } } const parser = entry.parser return (input: I): input is I & T => { const exit = Effect.runSyncExit(parserResult(parser(input, SchemaAST.defaultParseOptions), input)) if (Exit.isSuccess(exit)) return true InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Type guard adapter can only return false for schema issues") return false } } /** @internal */ export function _is(ast: SchemaAST.AST) { const typeAST = SchemaAST.toType(ast) let guard: (input: I) => input is I & T = (input: I): input is I & T => { guard = makeIs(typeAST) return guard(input) } return (input: I): input is I & T => { return guard(input) } } /** @internal */ export function _issue(ast: SchemaAST.AST) { const parser = run(ast) return (input: unknown, options: SchemaAST.ParseOptions): SchemaIssue.Issue | undefined => { const exit = Effect.runSyncExit(parser(input, options)) if (Exit.isSuccess(exit)) { return undefined } return InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Issue adapter can only return schema issues") } } /** * Asserts that an input satisfies the schema's decoded type side. * * **When to use** * * Use to assert that an input satisfies the decoded side of a schema when schema * validation failures should throw an `Error` whose cause is `SchemaIssue.Issue`. * * **Details** * * The assertion returns normally when validation succeeds. When the input does * not satisfy the schema with a schema-only failure, it throws an `Error` with * the `SchemaIssue.Issue` in its `cause`. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or asynchronous work at this * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, * instead of being converted to a schema validation error. * * @stability stable * @category guards * @since 4.0.0 */ export function asserts(schema: S, input: I): asserts input is I & S["Type"] { const ast = SchemaAST.toType(schema.ast) const result = CompilerRegistry.resolve(ast).parser(input, SchemaAST.defaultParseOptions) const exit = Effect.runSyncExit(parserResult(result, input)) if (Exit.isFailure(exit)) { const issue = InternalSchemaCause.getSchemaIssueOrThrow( exit.cause, "Assertion adapter can only throw schema issues" ) throw new Error("Schema validation failed", { cause: issue }) } } /** * Creates an effectful decoder for `unknown` input. * * **When to use** * * Use when you need to decode untyped boundary input in an `Effect` whose * failure channel is `SchemaIssue.Issue`, while preserving transformations * and service requirements. * * **Details** * * The returned function succeeds with the schema's decoded `Type` or fails with a * `SchemaIssue.Issue`. Decoding service requirements are preserved in the returned * `Effect`. Parse options may be provided when creating the decoder and overridden * when applying it. * * @see {@link decodeEffect} for input already typed as the schema's `Encoded` type * * @stability stable * @category decoding * @since 4.0.0 */ export function decodeUnknownEffect( schema: S, options?: SchemaAST.ParseOptions ): ( input: unknown, options?: SchemaAST.ParseOptions ) => Effect.Effect { const parser = run(schema.ast) return options === undefined ? parser : (input, overrideOptions) => parser(input, mergeParseOptions(options, overrideOptions)) } /** * Creates an effectful decoder for input already typed as the schema's `Encoded` * type. * * **When to use** * * Use when you already have input typed as the schema's `Encoded` type and * need an `Effect` whose failure channel is `SchemaIssue.Issue`, while * preserving decoding service requirements. * * **Details** * * The returned function succeeds with the decoded `Type` or fails with a * `SchemaIssue.Issue`, preserving any decoding service requirements in the * returned `Effect`. * * @see {@link decodeUnknownEffect} for untyped boundary input * @see {@link encodeEffect} for the opposite direction * * @stability stable * @category decoding * @since 4.0.0 */ export const decodeEffect: ( schema: S, options?: SchemaAST.ParseOptions ) => ( input: S["Encoded"], options?: SchemaAST.ParseOptions ) => Effect.Effect = decodeUnknownEffect /** * Creates a Promise-based decoder for `unknown` input. * * **When to use** * * Use when you need to decode untyped input with a service-free schema and * return a JavaScript `Promise`. * * **Details** * * The returned function resolves with the decoded `Type` on success and rejects * with an `Error` whose cause is a `SchemaIssue.Issue` on decoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or other non-schema reasons reject * with an `Error` whose cause is the underlying `Cause`. * * @see {@link decodePromise} for input already typed as the schema's `Encoded` type * @see {@link decodeUnknownEffect} for schemas that require decoding services or when failures should remain in `Effect` * * @stability stable * @category decoding * @since 3.10.0 */ export function decodeUnknownPromise>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Promise { return asPromise(decodeUnknownEffect(schema, options)) } /** * Creates a Promise-based decoder for input already typed as the schema's * `Encoded` type. * * **When to use** * * Use when you already have input typed as the schema's `Encoded` type and need * decoding to return a JavaScript `Promise`. * * **Details** * * The returned function resolves with the decoded `Type` on success and rejects * with an `Error` whose cause is a `SchemaIssue.Issue` on decoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or other non-schema reasons reject * with an `Error` whose cause is the underlying `Cause`. * * @see {@link decodeUnknownPromise} for untyped input returning a JavaScript `Promise` * @see {@link decodeEffect} for preserving decoding services and failures in `Effect` * * @stability stable * @category decoding * @since 3.10.0 */ export function decodePromise>( schema: S, options?: SchemaAST.ParseOptions ): (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Promise { return asPromise(decodeEffect(schema, options)) } /** * Creates a synchronous decoder for `unknown` input that reports failure safely * as an `Exit`. * * **When to use** * * Use when you need to decode unknown input synchronously into an `Exit` whose * failure contains `SchemaIssue.Issue`. * * **Details** * * The returned function produces `Exit.Success` with the decoded `Type`. * Schema issues are represented by an `Exit.Failure` cause containing a * `SchemaIssue.Issue`. * * **Gotchas** * * Because this adapter runs synchronously, async decoding work can produce an * `Exit.Failure` with a defect cause. When the cause contains both schema * issues and non-schema reasons, all reasons remain in the returned `Cause`. * * @see {@link decodeExit} for input already typed as the schema's `Encoded` type * @see {@link decodeUnknownEffect} for preserving decoding services and failures in `Effect` * @see {@link decodeUnknownResult} for returning schema issues as data * @see {@link decodeUnknownSync} for throwing on decoding failure * * @stability stable * @category decoding * @since 4.0.0 */ export function decodeUnknownExit>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Exit.Exit { return asExit(decodeUnknownEffect(schema, options)) } /** * Creates a synchronous decoder for input already typed as the schema's `Encoded` * type, reporting failure safely as an `Exit`. * * **When to use** * * Use when you need synchronous decoding of already typed `Encoded` input into * an `Exit` whose failure contains `SchemaIssue.Issue`. * * **Details** * * The returned function produces `Exit.Success` with the decoded `Type` or * `Exit.Failure` with a `SchemaIssue.Issue`. * * **Gotchas** * * Because this adapter runs synchronously, async decoding work can produce an * `Exit.Failure` with a defect cause. When the cause contains both schema * issues and non-schema reasons, all reasons remain in the returned `Cause`. * * @see {@link decodeUnknownExit} for untyped input with the same `Exit` result shape * @see {@link decodeEffect} for preserving decoding services and failures in `Effect` * * @stability stable * @category decoding * @since 4.0.0 */ export const decodeExit: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Exit.Exit = decodeUnknownExit /** @internal */ export function decodeUnknownOption>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Option.Option { return asOption(decodeUnknownEffect(schema, options)) } /** @internal */ export const decodeOption: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Option.Option = decodeUnknownOption /** * Creates a decoder for `unknown` input that reports failure safely as a * `Result`. * * **When to use** * * Use when decoding untyped boundary input and you want `SchemaIssue.Issue` * failures returned as data in a `Result`. * * **Details** * * The returned function produces `Result.succeed` with the decoded `Type` on * success or `Result.fail` with a `SchemaIssue.Issue` on decoding failure. * * **Gotchas** * * This adapter runs synchronously. Causes made entirely of schema issues become * `Result.fail`, but causes that contain defects, interruptions, or asynchronous * work at this synchronous boundary throw instead. * * @see {@link decodeResult} for input already typed as the schema's `Encoded` type * @see {@link decodeUnknownEffect} for effectful or service-requiring decoding * * @stability stable * @category decoding * @since 4.0.0 */ export function decodeUnknownResult>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Result.Result { return asResult(decodeUnknownEffect(schema, options)) } /** * Creates a decoder for input already typed as the schema's `Encoded` type, * reporting failure safely as a `Result`. * * **When to use** * * Use when you already have input typed as the schema's `Encoded` type and want * schema decoding failures represented as `Result.fail` with `SchemaIssue.Issue`. * * **Details** * * The returned function produces `Result.succeed` with the decoded `Type` on * success or `Result.fail` with a `SchemaIssue.Issue` on decoding failure. * * **Gotchas** * * This synchronous adapter returns `Result.fail` for causes made entirely of * schema issues, but causes that contain defects, interruptions, or other * non-schema reasons throw instead. * * @see {@link decodeUnknownResult} for untyped input with the same `Result` shape * @see {@link decodeEffect} for effectful or service-requiring decoding * * @stability stable * @category decoding * @since 4.0.0 */ export const decodeResult: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Result.Result = decodeUnknownResult /** * Creates a synchronous decoder for `unknown` input. * * **When to use** * * Use to decode untrusted or dynamically typed input at a synchronous boundary * where invalid data should throw an `Error` whose cause is `SchemaIssue.Issue`. * * **Details** * * The returned function returns the decoded `Type` on success and throws an * `Error` with the `SchemaIssue.Issue` in its `cause` on decoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or asynchronous work at this * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, * instead of being converted to a schema validation error. * * @see {@link decodeSync} for input already typed as the schema's `Encoded` type * @see {@link decodeUnknownEffect} for preserving decoding failures in `Effect` * @see {@link decodeUnknownResult} for returning schema issues as data * * @stability stable * @category decoding * @since 3.10.0 */ export function decodeUnknownSync>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => S["Type"] { return CompilerRegistry.compilerAdaptersEnabled ? makeSync(schema.ast, options) : asSync(decodeUnknownEffect(schema, options)) } /** * Creates a synchronous decoder for input already typed as the schema's `Encoded` * type. * * **When to use** * * Use to decode values already typed as the schema's `Encoded` input when * decoding failure should throw an `Error` whose cause is `SchemaIssue.Issue`. * * **Details** * * The returned function returns the decoded `Type` on success and throws an * `Error` with the `SchemaIssue.Issue` in its `cause` on decoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or asynchronous work at this * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, * instead of being converted to a schema validation error. * * @see {@link decodeUnknownSync} for untrusted or dynamically typed input * @see {@link decodeResult} for returning schema issues as data * @see {@link decodeEffect} for preserving decoding failures in `Effect` * * @stability stable * @category decoding * @since 3.10.0 */ export const decodeSync: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => S["Type"] = decodeUnknownSync /** * Creates an effectful encoder for `unknown` input. * * **When to use** * * Use when you need to encode untyped boundary input in an `Effect` whose * failure channel is `SchemaIssue.Issue`, while preserving service * requirements. * * **Details** * * The returned function succeeds with the schema's `Encoded` value or fails with a * `SchemaIssue.Issue`. Encoding service requirements are preserved in the returned * `Effect`. Parse options may be provided when creating the encoder and overridden * when applying it. * * @see {@link encodeEffect} for the typed-input variant when the value is already typed as the schema's decoded `Type` * * @stability stable * @category encoding * @since 4.0.0 */ export function encodeUnknownEffect( schema: S, options?: SchemaAST.ParseOptions ): ( input: unknown, options?: SchemaAST.ParseOptions ) => Effect.Effect { const parser = run(SchemaAST.flip(schema.ast)) return options === undefined ? parser : (input, overrideOptions) => parser(input, mergeParseOptions(options, overrideOptions)) } /** * Creates an effectful encoder for input already typed as the schema's decoded * `Type`. * * **When to use** * * Use when you need to encode values already typed as the schema's decoded * `Type` in an `Effect` whose failure channel is `SchemaIssue.Issue`, while * preserving service requirements. * * **Details** * * The returned function succeeds with the schema's `Encoded` value or fails with a * `SchemaIssue.Issue`, preserving any encoding service requirements in the * returned `Effect`. * * @see {@link encodeUnknownEffect} for encoding unknown input before the value is statically typed as the schema's `Type` * * @stability stable * @category encoding * @since 4.0.0 */ export const encodeEffect: ( schema: S, options?: SchemaAST.ParseOptions ) => ( input: S["Type"], options?: SchemaAST.ParseOptions ) => Effect.Effect = encodeUnknownEffect /** * Creates a Promise-based encoder for `unknown` input. * * **When to use** * * Use when you need to encode untrusted or dynamically typed values with a * service-free schema and return a JavaScript `Promise`. * * **Details** * * The returned function resolves with the schema's `Encoded` value on success and * rejects with an `Error` whose cause is a `SchemaIssue.Issue` on encoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or other non-schema reasons reject * with an `Error` whose cause is the underlying `Cause`. * * @see {@link encodePromise} for input already typed as the schema's decoded `Type` * @see {@link encodeUnknownEffect} for schemas that require encoding services or when failures should remain in `Effect` * * @stability stable * @category encoding * @since 3.10.0 */ export const encodeUnknownPromise = >( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Promise => asPromise(encodeUnknownEffect(schema, options)) /** * Creates a Promise-based encoder for input already typed as the schema's decoded * `Type`. * * **When to use** * * Use when you already have values typed as the schema's decoded `Type` and * need encoding to return a JavaScript `Promise`. * * **Details** * * The returned function resolves with the schema's `Encoded` value on success and * rejects with an `Error` whose cause is a `SchemaIssue.Issue` on encoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or other non-schema reasons reject * with an `Error` whose cause is the underlying `Cause`. * * @see {@link encodeUnknownPromise} for encoding untyped input * @see {@link encodeEffect} for effectful encoding or schemas with encoding service requirements * * @stability stable * @category encoding * @since 3.10.0 */ export const encodePromise: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Promise = encodeUnknownPromise /** * Creates a synchronous encoder for `unknown` input that reports failure safely * as an `Exit`. * * **When to use** * * Use when you need synchronous encoding of unknown input into an `Exit` whose * failure contains `SchemaIssue.Issue`. * * **Details** * * The returned function produces `Exit.Success` with the schema's `Encoded` value * or `Exit.Failure` with a `SchemaIssue.Issue`. * * **Gotchas** * * Because this adapter runs synchronously, async encoding work can produce an * `Exit.Failure` with a defect cause. When the cause contains both schema * issues and non-schema reasons, all reasons remain in the returned `Cause`. * * @see {@link encodeExit} for input already typed as the schema's decoded `Type` * @see {@link encodeUnknownEffect} for effectful encoding that preserves service requirements * * @stability stable * @category encoding * @since 4.0.0 */ export function encodeUnknownExit>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Exit.Exit { return asExit(encodeUnknownEffect(schema, options)) } /** * Creates a synchronous encoder for input already typed as the schema's decoded * `Type`, reporting failure safely as an `Exit`. * * **When to use** * * Use when you need synchronous encoding of already typed schema values into * an `Exit` whose failure contains `SchemaIssue.Issue`. * * **Details** * * The returned function produces `Exit.Success` with the schema's `Encoded` value * or `Exit.Failure` with a `SchemaIssue.Issue`. * * **Gotchas** * * Because this adapter runs synchronously, async encoding work can produce an * `Exit.Failure` with a defect cause. When the cause contains both schema * issues and non-schema reasons, all reasons remain in the returned `Cause`. * * @see {@link encodeUnknownExit} for unknown input with the same `Exit` result shape * @see {@link encodeEffect} for effectful encoding that preserves service requirements * * @stability stable * @category encoding * @since 4.0.0 */ export const encodeExit: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Exit.Exit = encodeUnknownExit /** @internal */ export function encodeUnknownOption>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Option.Option { return asOption(encodeUnknownEffect(schema, options)) } /** @internal */ export const encodeOption: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Option.Option = encodeUnknownOption /** * Creates an encoder for `unknown` input that reports failure safely as a * `Result`. * * **When to use** * * Use when encoding values from an unknown or dynamically typed boundary * synchronously, and you want `SchemaIssue.Issue` failures returned as `Result` * data. * * **Details** * * The returned function produces `Result.succeed` with the schema's `Encoded` * value on success or `Result.fail` with a `SchemaIssue.Issue` on encoding * failure. * * **Gotchas** * * This adapter runs synchronously. Causes made entirely of schema issues become * `Result.fail`, but causes that contain defects, interruptions, or asynchronous * work at this synchronous boundary throw instead. * * @see {@link encodeResult} for input already typed as the schema's decoded `Type` * @see {@link encodeUnknownEffect} for effectful encoding, including schemas with encoding service requirements * * @stability stable * @category encoding * @since 4.0.0 */ export function encodeUnknownResult>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => Result.Result { return asResult(encodeUnknownEffect(schema, options)) } /** * Creates an encoder for input already typed as the schema's decoded `Type`, * reporting failure safely as a `Result`. * * **When to use** * * Use when you already have input typed as the schema's decoded `Type` and want * encoding failures returned as `Result.fail` with `SchemaIssue.Issue`. * * **Details** * * The returned function produces `Result.succeed` with the schema's `Encoded` * value on success or `Result.fail` with a `SchemaIssue.Issue` on encoding * failure. * * **Gotchas** * * This synchronous adapter returns `Result.fail` for causes made entirely of * schema issues, but causes that contain defects, interruptions, or other * non-schema reasons throw instead. * * @see {@link encodeUnknownResult} for the same `Result` shape when the input is not already typed * * @stability stable * @category encoding * @since 4.0.0 */ export const encodeResult: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Result.Result = encodeUnknownResult /** * Creates a synchronous encoder for `unknown` input. * * **When to use** * * Use when you need to encode values from untyped input in synchronous code and * want encoding failures to throw an `Error` whose cause is `SchemaIssue.Issue`. * * **Details** * * The returned function returns the schema's `Encoded` value on success and throws * an `Error` with the `SchemaIssue.Issue` in its `cause` on encoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or asynchronous work at this * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, * instead of being converted to a schema validation error. * * @see {@link encodeSync} for input already typed as the schema's decoded `Type` * @see {@link encodeUnknownEffect} for effectful encoding that preserves service requirements * * @stability stable * @category encoding * @since 3.10.0 */ export function encodeUnknownSync>( schema: S, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => S["Encoded"] { return CompilerRegistry.compilerAdaptersEnabled ? makeSync(SchemaAST.flip(schema.ast), options) : asSync(encodeUnknownEffect(schema, options)) } /** * Creates a synchronous encoder for input already typed as the schema's decoded * `Type`. * * **When to use** * * Use to encode already typed schema values synchronously when encoding failure * should throw an `Error` whose cause is `SchemaIssue.Issue`. * * **Details** * * The returned function returns the schema's `Encoded` value on success and throws * an `Error` with the `SchemaIssue.Issue` in its `cause` on encoding failure. * Schema validation failures use the generic message `"Schema validation failed"`. * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when * human-readable details are needed. * * **Gotchas** * * Causes that contain defects, interruptions, or asynchronous work at this * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, * instead of being converted to a schema validation error. * * @see {@link encodeUnknownSync} for unknown input with the same throwing boundary * @see {@link encodeResult} for returning schema issues as data * @see {@link encodeEffect} for effectful encoding that preserves service requirements * * @stability stable * @category encoding * @since 3.10.0 */ export const encodeSync: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => S["Encoded"] = encodeUnknownSync const mergeParseOptions = ( options: SchemaAST.ParseOptions, overrideOptions: SchemaAST.ParseOptions | undefined ): SchemaAST.ParseOptions => overrideOptions ? { ...options, ...overrideOptions } : options const getValue = (value: unknown): Effect.Effect => { if (value === InternalParser.missing) { return Effect.fail(new SchemaIssue.InvalidValue()) } return Effect.succeed(value) } /** @internal */ export function run(ast: SchemaAST.AST) { return runWithCompiler(normalCompiler, ast) } function parserResult( result: Effect.Effect, input: unknown ): Effect.Effect { if (result === InternalParser.sameExit) { return Effect.succeed(input) as Effect.Effect } if (!effectIsExit(result)) { return Effect.flatMapEager(result, getValue) } return (result as InternalParser.Success)[InternalParser.args] === InternalParser.missing ? getValue(InternalParser.missing) : result as Effect.Effect } function runWithCompiler(compiler: Compiler, ast: SchemaAST.AST) { let parser: Parser return (input: unknown, options?: SchemaAST.ParseOptions): Effect.Effect => { const result = (parser ??= compiler(ast))( input, options ?? SchemaAST.defaultParseOptions ) if (result === InternalParser.sameExit) { return Effect.succeed(input) as Effect.Effect } if (!effectIsExit(result)) { return Effect.flatMapEager(result, getValue) } return (result as InternalParser.Success)[InternalParser.args] === InternalParser.missing ? getValue(InternalParser.missing) : result as Effect.Effect } } function asPromise( parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect ): (input: E, options?: SchemaAST.ParseOptions) => Promise { return (input: E, options?: SchemaAST.ParseOptions) => Effect.runPromiseExit(parser(input, options)).then((exit) => { if (Exit.isSuccess(exit)) { return exit.value } const issue = InternalSchemaCause.getSchemaIssueOrThrow( exit.cause, "Promise adapter can only reject schema issues" ) throw new Error("Schema validation failed", { cause: issue }) }) } function asExit( parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect ): (input: E, options?: SchemaAST.ParseOptions) => Exit.Exit { return (input: E, options?: SchemaAST.ParseOptions) => Effect.runSyncExit(parser(input, options) as any) } /** @internal */ export function asOption( parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect ): (input: E, options?: SchemaAST.ParseOptions) => Option.Option { const parserExit = asExit(parser) return (input: E, options?: SchemaAST.ParseOptions) => { const exit = parserExit(input, options) if (Exit.isSuccess(exit)) { return Option.some(exit.value) } InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Option adapter can only return none for schema issues") return Option.none() } } function asResult( parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect ): (input: E, options?: SchemaAST.ParseOptions) => Result.Result { const parserExit = asExit(parser) return (input: E, options?: SchemaAST.ParseOptions) => { const exit = parserExit(input, options) if (Exit.isSuccess(exit)) { return Result.succeed(exit.value) } return Result.fail( InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Result adapter can only return schema issues") ) } } function makeSync( ast: SchemaAST.AST, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => T { let run: ((input: unknown, options?: SchemaAST.ParseOptions) => T) | undefined return (input, overrideOptions) => (run ??= makeSyncEntry(CompilerRegistry.resolve(ast), options))(input, overrideOptions) } function makeSyncEntry( entry: CompilerRegistry.Entry, options?: SchemaAST.ParseOptions ): (input: unknown, options?: SchemaAST.ParseOptions) => T { const decode = entry.decode let detailed: ((input: unknown, options?: SchemaAST.ParseOptions) => T) | undefined const run = decode === undefined ? (input: unknown, parseOptions = SchemaAST.defaultParseOptions): T => (detailed ??= makeDetailedSync(entry))(input, parseOptions) : (input: unknown, parseOptions = SchemaAST.defaultParseOptions): T => { if (input === InternalParser.missing) { return (detailed ??= makeDetailedSync(entry))(input, parseOptions) } let output: unknown try { output = decode(input, parseOptions) } catch (error) { InternalSchemaCause.getSchemaIssueOrThrow(Cause.die(error), "Sync adapter can only throw schema issues") throw error } if (output !== CompilerRegistry.invalid) return output as T return (detailed ??= makeDetailedSync(entry))(input, parseOptions) } return options === undefined ? run : (input, overrideOptions) => run(input, mergeParseOptions(options, overrideOptions)) } function makeDetailedSync( entry: CompilerRegistry.Entry ): (input: unknown, options?: SchemaAST.ParseOptions) => T { return asSync(runWithCompiler(() => entry.decodeEffect, entry.ast)) } function asSync( parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect ): (input: E, options?: SchemaAST.ParseOptions) => T { const parserExit = asExit(parser) return (input: E, options?: SchemaAST.ParseOptions) => { const exit = parserExit(input, options) if (Exit.isSuccess(exit)) { return exit.value } const issue = InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Sync adapter can only throw schema issues") throw new Error("Schema validation failed", { cause: issue }) } } function runSync(effect: Effect.Effect, message: string): T { const exit = Effect.runSyncExit(effect) if (Exit.isSuccess(exit)) { return exit.value } const issue = InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, message) throw new Error("Schema validation failed", { cause: issue }) } function makeConstructorSync( ast: SchemaAST.AST ): (input: E, options?: Schema.MakeOptions) => T { let entry: CompilerRegistry.Entry | undefined let parser: Parser | undefined return (input, options) => { entry ??= CompilerRegistry.resolve(ast) const parseOptions = options?.disableChecks ? options.parseOptions ? { ...options.parseOptions, disableChecks: true } : { disableChecks: true } : options?.parseOptions ?? SchemaAST.defaultParseOptions const make = entry.make if (make !== undefined && input !== InternalParser.missing) { let output: unknown try { output = make(input, parseOptions) } catch (error) { InternalSchemaCause.getSchemaIssueOrThrow(Cause.die(error), "Constructor adapter can only throw schema issues") throw error } if (output !== CompilerRegistry.invalid && output !== InternalParser.missing) return output as T } const result = (parser ??= entry.makeEffect)(input, parseOptions) return runSync(parserResult(result, input), "Constructor adapter can only throw schema issues") } } /** @internal */ export interface Parser { ( input: unknown, options: SchemaAST.ParseOptions ): Effect.Effect } /** @internal */ export interface Compiler { (ast: SchemaAST.AST): Parser } const normalCompiler: Compiler = (ast) => CompilerRegistry.resolve(ast).parser const constructorCompiler: Compiler = (ast) => CompilerRegistry.resolve(ast).makeEffect