@effect/platform 0.66.0 → 0.66.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,714 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as AST from "@effect/schema/AST"
5
+ import type * as ParseResult from "@effect/schema/ParseResult"
6
+ import type * as Schema from "@effect/schema/Schema"
7
+ import * as Arr from "effect/Array"
8
+ import * as Option from "effect/Option"
9
+ import * as Predicate from "effect/Predicate"
10
+ import * as Record from "effect/Record"
11
+
12
+ /**
13
+ * @category model
14
+ * @since 1.0.0
15
+ */
16
+ export interface Annotations {
17
+ title?: string
18
+ description?: string
19
+ default?: unknown
20
+ examples?: globalThis.Array<unknown>
21
+ }
22
+
23
+ /**
24
+ * @category model
25
+ * @since 1.0.0
26
+ */
27
+ export interface Any extends Annotations {
28
+ $id: "/schemas/any"
29
+ }
30
+
31
+ /**
32
+ * @category model
33
+ * @since 1.0.0
34
+ */
35
+ export interface Unknown extends Annotations {
36
+ $id: "/schemas/unknown"
37
+ }
38
+
39
+ /**
40
+ * @category model
41
+ * @since 0.69.0
42
+ */
43
+ export interface Void extends Annotations {
44
+ $id: "/schemas/void"
45
+ }
46
+
47
+ /**
48
+ * @category model
49
+ * @since 0.71.0
50
+ */
51
+ export interface AnyObject extends Annotations {
52
+ $id: "/schemas/object"
53
+ anyOf: [
54
+ { type: "object" },
55
+ { type: "array" }
56
+ ]
57
+ }
58
+
59
+ /**
60
+ * @category model
61
+ * @since 0.71.0
62
+ */
63
+ export interface Empty extends Annotations {
64
+ $id: "/schemas/{}"
65
+ anyOf: [
66
+ { type: "object" },
67
+ { type: "array" }
68
+ ]
69
+ }
70
+
71
+ /**
72
+ * @category model
73
+ * @since 1.0.0
74
+ */
75
+ export interface Ref extends Annotations {
76
+ $ref: string
77
+ }
78
+
79
+ /**
80
+ * @category model
81
+ * @since 1.0.0
82
+ */
83
+ export interface String extends Annotations {
84
+ type: "string"
85
+ minLength?: number
86
+ maxLength?: number
87
+ pattern?: string
88
+ contentEncoding?: string
89
+ contentMediaType?: string
90
+ contentSchema?: JsonSchema
91
+ }
92
+
93
+ /**
94
+ * @category model
95
+ * @since 1.0.0
96
+ */
97
+ export interface Numeric extends Annotations {
98
+ minimum?: number
99
+ exclusiveMinimum?: number
100
+ maximum?: number
101
+ exclusiveMaximum?: number
102
+ }
103
+
104
+ /**
105
+ * @category model
106
+ * @since 1.0.0
107
+ */
108
+ export interface Number extends Numeric {
109
+ type: "number"
110
+ }
111
+
112
+ /**
113
+ * @category model
114
+ * @since 1.0.0
115
+ */
116
+ export interface Integer extends Numeric {
117
+ type: "integer"
118
+ }
119
+
120
+ /**
121
+ * @category model
122
+ * @since 1.0.0
123
+ */
124
+ export interface Boolean extends Annotations {
125
+ type: "boolean"
126
+ }
127
+
128
+ /**
129
+ * @category model
130
+ * @since 1.0.0
131
+ */
132
+ export interface Array extends Annotations {
133
+ type: "array"
134
+ items?: JsonSchema | globalThis.Array<JsonSchema>
135
+ minItems?: number
136
+ maxItems?: number
137
+ additionalItems?: JsonSchema | boolean
138
+ }
139
+
140
+ /**
141
+ * @category model
142
+ * @since 1.0.0
143
+ */
144
+ export interface Enum extends Annotations {
145
+ enum: globalThis.Array<AST.LiteralValue>
146
+ }
147
+
148
+ /**
149
+ * @category model
150
+ * @since 0.71.0
151
+ */
152
+ export interface Enums extends Annotations {
153
+ $comment: "/schemas/enums"
154
+ anyOf: globalThis.Array<{
155
+ title: string
156
+ enum: [string | number]
157
+ }>
158
+ }
159
+
160
+ /**
161
+ * @category model
162
+ * @since 1.0.0
163
+ */
164
+ export interface AnyOf extends Annotations {
165
+ anyOf: globalThis.Array<JsonSchema>
166
+ }
167
+
168
+ /**
169
+ * @category model
170
+ * @since 1.0.0
171
+ */
172
+ export interface Object extends Annotations {
173
+ type: "object"
174
+ required: globalThis.Array<string>
175
+ properties: Record<string, JsonSchema>
176
+ additionalProperties?: boolean | JsonSchema
177
+ patternProperties?: Record<string, JsonSchema>
178
+ propertyNames?: JsonSchema
179
+ }
180
+
181
+ /**
182
+ * @category model
183
+ * @since 0.71.0
184
+ */
185
+ export type JsonSchema =
186
+ | Any
187
+ | Unknown
188
+ | Void
189
+ | AnyObject
190
+ | Empty
191
+ | Ref
192
+ | String
193
+ | Number
194
+ | Integer
195
+ | Boolean
196
+ | Array
197
+ | Enum
198
+ | Enums
199
+ | AnyOf
200
+ | Object
201
+
202
+ /**
203
+ * @category model
204
+ * @since 1.0.0
205
+ */
206
+ export type Root = JsonSchema & {
207
+ $defs?: Record<string, JsonSchema>
208
+ }
209
+
210
+ /**
211
+ * @category encoding
212
+ * @since 1.0.0
213
+ */
214
+ export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): Root => {
215
+ const $defs: Record<string, any> = {}
216
+ const out = go(schema.ast, $defs, true, []) as Root
217
+ // clean up self-referencing entries
218
+ for (const id in $defs) {
219
+ if ($defs[id]["$ref"] === get$ref(id)) {
220
+ delete $defs[id]
221
+ }
222
+ }
223
+ if (!Record.isEmptyRecord($defs)) {
224
+ out.$defs = $defs
225
+ }
226
+ return out
227
+ }
228
+
229
+ const constAny: JsonSchema = { $id: "/schemas/any" }
230
+
231
+ const constUnknown: JsonSchema = { $id: "/schemas/unknown" }
232
+
233
+ const constVoid: JsonSchema = { $id: "/schemas/void" }
234
+
235
+ const constAnyObject: JsonSchema = {
236
+ "$id": "/schemas/object",
237
+ "anyOf": [
238
+ { "type": "object" },
239
+ { "type": "array" }
240
+ ]
241
+ }
242
+
243
+ const constEmpty: JsonSchema = {
244
+ "$id": "/schemas/{}",
245
+ "anyOf": [
246
+ { "type": "object" },
247
+ { "type": "array" }
248
+ ]
249
+ }
250
+
251
+ const getJsonSchemaAnnotations = (annotated: AST.Annotated): Annotations =>
252
+ Record.getSomes({
253
+ description: AST.getDescriptionAnnotation(annotated),
254
+ title: AST.getTitleAnnotation(annotated),
255
+ examples: AST.getExamplesAnnotation(annotated),
256
+ default: AST.getDefaultAnnotation(annotated)
257
+ })
258
+
259
+ const removeDefaultJsonSchemaAnnotations = (
260
+ jsonSchemaAnnotations: Annotations,
261
+ ast: AST.AST
262
+ ): Annotations => {
263
+ if (jsonSchemaAnnotations["title"] === ast.annotations[AST.TitleAnnotationId]) {
264
+ delete jsonSchemaAnnotations["title"]
265
+ }
266
+ if (jsonSchemaAnnotations["description"] === ast.annotations[AST.DescriptionAnnotationId]) {
267
+ delete jsonSchemaAnnotations["description"]
268
+ }
269
+ return jsonSchemaAnnotations
270
+ }
271
+
272
+ const getASTJsonSchemaAnnotations = (ast: AST.AST): Annotations => {
273
+ const jsonSchemaAnnotations = getJsonSchemaAnnotations(ast)
274
+ switch (ast._tag) {
275
+ case "StringKeyword":
276
+ return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.stringKeyword)
277
+ case "NumberKeyword":
278
+ return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.numberKeyword)
279
+ case "BooleanKeyword":
280
+ return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.booleanKeyword)
281
+ default:
282
+ return jsonSchemaAnnotations
283
+ }
284
+ }
285
+
286
+ const pruneUndefinedKeyword = (ps: AST.PropertySignature): AST.AST | undefined => {
287
+ const type = ps.type
288
+ if (AST.isUnion(type) && Option.isNone(AST.getJSONSchemaAnnotation(type))) {
289
+ const types = type.types.filter((type) => !AST.isUndefinedKeyword(type))
290
+ if (types.length < type.types.length) {
291
+ return AST.Union.make(types, type.annotations)
292
+ }
293
+ }
294
+ }
295
+
296
+ const DEFINITION_PREFIX = "#/$defs/"
297
+
298
+ const get$ref = (id: string): string => `${DEFINITION_PREFIX}${id}`
299
+
300
+ const getRefinementInnerTransformation = (ast: AST.Refinement): AST.AST | undefined => {
301
+ switch (ast.from._tag) {
302
+ case "Transformation":
303
+ return ast.from
304
+ case "Refinement":
305
+ return getRefinementInnerTransformation(ast.from)
306
+ case "Suspend": {
307
+ const from = ast.from.f()
308
+ if (AST.isRefinement(from)) {
309
+ return getRefinementInnerTransformation(from)
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ const isParseJsonTransformation = (ast: AST.AST): boolean => ast.annotations[AST.TypeAnnotationId] === ParseJsonTypeId
316
+
317
+ const isOverrideAnnotation = (jsonSchema: JsonSchema): boolean => {
318
+ return ("type" in jsonSchema) || ("oneOf" in jsonSchema) || ("anyOf" in jsonSchema) || ("const" in jsonSchema) ||
319
+ ("enum" in jsonSchema) || ("$ref" in jsonSchema)
320
+ }
321
+
322
+ const go = (
323
+ ast: AST.AST,
324
+ $defs: Record<string, JsonSchema>,
325
+ handleIdentifier: boolean,
326
+ path: ReadonlyArray<PropertyKey>
327
+ ): JsonSchema => {
328
+ const hook = AST.getJSONSchemaAnnotation(ast)
329
+ if (Option.isSome(hook)) {
330
+ const handler = hook.value as JsonSchema
331
+ if (AST.isRefinement(ast)) {
332
+ const t = getRefinementInnerTransformation(ast)
333
+ if (t === undefined) {
334
+ try {
335
+ return {
336
+ ...go(ast.from, $defs, true, path),
337
+ ...getJsonSchemaAnnotations(ast),
338
+ ...handler
339
+ }
340
+ } catch (e) {
341
+ return {
342
+ ...getJsonSchemaAnnotations(ast),
343
+ ...handler
344
+ }
345
+ }
346
+ } else if (!isOverrideAnnotation(handler)) {
347
+ return {
348
+ ...go(t, $defs, true, path),
349
+ ...getJsonSchemaAnnotations(ast)
350
+ }
351
+ }
352
+ }
353
+ return handler
354
+ }
355
+ const surrogate = getSurrogateAnnotation(ast)
356
+ if (Option.isSome(surrogate)) {
357
+ return {
358
+ ...(ast._tag === "Transformation" ? getJsonSchemaAnnotations(ast.to) : {}),
359
+ ...go(surrogate.value, $defs, handleIdentifier, path),
360
+ ...getJsonSchemaAnnotations(ast)
361
+ }
362
+ }
363
+ if (handleIdentifier && !AST.isTransformation(ast) && !AST.isRefinement(ast)) {
364
+ const identifier = getJSONIdentifier(ast)
365
+ if (Option.isSome(identifier)) {
366
+ const id = identifier.value
367
+ const out = { $ref: get$ref(id) }
368
+ if (!Record.has($defs, id)) {
369
+ $defs[id] = out
370
+ $defs[id] = go(ast, $defs, false, path)
371
+ }
372
+ return out
373
+ }
374
+ }
375
+ switch (ast._tag) {
376
+ case "Declaration":
377
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
378
+ case "Literal": {
379
+ const literal = ast.literal
380
+ if (literal === null) {
381
+ return {
382
+ enum: [null],
383
+ ...getJsonSchemaAnnotations(ast)
384
+ }
385
+ } else if (Predicate.isString(literal) || Predicate.isNumber(literal) || Predicate.isBoolean(literal)) {
386
+ return {
387
+ enum: [literal],
388
+ ...getJsonSchemaAnnotations(ast)
389
+ }
390
+ }
391
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
392
+ }
393
+ case "UniqueSymbol":
394
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
395
+ case "UndefinedKeyword":
396
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
397
+ case "VoidKeyword":
398
+ return {
399
+ ...constVoid,
400
+ ...getJsonSchemaAnnotations(ast)
401
+ }
402
+ case "NeverKeyword":
403
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
404
+ case "UnknownKeyword":
405
+ return {
406
+ ...constUnknown,
407
+ ...getJsonSchemaAnnotations(ast)
408
+ }
409
+
410
+ case "AnyKeyword":
411
+ return {
412
+ ...constAny,
413
+ ...getJsonSchemaAnnotations(ast)
414
+ }
415
+ case "ObjectKeyword":
416
+ return {
417
+ ...constAnyObject,
418
+ ...getJsonSchemaAnnotations(ast)
419
+ }
420
+ case "StringKeyword":
421
+ return { type: "string", ...getASTJsonSchemaAnnotations(ast) }
422
+ case "NumberKeyword":
423
+ return { type: "number", ...getASTJsonSchemaAnnotations(ast) }
424
+ case "BooleanKeyword":
425
+ return { type: "boolean", ...getASTJsonSchemaAnnotations(ast) }
426
+ case "BigIntKeyword":
427
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
428
+ case "SymbolKeyword":
429
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
430
+ case "TupleType": {
431
+ const elements = ast.elements.map((e, i) => ({
432
+ ...go(e.type, $defs, true, path.concat(i)),
433
+ ...getJsonSchemaAnnotations(e)
434
+ }))
435
+ const rest = ast.rest.map((annotatedAST) => ({
436
+ ...go(annotatedAST.type, $defs, true, path),
437
+ ...getJsonSchemaAnnotations(annotatedAST)
438
+ }))
439
+ const output: Array = { type: "array" }
440
+ // ---------------------------------------------
441
+ // handle elements
442
+ // ---------------------------------------------
443
+ const len = ast.elements.length
444
+ if (len > 0) {
445
+ output.minItems = len - ast.elements.filter((element) => element.isOptional).length
446
+ output.items = elements
447
+ }
448
+ // ---------------------------------------------
449
+ // handle rest element
450
+ // ---------------------------------------------
451
+ const restLength = rest.length
452
+ if (restLength > 0) {
453
+ const head = rest[0]
454
+ const isHomogeneous = restLength === 1 && ast.elements.every((e) => e.type === ast.rest[0].type)
455
+ if (isHomogeneous) {
456
+ output.items = head
457
+ } else {
458
+ output.additionalItems = head
459
+ }
460
+
461
+ // ---------------------------------------------
462
+ // handle post rest elements
463
+ // ---------------------------------------------
464
+ if (restLength > 1) {
465
+ throw new Error(getJSONSchemaUnsupportedPostRestElementsErrorMessage(path))
466
+ }
467
+ } else {
468
+ if (len > 0) {
469
+ output.additionalItems = false
470
+ } else {
471
+ output.maxItems = 0
472
+ }
473
+ }
474
+
475
+ return {
476
+ ...output,
477
+ ...getJsonSchemaAnnotations(ast)
478
+ }
479
+ }
480
+ case "TypeLiteral": {
481
+ if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
482
+ return {
483
+ ...constEmpty,
484
+ ...getJsonSchemaAnnotations(ast)
485
+ }
486
+ }
487
+ let patternProperties: JsonSchema | undefined = undefined
488
+ let propertyNames: JsonSchema | undefined = undefined
489
+ for (const is of ast.indexSignatures) {
490
+ const parameter = is.parameter
491
+ switch (parameter._tag) {
492
+ case "StringKeyword": {
493
+ patternProperties = go(is.type, $defs, true, path)
494
+ break
495
+ }
496
+ case "TemplateLiteral": {
497
+ patternProperties = go(is.type, $defs, true, path)
498
+ propertyNames = {
499
+ type: "string",
500
+ pattern: AST.getTemplateLiteralRegExp(parameter).source
501
+ }
502
+ break
503
+ }
504
+ case "Refinement": {
505
+ patternProperties = go(is.type, $defs, true, path)
506
+ propertyNames = go(parameter, $defs, true, path)
507
+ break
508
+ }
509
+ case "SymbolKeyword":
510
+ throw new Error(getJSONSchemaUnsupportedParameterErrorMessage(path, parameter))
511
+ }
512
+ }
513
+ const output: Object = {
514
+ type: "object",
515
+ required: [],
516
+ properties: {},
517
+ additionalProperties: false
518
+ }
519
+ // ---------------------------------------------
520
+ // handle property signatures
521
+ // ---------------------------------------------
522
+ for (let i = 0; i < ast.propertySignatures.length; i++) {
523
+ const ps = ast.propertySignatures[i]
524
+ const name = ps.name
525
+ if (Predicate.isString(name)) {
526
+ const pruned = pruneUndefinedKeyword(ps)
527
+ output.properties[name] = {
528
+ ...go(pruned ? pruned : ps.type, $defs, true, path.concat(ps.name)),
529
+ ...getJsonSchemaAnnotations(ps)
530
+ }
531
+ // ---------------------------------------------
532
+ // handle optional property signatures
533
+ // ---------------------------------------------
534
+ if (!ps.isOptional && pruned === undefined) {
535
+ output.required.push(name)
536
+ }
537
+ } else {
538
+ throw new Error(getJSONSchemaUnsupportedKeyErrorMessage(name, path))
539
+ }
540
+ }
541
+ // ---------------------------------------------
542
+ // handle index signatures
543
+ // ---------------------------------------------
544
+ if (patternProperties !== undefined) {
545
+ delete output.additionalProperties
546
+ output.patternProperties = { "": patternProperties }
547
+ }
548
+ if (propertyNames !== undefined) {
549
+ output.propertyNames = propertyNames
550
+ }
551
+
552
+ return {
553
+ ...output,
554
+ ...getJsonSchemaAnnotations(ast)
555
+ }
556
+ }
557
+ case "Union": {
558
+ const enums: globalThis.Array<AST.LiteralValue> = []
559
+ const anyOf: globalThis.Array<JsonSchema> = []
560
+ for (const type of ast.types) {
561
+ const schema = go(type, $defs, true, path)
562
+ if ("enum" in schema) {
563
+ if (Object.keys(schema).length > 1) {
564
+ anyOf.push(schema)
565
+ } else {
566
+ for (const e of schema.enum) {
567
+ enums.push(e)
568
+ }
569
+ }
570
+ } else {
571
+ anyOf.push(schema)
572
+ }
573
+ }
574
+ if (anyOf.length === 0) {
575
+ return { enum: enums, ...getJsonSchemaAnnotations(ast) }
576
+ } else {
577
+ if (enums.length >= 1) {
578
+ anyOf.push({ enum: enums })
579
+ }
580
+ return { anyOf, ...getJsonSchemaAnnotations(ast) }
581
+ }
582
+ }
583
+ case "Enums": {
584
+ return {
585
+ $comment: "/schemas/enums",
586
+ anyOf: ast.enums.map((e) => ({ title: e[0], enum: [e[1]] })),
587
+ ...getJsonSchemaAnnotations(ast)
588
+ }
589
+ }
590
+ case "Refinement": {
591
+ if (AST.encodedBoundAST(ast) === ast) {
592
+ throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast))
593
+ }
594
+ return go(ast.from, $defs, true, path)
595
+ }
596
+ case "TemplateLiteral": {
597
+ const regex = AST.getTemplateLiteralRegExp(ast)
598
+ return {
599
+ type: "string",
600
+ description: "a template literal",
601
+ pattern: regex.source,
602
+ ...getJsonSchemaAnnotations(ast)
603
+ }
604
+ }
605
+ case "Suspend": {
606
+ const identifier = Option.orElse(getJSONIdentifier(ast), () => getJSONIdentifier(ast.f()))
607
+ if (Option.isNone(identifier)) {
608
+ throw new Error(getJSONSchemaMissingIdentifierAnnotationErrorMessage(path, ast))
609
+ }
610
+ return {
611
+ ...go(ast.f(), $defs, true, path),
612
+ ...getJsonSchemaAnnotations(ast)
613
+ }
614
+ }
615
+ case "Transformation": {
616
+ // Properly handle S.parseJson transformations by focusing on
617
+ // the 'to' side of the AST. This approach prevents the generation of useless schemas
618
+ // derived from the 'from' side (type: string), ensuring the output matches the intended
619
+ // complex schema type.
620
+ if (isParseJsonTransformation(ast.from)) {
621
+ return {
622
+ type: "string",
623
+ contentMediaType: "application/json",
624
+ contentSchema: go(ast.to, $defs, true, path),
625
+ ...getJsonSchemaAnnotations(ast)
626
+ }
627
+ }
628
+ return {
629
+ ...getASTJsonSchemaAnnotations(ast.to),
630
+ ...go(ast.from, $defs, true, path),
631
+ ...getJsonSchemaAnnotations(ast)
632
+ }
633
+ }
634
+ }
635
+ }
636
+
637
+ const getJSONSchemaMissingAnnotationErrorMessage = (
638
+ path: ReadonlyArray<PropertyKey>,
639
+ ast: AST.AST
640
+ ) =>
641
+ getMissingAnnotationErrorMessage(
642
+ `Generating a JSON Schema for this schema requires a "jsonSchema" annotation`,
643
+ path,
644
+ ast
645
+ )
646
+
647
+ const getJSONSchemaMissingIdentifierAnnotationErrorMessage = (
648
+ path: ReadonlyArray<PropertyKey>,
649
+ ast: AST.AST
650
+ ) =>
651
+ getMissingAnnotationErrorMessage(
652
+ `Generating a JSON Schema for this schema requires an "identifier" annotation`,
653
+ path,
654
+ ast
655
+ )
656
+
657
+ const getJSONSchemaUnsupportedParameterErrorMessage = (
658
+ path: ReadonlyArray<PropertyKey>,
659
+ parameter: AST.AST
660
+ ): string => getErrorMessage("Unsupported index signature parameter", undefined, path, parameter)
661
+
662
+ const getJSONSchemaUnsupportedPostRestElementsErrorMessage = (path: ReadonlyArray<PropertyKey>): string =>
663
+ getErrorMessage(
664
+ "Generating a JSON Schema for post-rest elements is not currently supported. You're welcome to contribute by submitting a Pull Request",
665
+ undefined,
666
+ path
667
+ )
668
+
669
+ const getJSONSchemaUnsupportedKeyErrorMessage = (key: PropertyKey, path: ReadonlyArray<PropertyKey>): string =>
670
+ getErrorMessage("Unsupported key", `Cannot encode ${formatPropertyKey(key)} key to JSON Schema`, path)
671
+
672
+ const getMissingAnnotationErrorMessage = (details?: string, path?: ReadonlyArray<PropertyKey>, ast?: AST.AST): string =>
673
+ getErrorMessage("Missing annotation", details, path, ast)
674
+
675
+ const getErrorMessage = (
676
+ reason: string,
677
+ details?: string,
678
+ path?: ReadonlyArray<PropertyKey>,
679
+ ast?: AST.AST
680
+ ): string => {
681
+ let out = reason
682
+
683
+ if (path && Arr.isNonEmptyReadonlyArray(path)) {
684
+ out += `\nat path: ${formatPath(path)}`
685
+ }
686
+
687
+ if (details !== undefined) {
688
+ out += `\ndetails: ${details}`
689
+ }
690
+
691
+ if (ast) {
692
+ out += `\nschema (${ast._tag}): ${ast}`
693
+ }
694
+
695
+ return out
696
+ }
697
+
698
+ const formatPathKey = (key: PropertyKey): string => `[${formatPropertyKey(key)}]`
699
+
700
+ const formatPath = (path: ParseResult.Path): string =>
701
+ isNonEmpty(path) ? path.map(formatPathKey).join("") : formatPathKey(path)
702
+
703
+ const isNonEmpty = <A>(x: ParseResult.SingleOrNonEmpty<A>): x is Arr.NonEmptyReadonlyArray<A> => Array.isArray(x)
704
+
705
+ const formatPropertyKey = (name: PropertyKey): string => typeof name === "string" ? JSON.stringify(name) : String(name)
706
+
707
+ const ParseJsonTypeId: unique symbol = Symbol.for("@effect/schema/TypeId/ParseJson")
708
+ const SurrogateAnnotationId = Symbol.for("@effect/schema/annotation/Surrogate")
709
+ const JSONIdentifierAnnotationId = Symbol.for("@effect/schema/annotation/JSONIdentifier")
710
+
711
+ const getSurrogateAnnotation = AST.getAnnotation<AST.AST>(SurrogateAnnotationId)
712
+ const getJSONIdentifierAnnotation = AST.getAnnotation<string>(JSONIdentifierAnnotationId)
713
+ const getJSONIdentifier = (annotated: AST.Annotated) =>
714
+ Option.orElse(getJSONIdentifierAnnotation(annotated), () => AST.getIdentifierAnnotation(annotated))
package/src/index.ts CHANGED
@@ -194,6 +194,11 @@ export * as Multipart from "./Multipart.js"
194
194
  */
195
195
  export * as OpenApi from "./OpenApi.js"
196
196
 
197
+ /**
198
+ * @since 1.0.0
199
+ */
200
+ export * as OpenApiJsonSchema from "./OpenApiJsonSchema.js"
201
+
197
202
  /**
198
203
  * @since 1.0.0
199
204
  */