1import * as Cause from "../../Cause.ts"
2import * as Effect from "../../Effect.ts"
3import * as Exit from "../../Exit.ts"
4import * as JsonSchema from "../../JsonSchema.ts"
5import * as Scheduler from "../../Scheduler.ts"
6import type * as Schema from "../../Schema.ts"
7import * as SchemaAST from "../../SchemaAST.ts"
8import * as SchemaIssue from "../../SchemaIssue.ts"
9import * as SchemaParser from "../../SchemaParser.ts"
10import type { StandardJSONSchemaV1, StandardSchemaV1 } from "../../StandardSchema.ts"
11import * as InternalToCodec from "./toCodec.ts"
12import * as InternalToJsonSchemaDocument from "./toJsonSchemaDocument.ts"
13import * as InternalToRepresentation from "./toRepresentation.ts"
14
15function 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 */
22export 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
71function 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
78function 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 */
101export 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}