| 1 | import * as Cause from "../../Cause.ts" |
| 2 | import * as Effect from "../../Effect.ts" |
| 3 | import * as Exit from "../../Exit.ts" |
| 4 | import * as JsonSchema from "../../JsonSchema.ts" |
| 5 | import * as Scheduler from "../../Scheduler.ts" |
| 6 | import type * as Schema from "../../Schema.ts" |
| 7 | import * as SchemaAST from "../../SchemaAST.ts" |
| 8 | import * as SchemaIssue from "../../SchemaIssue.ts" |
| 9 | import * as SchemaParser from "../../SchemaParser.ts" |
| 10 | import type { StandardJSONSchemaV1, StandardSchemaV1 } from "../../StandardSchema.ts" |
| 11 | import * as InternalToCodec from "./toCodec.ts" |
| 12 | import * as InternalToJsonSchemaDocument from "./toJsonSchemaDocument.ts" |
| 13 | import * as InternalToRepresentation from "./toRepresentation.ts" |
| 14 | |
| 15 | function makeStandardResult<A>(exit: Exit.Exit<StandardSchemaV1.Result<A>>): StandardSchemaV1.Result<A> { |
| 16 | return Exit.isSuccess(exit) ? exit.value : { |
| 17 | issues: [{ message: Cause.pretty(exit.cause) }] |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | /** @internal */ |
| 22 | export function toStandardSchemaV1<S extends Schema.ConstraintDecoder<unknown>>( |
| 23 | self: S, |
| 24 | options?: { |
| 25 | readonly leafHook?: SchemaIssue.LeafHook | undefined |
| 26 | readonly checkHook?: SchemaIssue.CheckHook | undefined |
| 27 | readonly parseOptions?: SchemaAST.ParseOptions | undefined |
| 28 | } |
| 29 | ): StandardSchemaV1<S["Encoded"], S["Type"]> & S { |
| 30 | const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(self) as ( |
| 31 | input: unknown, |
| 32 | options?: SchemaAST.ParseOptions |
| 33 | ) => Effect.Effect<S["Type"], SchemaIssue.Issue> |
| 34 | const parseOptions: SchemaAST.ParseOptions = { errors: "all", ...options?.parseOptions } |
| 35 | const formatter = SchemaIssue.makeFormatterStandardSchemaV1(options) |
| 36 | const validate: StandardSchemaV1<S["Encoded"], S["Type"]>["~standard"]["validate"] = (value: unknown) => { |
| 37 | const scheduler = new Scheduler.MixedScheduler("sync") |
| 38 | const fiber = Effect.runFork( |
| 39 | Effect.match(decodeUnknownEffect(value, parseOptions), { |
| 40 | onFailure: formatter, |
| 41 | onSuccess: (value): StandardSchemaV1.Result<S["Type"]> => ({ value }) |
| 42 | }), |
| 43 | { scheduler } |
| 44 | ) |
| 45 | fiber.currentDispatcher?.flush() |
| 46 | const exit = fiber.pollUnsafe() |
| 47 | if (exit) { |
| 48 | return makeStandardResult(exit) |
| 49 | } |
| 50 | return new Promise((resolve) => { |
| 51 | fiber.addObserver((exit) => { |
| 52 | resolve(makeStandardResult(exit)) |
| 53 | }) |
| 54 | }) |
| 55 | } |
| 56 | if ("~standard" in self) { |
| 57 | const out = self as any |
| 58 | if ("validate" in out["~standard"]) return out |
| 59 | Object.assign(out["~standard"], { validate }) |
| 60 | return out |
| 61 | } |
| 62 | return Object.assign(self, { |
| 63 | "~standard": { |
| 64 | version: 1, |
| 65 | vendor: "effect", |
| 66 | validate |
| 67 | } as const |
| 68 | }) |
| 69 | } |
| 70 | |
| 71 | function toJsonSchemaDocument(ast: SchemaAST.AST): JsonSchema.Document<"draft-2020-12"> { |
| 72 | const document = InternalToRepresentation.toRepresentation( |
| 73 | InternalToCodec.toCodecJsonAST(ast) |
| 74 | ) |
| 75 | return InternalToJsonSchemaDocument.toJsonSchemaDocument(document) |
| 76 | } |
| 77 | |
| 78 | function toBaseStandardJSONSchemaV1( |
| 79 | ast: SchemaAST.AST, |
| 80 | target: StandardJSONSchemaV1.Target |
| 81 | ): JsonSchema.JsonSchema { |
| 82 | const doc2020_12 = toJsonSchemaDocument(ast) |
| 83 | if (target === "draft-2020-12") { |
| 84 | const schema = doc2020_12.schema |
| 85 | if (Object.keys(doc2020_12.definitions).length > 0) { |
| 86 | schema.$defs = doc2020_12.definitions |
| 87 | } |
| 88 | return schema |
| 89 | } else if (target === "draft-07") { |
| 90 | const doc07 = JsonSchema.toDocumentDraft07(doc2020_12) |
| 91 | const schema = doc07.schema |
| 92 | if (Object.keys(doc07.definitions).length > 0) { |
| 93 | schema.definitions = doc07.definitions |
| 94 | } |
| 95 | return schema |
| 96 | } |
| 97 | throw new globalThis.Error(`Unsupported target: ${target}`) |
| 98 | } |
| 99 | |
| 100 | /** @internal */ |
| 101 | export function toStandardJSONSchemaV1<S extends Schema.Constraint>( |
| 102 | self: S |
| 103 | ): StandardJSONSchemaV1<S["Encoded"], S["Type"]> & S { |
| 104 | const jsonSchema: StandardJSONSchemaV1.Props<S["Encoded"], S["Type"]>["jsonSchema"] = { |
| 105 | input(options) { |
| 106 | return toBaseStandardJSONSchemaV1(self.ast, options.target) |
| 107 | }, |
| 108 | output(options) { |
| 109 | return toBaseStandardJSONSchemaV1(SchemaAST.toType(self.ast), options.target) |
| 110 | } |
| 111 | } |
| 112 | if ("~standard" in self) { |
| 113 | const out = self as any |
| 114 | if ("jsonSchema" in out["~standard"]) return out |
| 115 | Object.assign(out["~standard"], { jsonSchema }) |
| 116 | return out |
| 117 | } |
| 118 | return Object.assign(self, { |
| 119 | "~standard": { |
| 120 | version: 1, |
| 121 | vendor: "effect", |
| 122 | jsonSchema |
| 123 | } as const |
| 124 | }) |
| 125 | } |