effect 3.11.4 → 3.11.5
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.
- package/dist/cjs/Effect.js.map +1 -1
- package/dist/cjs/JSONSchema.js +226 -134
- package/dist/cjs/JSONSchema.js.map +1 -1
- package/dist/cjs/Schema.js +29 -10
- package/dist/cjs/Schema.js.map +1 -1
- package/dist/cjs/SchemaAST.js +34 -9
- package/dist/cjs/SchemaAST.js.map +1 -1
- package/dist/cjs/internal/core.js +6 -7
- package/dist/cjs/internal/core.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Effect.d.ts +50 -19
- package/dist/dts/Effect.d.ts.map +1 -1
- package/dist/dts/JSONSchema.d.ts +57 -1
- package/dist/dts/JSONSchema.d.ts.map +1 -1
- package/dist/dts/Schema.d.ts +1 -1
- package/dist/dts/Schema.d.ts.map +1 -1
- package/dist/dts/SchemaAST.d.ts +1 -1
- package/dist/dts/SchemaAST.d.ts.map +1 -1
- package/dist/dts/internal/core.d.ts +5 -0
- package/dist/dts/internal/core.d.ts.map +1 -1
- package/dist/esm/Effect.js.map +1 -1
- package/dist/esm/JSONSchema.js +224 -133
- package/dist/esm/JSONSchema.js.map +1 -1
- package/dist/esm/Schema.js +29 -10
- package/dist/esm/Schema.js.map +1 -1
- package/dist/esm/SchemaAST.js +31 -7
- package/dist/esm/SchemaAST.js.map +1 -1
- package/dist/esm/internal/core.js +7 -6
- package/dist/esm/internal/core.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +1 -1
- package/src/Effect.ts +68 -20
- package/src/JSONSchema.ts +233 -134
- package/src/Schema.ts +25 -10
- package/src/SchemaAST.ts +31 -7
- package/src/internal/core.ts +9 -6
- package/src/internal/version.ts +1 -1
package/src/JSONSchema.ts
CHANGED
|
@@ -20,6 +20,15 @@ export interface JsonSchemaAnnotations {
|
|
|
20
20
|
examples?: Array<unknown>
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @category model
|
|
25
|
+
* @since 3.11.5
|
|
26
|
+
*/
|
|
27
|
+
export interface JsonSchema7Never extends JsonSchemaAnnotations {
|
|
28
|
+
$id: "/schemas/never"
|
|
29
|
+
not: {}
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
/**
|
|
24
33
|
* @category model
|
|
25
34
|
* @since 3.10.0
|
|
@@ -86,6 +95,12 @@ export interface JsonSchema7String extends JsonSchemaAnnotations {
|
|
|
86
95
|
maxLength?: number
|
|
87
96
|
pattern?: string
|
|
88
97
|
format?: string
|
|
98
|
+
contentMediaType?: string
|
|
99
|
+
allOf?: Array<{
|
|
100
|
+
minLength?: number
|
|
101
|
+
maxLength?: number
|
|
102
|
+
pattern?: string
|
|
103
|
+
}>
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
/**
|
|
@@ -97,6 +112,14 @@ export interface JsonSchema7Numeric extends JsonSchemaAnnotations {
|
|
|
97
112
|
exclusiveMinimum?: number
|
|
98
113
|
maximum?: number
|
|
99
114
|
exclusiveMaximum?: number
|
|
115
|
+
multipleOf?: number
|
|
116
|
+
allOf?: Array<{
|
|
117
|
+
minimum?: number
|
|
118
|
+
exclusiveMinimum?: number
|
|
119
|
+
maximum?: number
|
|
120
|
+
exclusiveMaximum?: number
|
|
121
|
+
multipleOf?: number
|
|
122
|
+
}>
|
|
100
123
|
}
|
|
101
124
|
|
|
102
125
|
/**
|
|
@@ -181,6 +204,7 @@ export interface JsonSchema7Object extends JsonSchemaAnnotations {
|
|
|
181
204
|
* @since 3.10.0
|
|
182
205
|
*/
|
|
183
206
|
export type JsonSchema7 =
|
|
207
|
+
| JsonSchema7Never
|
|
184
208
|
| JsonSchema7Any
|
|
185
209
|
| JsonSchema7Unknown
|
|
186
210
|
| JsonSchema7Void
|
|
@@ -211,31 +235,84 @@ export type JsonSchema7Root = JsonSchema7 & {
|
|
|
211
235
|
* @since 3.10.0
|
|
212
236
|
*/
|
|
213
237
|
export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): JsonSchema7Root => {
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
if (!Record.isEmptyRecord($defs)) {
|
|
227
|
-
out.$defs = $defs
|
|
238
|
+
const definitions: Record<string, any> = {}
|
|
239
|
+
const ast = AST.isTransformation(schema.ast) && isParseJsonTransformation(schema.ast.from)
|
|
240
|
+
// Special case top level `parseJson` transformations
|
|
241
|
+
? schema.ast.to
|
|
242
|
+
: schema.ast
|
|
243
|
+
const out: JsonSchema7Root = fromAST(ast, {
|
|
244
|
+
definitions
|
|
245
|
+
})
|
|
246
|
+
out.$schema = $schema
|
|
247
|
+
if (!Record.isEmptyRecord(definitions)) {
|
|
248
|
+
out.$defs = definitions
|
|
228
249
|
}
|
|
229
250
|
return out
|
|
230
251
|
}
|
|
231
252
|
|
|
232
|
-
|
|
253
|
+
type Target = "jsonSchema7" | "jsonSchema2019-09" | "openApi3.1"
|
|
254
|
+
|
|
255
|
+
type TopLevelReferenceStrategy = "skip" | "keep"
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Returns a JSON Schema with additional options and definitions.
|
|
259
|
+
*
|
|
260
|
+
* **Warning**
|
|
261
|
+
*
|
|
262
|
+
* This function is experimental and subject to change.
|
|
263
|
+
*
|
|
264
|
+
* **Details**
|
|
265
|
+
*
|
|
266
|
+
* - `definitions`: A record of definitions that are included in the schema.
|
|
267
|
+
* - `definitionPath`: The path to the definitions within the schema (defaults
|
|
268
|
+
* to "#/$defs/").
|
|
269
|
+
* - `target`: Which spec to target. Possible values are:
|
|
270
|
+
* - `'jsonSchema7'`: JSON Schema draft-07 (default behavior).
|
|
271
|
+
* - `'jsonSchema2019-09'`: JSON Schema draft-2019-09.
|
|
272
|
+
* - `'openApi3.1'`: OpenAPI 3.1.
|
|
273
|
+
* - `topLevelReferenceStrategy`: Controls the handling of the top-level
|
|
274
|
+
* reference. Possible values are:
|
|
275
|
+
* - `"keep"`: Keep the top-level reference (default behavior).
|
|
276
|
+
* - `"skip"`: Skip the top-level reference.
|
|
277
|
+
*
|
|
278
|
+
* @category encoding
|
|
279
|
+
* @since 3.11.5
|
|
280
|
+
* @experimental
|
|
281
|
+
*/
|
|
282
|
+
export const fromAST = (ast: AST.AST, options: {
|
|
283
|
+
readonly definitions: Record<string, JsonSchema7>
|
|
284
|
+
readonly definitionPath?: string
|
|
285
|
+
readonly target?: Target
|
|
286
|
+
readonly topLevelReferenceStrategy?: TopLevelReferenceStrategy
|
|
287
|
+
}): JsonSchema7 => {
|
|
288
|
+
const definitionPath = options.definitionPath ?? "#/$defs/"
|
|
289
|
+
const getRef = (id: string) => definitionPath + id
|
|
290
|
+
const target: Target = options.target ?? "jsonSchema7"
|
|
291
|
+
const handleIdentifier = options.topLevelReferenceStrategy !== "skip"
|
|
292
|
+
return go(ast, options.definitions, handleIdentifier, [], {
|
|
293
|
+
getRef,
|
|
294
|
+
target
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const constNever: JsonSchema7 = {
|
|
299
|
+
"$id": "/schemas/never",
|
|
300
|
+
"not": {}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const constAny: JsonSchema7 = {
|
|
304
|
+
"$id": "/schemas/any"
|
|
305
|
+
}
|
|
233
306
|
|
|
234
|
-
const
|
|
307
|
+
const constUnknown: JsonSchema7 = {
|
|
308
|
+
"$id": "/schemas/unknown"
|
|
309
|
+
}
|
|
235
310
|
|
|
236
|
-
const
|
|
311
|
+
const constVoid: JsonSchema7 = {
|
|
312
|
+
"$id": "/schemas/void"
|
|
313
|
+
}
|
|
237
314
|
|
|
238
|
-
const
|
|
315
|
+
const constAnyObject: JsonSchema7 = {
|
|
239
316
|
"$id": "/schemas/object",
|
|
240
317
|
"anyOf": [
|
|
241
318
|
{ "type": "object" },
|
|
@@ -243,13 +320,13 @@ const objectJsonSchema: JsonSchema7 = {
|
|
|
243
320
|
]
|
|
244
321
|
}
|
|
245
322
|
|
|
246
|
-
const
|
|
323
|
+
const constEmpty: JsonSchema7 = {
|
|
247
324
|
"$id": "/schemas/{}",
|
|
248
325
|
"anyOf": [
|
|
249
326
|
{ "type": "object" },
|
|
250
327
|
{ "type": "array" }
|
|
251
328
|
]
|
|
252
|
-
}
|
|
329
|
+
}
|
|
253
330
|
|
|
254
331
|
const $schema = "http://json-schema.org/draft-07/schema#"
|
|
255
332
|
|
|
@@ -291,35 +368,31 @@ const getASTJsonSchemaAnnotations = (ast: AST.AST): JsonSchemaAnnotations => {
|
|
|
291
368
|
const pruneUndefinedFromPropertySignature = (ast: AST.AST): AST.AST | undefined => {
|
|
292
369
|
if (Option.isNone(AST.getJSONSchemaAnnotation(ast))) {
|
|
293
370
|
switch (ast._tag) {
|
|
371
|
+
case "UndefinedKeyword":
|
|
372
|
+
return AST.neverKeyword
|
|
294
373
|
case "Union": {
|
|
295
|
-
const types
|
|
296
|
-
|
|
297
|
-
|
|
374
|
+
const types: Array<AST.AST> = []
|
|
375
|
+
let hasUndefined = false
|
|
376
|
+
for (const type of ast.types) {
|
|
377
|
+
const pruned = pruneUndefinedFromPropertySignature(type)
|
|
378
|
+
if (pruned) {
|
|
379
|
+
hasUndefined = true
|
|
380
|
+
if (!AST.isNeverKeyword(pruned)) {
|
|
381
|
+
types.push(pruned)
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
types.push(type)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (hasUndefined) {
|
|
388
|
+
return AST.Union.make(types)
|
|
298
389
|
}
|
|
299
390
|
break
|
|
300
391
|
}
|
|
392
|
+
case "Suspend":
|
|
393
|
+
return pruneUndefinedFromPropertySignature(ast.f())
|
|
301
394
|
case "Transformation":
|
|
302
|
-
return pruneUndefinedFromPropertySignature(
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/** @internal */
|
|
308
|
-
export const DEFINITION_PREFIX = "#/$defs/"
|
|
309
|
-
|
|
310
|
-
const get$ref = (id: string): string => `${DEFINITION_PREFIX}${id}`
|
|
311
|
-
|
|
312
|
-
const getRefinementInnerTransformation = (ast: AST.Refinement): AST.AST | undefined => {
|
|
313
|
-
switch (ast.from._tag) {
|
|
314
|
-
case "Transformation":
|
|
315
|
-
return ast.from
|
|
316
|
-
case "Refinement":
|
|
317
|
-
return getRefinementInnerTransformation(ast.from)
|
|
318
|
-
case "Suspend": {
|
|
319
|
-
const from = ast.from.f()
|
|
320
|
-
if (AST.isRefinement(from)) {
|
|
321
|
-
return getRefinementInnerTransformation(from)
|
|
322
|
-
}
|
|
395
|
+
return pruneUndefinedFromPropertySignature(ast.from)
|
|
323
396
|
}
|
|
324
397
|
}
|
|
325
398
|
}
|
|
@@ -327,63 +400,86 @@ const getRefinementInnerTransformation = (ast: AST.Refinement): AST.AST | undefi
|
|
|
327
400
|
const isParseJsonTransformation = (ast: AST.AST): boolean =>
|
|
328
401
|
ast.annotations[AST.SchemaIdAnnotationId] === AST.ParseJsonSchemaId
|
|
329
402
|
|
|
330
|
-
function merge(a: JsonSchemaAnnotations, b: JsonSchema7): JsonSchema7
|
|
331
|
-
function merge(a: JsonSchema7, b: JsonSchemaAnnotations): JsonSchema7
|
|
332
|
-
function merge(a: JsonSchema7, b: JsonSchema7): JsonSchema7
|
|
333
|
-
function merge(a: object, b: object): object {
|
|
334
|
-
return { ...a, ...b }
|
|
335
|
-
}
|
|
336
|
-
|
|
337
403
|
const isOverrideAnnotation = (jsonSchema: JsonSchema7): boolean => {
|
|
338
404
|
return ("type" in jsonSchema) || ("oneOf" in jsonSchema) || ("anyOf" in jsonSchema) || ("const" in jsonSchema) ||
|
|
339
405
|
("enum" in jsonSchema) || ("$ref" in jsonSchema)
|
|
340
406
|
}
|
|
341
407
|
|
|
408
|
+
// Returns true if the schema is an enum with no other properties.
|
|
409
|
+
// This is used to merge enums together.
|
|
410
|
+
const isEnumOnly = (schema: JsonSchema7): schema is JsonSchema7Enum =>
|
|
411
|
+
"enum" in schema && Object.keys(schema).length === 1
|
|
412
|
+
|
|
413
|
+
const mergeRefinements = (from: any, jsonSchema: any, annotations: any): any => {
|
|
414
|
+
const out: any = { ...from, ...annotations, ...jsonSchema }
|
|
415
|
+
out.allOf ??= []
|
|
416
|
+
|
|
417
|
+
const handle = (name: string, filter: (i: any) => boolean) => {
|
|
418
|
+
if (name in jsonSchema && name in from) {
|
|
419
|
+
out.allOf.unshift({ [name]: from[name] })
|
|
420
|
+
out.allOf = out.allOf.filter(filter)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
handle("minLength", (i) => i.minLength > jsonSchema.minLength)
|
|
425
|
+
handle("maxLength", (i) => i.maxLength < jsonSchema.maxLength)
|
|
426
|
+
handle("pattern", (i) => i.pattern !== jsonSchema.pattern)
|
|
427
|
+
handle("minItems", (i) => i.minItems > jsonSchema.minItems)
|
|
428
|
+
handle("maxItems", (i) => i.maxItems < jsonSchema.maxItems)
|
|
429
|
+
handle("minimum", (i) => i.minimum > jsonSchema.minimum)
|
|
430
|
+
handle("maximum", (i) => i.maximum < jsonSchema.maximum)
|
|
431
|
+
handle("exclusiveMinimum", (i) => i.exclusiveMinimum > jsonSchema.exclusiveMinimum)
|
|
432
|
+
handle("exclusiveMaximum", (i) => i.exclusiveMaximum < jsonSchema.exclusiveMaximum)
|
|
433
|
+
handle("multipleOf", (i) => i.multipleOf !== jsonSchema.multipleOf)
|
|
434
|
+
|
|
435
|
+
if (out.allOf.length === 0) {
|
|
436
|
+
delete out.allOf
|
|
437
|
+
}
|
|
438
|
+
return out
|
|
439
|
+
}
|
|
440
|
+
|
|
342
441
|
const go = (
|
|
343
442
|
ast: AST.AST,
|
|
344
443
|
$defs: Record<string, JsonSchema7>,
|
|
345
444
|
handleIdentifier: boolean,
|
|
346
|
-
path: ReadonlyArray<PropertyKey
|
|
445
|
+
path: ReadonlyArray<PropertyKey>,
|
|
446
|
+
options: {
|
|
447
|
+
readonly getRef: (id: string) => string
|
|
448
|
+
readonly target: Target
|
|
449
|
+
}
|
|
347
450
|
): JsonSchema7 => {
|
|
451
|
+
if (handleIdentifier) {
|
|
452
|
+
const identifier = AST.getJSONIdentifier(ast)
|
|
453
|
+
if (Option.isSome(identifier)) {
|
|
454
|
+
const id = identifier.value
|
|
455
|
+
const out = { $ref: options.getRef(id) }
|
|
456
|
+
if (!Record.has($defs, id)) {
|
|
457
|
+
$defs[id] = out
|
|
458
|
+
$defs[id] = go(ast, $defs, false, path, options)
|
|
459
|
+
}
|
|
460
|
+
return out
|
|
461
|
+
}
|
|
462
|
+
}
|
|
348
463
|
const hook = AST.getJSONSchemaAnnotation(ast)
|
|
349
464
|
if (Option.isSome(hook)) {
|
|
350
465
|
const handler = hook.value as JsonSchema7
|
|
351
466
|
if (AST.isRefinement(ast)) {
|
|
352
|
-
const t =
|
|
467
|
+
const t = AST.getTransformationFrom(ast)
|
|
353
468
|
if (t === undefined) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
} catch (e) {
|
|
361
|
-
return {
|
|
362
|
-
...getJsonSchemaAnnotations(ast),
|
|
363
|
-
...handler
|
|
364
|
-
}
|
|
365
|
-
}
|
|
469
|
+
return mergeRefinements(
|
|
470
|
+
go(ast.from, $defs, handleIdentifier, path, options),
|
|
471
|
+
handler,
|
|
472
|
+
getJsonSchemaAnnotations(ast)
|
|
473
|
+
)
|
|
366
474
|
} else if (!isOverrideAnnotation(handler)) {
|
|
367
|
-
return go(t, $defs,
|
|
475
|
+
return go(t, $defs, handleIdentifier, path, options)
|
|
368
476
|
}
|
|
369
477
|
}
|
|
370
478
|
return handler
|
|
371
479
|
}
|
|
372
480
|
const surrogate = AST.getSurrogateAnnotation(ast)
|
|
373
481
|
if (Option.isSome(surrogate)) {
|
|
374
|
-
return go(surrogate.value, $defs, handleIdentifier, path)
|
|
375
|
-
}
|
|
376
|
-
if (handleIdentifier && !AST.isTransformation(ast) && !AST.isRefinement(ast)) {
|
|
377
|
-
const identifier = AST.getJSONIdentifier(ast)
|
|
378
|
-
if (Option.isSome(identifier)) {
|
|
379
|
-
const id = identifier.value
|
|
380
|
-
const out = { $ref: get$ref(id) }
|
|
381
|
-
if (!Record.has($defs, id)) {
|
|
382
|
-
$defs[id] = out
|
|
383
|
-
$defs[id] = go(ast, $defs, false, path)
|
|
384
|
-
}
|
|
385
|
-
return out
|
|
386
|
-
}
|
|
482
|
+
return go(surrogate.value, $defs, handleIdentifier, path, options)
|
|
387
483
|
}
|
|
388
484
|
switch (ast._tag) {
|
|
389
485
|
case "Declaration":
|
|
@@ -391,9 +487,9 @@ const go = (
|
|
|
391
487
|
case "Literal": {
|
|
392
488
|
const literal = ast.literal
|
|
393
489
|
if (literal === null) {
|
|
394
|
-
return
|
|
490
|
+
return { enum: [null], ...getJsonSchemaAnnotations(ast) }
|
|
395
491
|
} else if (Predicate.isString(literal) || Predicate.isNumber(literal) || Predicate.isBoolean(literal)) {
|
|
396
|
-
return
|
|
492
|
+
return { enum: [literal], ...getJsonSchemaAnnotations(ast) }
|
|
397
493
|
}
|
|
398
494
|
throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
|
|
399
495
|
}
|
|
@@ -402,15 +498,15 @@ const go = (
|
|
|
402
498
|
case "UndefinedKeyword":
|
|
403
499
|
throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
|
|
404
500
|
case "VoidKeyword":
|
|
405
|
-
return
|
|
501
|
+
return { ...constVoid, ...getJsonSchemaAnnotations(ast) }
|
|
406
502
|
case "NeverKeyword":
|
|
407
|
-
|
|
503
|
+
return { ...constNever, ...getJsonSchemaAnnotations(ast) }
|
|
408
504
|
case "UnknownKeyword":
|
|
409
|
-
return
|
|
505
|
+
return { ...constUnknown, ...getJsonSchemaAnnotations(ast) }
|
|
410
506
|
case "AnyKeyword":
|
|
411
|
-
return
|
|
507
|
+
return { ...constAny, ...getJsonSchemaAnnotations(ast) }
|
|
412
508
|
case "ObjectKeyword":
|
|
413
|
-
return
|
|
509
|
+
return { ...constAnyObject, ...getJsonSchemaAnnotations(ast) }
|
|
414
510
|
case "StringKeyword":
|
|
415
511
|
return { type: "string", ...getASTJsonSchemaAnnotations(ast) }
|
|
416
512
|
case "NumberKeyword":
|
|
@@ -422,18 +518,14 @@ const go = (
|
|
|
422
518
|
case "SymbolKeyword":
|
|
423
519
|
throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
|
|
424
520
|
case "TupleType": {
|
|
425
|
-
const elements = ast.elements.map((e, i) =>
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
go(annotatedAST.type, $defs, true, path),
|
|
434
|
-
getJsonSchemaAnnotations(annotatedAST)
|
|
435
|
-
)
|
|
436
|
-
)
|
|
521
|
+
const elements = ast.elements.map((e, i) => ({
|
|
522
|
+
...go(e.type, $defs, true, path.concat(i), options),
|
|
523
|
+
...getJsonSchemaAnnotations(e)
|
|
524
|
+
}))
|
|
525
|
+
const rest = ast.rest.map((annotatedAST) => ({
|
|
526
|
+
...go(annotatedAST.type, $defs, true, path, options),
|
|
527
|
+
...getJsonSchemaAnnotations(annotatedAST)
|
|
528
|
+
}))
|
|
437
529
|
const output: JsonSchema7Array = { type: "array" }
|
|
438
530
|
// ---------------------------------------------
|
|
439
531
|
// handle elements
|
|
@@ -470,11 +562,11 @@ const go = (
|
|
|
470
562
|
}
|
|
471
563
|
}
|
|
472
564
|
|
|
473
|
-
return
|
|
565
|
+
return { ...output, ...getJsonSchemaAnnotations(ast) }
|
|
474
566
|
}
|
|
475
567
|
case "TypeLiteral": {
|
|
476
568
|
if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
|
|
477
|
-
return
|
|
569
|
+
return { ...constEmpty, ...getJsonSchemaAnnotations(ast) }
|
|
478
570
|
}
|
|
479
571
|
let patternProperties: JsonSchema7 | undefined = undefined
|
|
480
572
|
let propertyNames: JsonSchema7 | undefined = undefined
|
|
@@ -482,11 +574,11 @@ const go = (
|
|
|
482
574
|
const parameter = is.parameter
|
|
483
575
|
switch (parameter._tag) {
|
|
484
576
|
case "StringKeyword": {
|
|
485
|
-
patternProperties = go(is.type, $defs, true, path)
|
|
577
|
+
patternProperties = go(is.type, $defs, true, path, options)
|
|
486
578
|
break
|
|
487
579
|
}
|
|
488
580
|
case "TemplateLiteral": {
|
|
489
|
-
patternProperties = go(is.type, $defs, true, path)
|
|
581
|
+
patternProperties = go(is.type, $defs, true, path, options)
|
|
490
582
|
propertyNames = {
|
|
491
583
|
type: "string",
|
|
492
584
|
pattern: AST.getTemplateLiteralRegExp(parameter).source
|
|
@@ -494,8 +586,8 @@ const go = (
|
|
|
494
586
|
break
|
|
495
587
|
}
|
|
496
588
|
case "Refinement": {
|
|
497
|
-
patternProperties = go(is.type, $defs, true, path)
|
|
498
|
-
propertyNames = go(parameter, $defs, true, path)
|
|
589
|
+
patternProperties = go(is.type, $defs, true, path, options)
|
|
590
|
+
propertyNames = go(parameter, $defs, true, path, options)
|
|
499
591
|
break
|
|
500
592
|
}
|
|
501
593
|
case "SymbolKeyword":
|
|
@@ -516,10 +608,10 @@ const go = (
|
|
|
516
608
|
const name = ps.name
|
|
517
609
|
if (Predicate.isString(name)) {
|
|
518
610
|
const pruned = pruneUndefinedFromPropertySignature(ps.type)
|
|
519
|
-
output.properties[name] =
|
|
520
|
-
go(pruned
|
|
521
|
-
getJsonSchemaAnnotations(ps)
|
|
522
|
-
|
|
611
|
+
output.properties[name] = {
|
|
612
|
+
...go(pruned ?? ps.type, $defs, true, path.concat(ps.name), options),
|
|
613
|
+
...getJsonSchemaAnnotations(ps)
|
|
614
|
+
}
|
|
523
615
|
// ---------------------------------------------
|
|
524
616
|
// handle optional property signatures
|
|
525
617
|
// ---------------------------------------------
|
|
@@ -541,69 +633,76 @@ const go = (
|
|
|
541
633
|
output.propertyNames = propertyNames
|
|
542
634
|
}
|
|
543
635
|
|
|
544
|
-
return
|
|
636
|
+
return { ...output, ...getJsonSchemaAnnotations(ast) }
|
|
545
637
|
}
|
|
546
638
|
case "Union": {
|
|
547
|
-
const enums: Array<AST.LiteralValue> = []
|
|
548
639
|
const anyOf: Array<JsonSchema7> = []
|
|
549
640
|
for (const type of ast.types) {
|
|
550
|
-
const schema = go(type, $defs, true, path)
|
|
641
|
+
const schema = go(type, $defs, true, path, options)
|
|
551
642
|
if ("enum" in schema) {
|
|
552
643
|
if (Object.keys(schema).length > 1) {
|
|
553
644
|
anyOf.push(schema)
|
|
554
645
|
} else {
|
|
555
|
-
|
|
556
|
-
|
|
646
|
+
const last = anyOf[anyOf.length - 1]
|
|
647
|
+
if (last !== undefined && isEnumOnly(last)) {
|
|
648
|
+
for (const e of schema.enum) {
|
|
649
|
+
last.enum.push(e)
|
|
650
|
+
}
|
|
651
|
+
} else {
|
|
652
|
+
anyOf.push(schema)
|
|
557
653
|
}
|
|
558
654
|
}
|
|
559
655
|
} else {
|
|
560
656
|
anyOf.push(schema)
|
|
561
657
|
}
|
|
562
658
|
}
|
|
563
|
-
if (anyOf.length === 0) {
|
|
564
|
-
return
|
|
659
|
+
if (anyOf.length === 1 && isEnumOnly(anyOf[0])) {
|
|
660
|
+
return { enum: anyOf[0].enum, ...getJsonSchemaAnnotations(ast) }
|
|
565
661
|
} else {
|
|
566
|
-
|
|
567
|
-
anyOf.push({ enum: enums })
|
|
568
|
-
}
|
|
569
|
-
return merge({ anyOf }, getJsonSchemaAnnotations(ast))
|
|
662
|
+
return { anyOf, ...getJsonSchemaAnnotations(ast) }
|
|
570
663
|
}
|
|
571
664
|
}
|
|
572
665
|
case "Enums": {
|
|
573
|
-
return
|
|
666
|
+
return {
|
|
574
667
|
$comment: "/schemas/enums",
|
|
575
|
-
anyOf: ast.enums.map((e) => ({ title: e[0], enum: [e[1]] }))
|
|
576
|
-
|
|
668
|
+
anyOf: ast.enums.map((e) => ({ title: e[0], enum: [e[1]] })),
|
|
669
|
+
...getJsonSchemaAnnotations(ast)
|
|
670
|
+
}
|
|
577
671
|
}
|
|
578
672
|
case "Refinement": {
|
|
579
|
-
if
|
|
673
|
+
// The jsonSchema annotation is required only if the refinement does not have a transformation
|
|
674
|
+
if (AST.getTransformationFrom(ast) === undefined) {
|
|
580
675
|
throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast))
|
|
581
676
|
}
|
|
582
|
-
return go(ast.from, $defs,
|
|
677
|
+
return go(ast.from, $defs, handleIdentifier, path, options)
|
|
583
678
|
}
|
|
584
679
|
case "TemplateLiteral": {
|
|
585
680
|
const regex = AST.getTemplateLiteralRegExp(ast)
|
|
586
|
-
return
|
|
681
|
+
return {
|
|
587
682
|
type: "string",
|
|
588
683
|
title: String(ast),
|
|
589
684
|
description: "a template literal",
|
|
590
|
-
pattern: regex.source
|
|
591
|
-
|
|
685
|
+
pattern: regex.source,
|
|
686
|
+
...getJsonSchemaAnnotations(ast)
|
|
687
|
+
}
|
|
592
688
|
}
|
|
593
689
|
case "Suspend": {
|
|
594
690
|
const identifier = Option.orElse(AST.getJSONIdentifier(ast), () => AST.getJSONIdentifier(ast.f()))
|
|
595
691
|
if (Option.isNone(identifier)) {
|
|
596
692
|
throw new Error(errors_.getJSONSchemaMissingIdentifierAnnotationErrorMessage(path, ast))
|
|
597
693
|
}
|
|
598
|
-
return go(ast.f(), $defs,
|
|
694
|
+
return go(ast.f(), $defs, handleIdentifier, path, options)
|
|
599
695
|
}
|
|
600
696
|
case "Transformation": {
|
|
601
|
-
// Properly handle S.parseJson transformations by focusing on
|
|
602
|
-
// the 'to' side of the AST. This approach prevents the generation of useless schemas
|
|
603
|
-
// derived from the 'from' side (type: string), ensuring the output matches the intended
|
|
604
|
-
// complex schema type.
|
|
605
697
|
if (isParseJsonTransformation(ast.from)) {
|
|
606
|
-
|
|
698
|
+
const out: JsonSchema7String & { contentSchema?: JsonSchema7 } = {
|
|
699
|
+
"type": "string",
|
|
700
|
+
"contentMediaType": "application/json"
|
|
701
|
+
}
|
|
702
|
+
if (options.target !== "jsonSchema7") {
|
|
703
|
+
out["contentSchema"] = go(ast.to, $defs, handleIdentifier, path, options)
|
|
704
|
+
}
|
|
705
|
+
return out
|
|
607
706
|
}
|
|
608
707
|
let next = ast.from
|
|
609
708
|
if (AST.isTypeLiteralTransformation(ast.transformation)) {
|
|
@@ -622,7 +721,7 @@ const go = (
|
|
|
622
721
|
next = AST.annotations(next, { [AST.DescriptionAnnotationId]: description.value })
|
|
623
722
|
}
|
|
624
723
|
}
|
|
625
|
-
return go(next, $defs,
|
|
724
|
+
return go(next, $defs, handleIdentifier, path, options)
|
|
626
725
|
}
|
|
627
726
|
}
|
|
628
727
|
}
|
package/src/Schema.ts
CHANGED
|
@@ -2632,7 +2632,9 @@ export interface TypeLiteral<
|
|
|
2632
2632
|
annotations: Annotations.Schema<Simplify<TypeLiteral.Type<Fields, Records>>>
|
|
2633
2633
|
): TypeLiteral<Fields, Records>
|
|
2634
2634
|
make(
|
|
2635
|
-
props:
|
|
2635
|
+
props: RequiredKeys<TypeLiteral.Constructor<Fields, Records>> extends never
|
|
2636
|
+
? void | Simplify<TypeLiteral.Constructor<Fields, Records>>
|
|
2637
|
+
: Simplify<TypeLiteral.Constructor<Fields, Records>>,
|
|
2636
2638
|
options?: MakeOptions
|
|
2637
2639
|
): Simplify<TypeLiteral.Type<Fields, Records>>
|
|
2638
2640
|
}
|
|
@@ -4669,6 +4671,7 @@ export const lowercased =
|
|
|
4669
4671
|
filter((a) => a === a.toLowerCase(), {
|
|
4670
4672
|
schemaId: LowercasedSchemaId,
|
|
4671
4673
|
description: "a lowercase string",
|
|
4674
|
+
jsonSchema: { pattern: "^[^A-Z]*$" },
|
|
4672
4675
|
...annotations
|
|
4673
4676
|
})
|
|
4674
4677
|
)
|
|
@@ -4699,6 +4702,7 @@ export const capitalized =
|
|
|
4699
4702
|
filter((a) => a[0]?.toUpperCase() === a[0], {
|
|
4700
4703
|
schemaId: CapitalizedSchemaId,
|
|
4701
4704
|
description: "a capitalized string",
|
|
4705
|
+
jsonSchema: { pattern: "^[^a-z]?.*$" },
|
|
4702
4706
|
...annotations
|
|
4703
4707
|
})
|
|
4704
4708
|
)
|
|
@@ -4729,6 +4733,7 @@ export const uncapitalized =
|
|
|
4729
4733
|
filter((a) => a[0]?.toLowerCase() === a[0], {
|
|
4730
4734
|
schemaId: UncapitalizedSchemaId,
|
|
4731
4735
|
description: "a uncapitalized string",
|
|
4736
|
+
jsonSchema: { pattern: "^[^A-Z]?.*$" },
|
|
4732
4737
|
...annotations
|
|
4733
4738
|
})
|
|
4734
4739
|
)
|
|
@@ -4759,6 +4764,7 @@ export const uppercased =
|
|
|
4759
4764
|
filter((a) => a === a.toUpperCase(), {
|
|
4760
4765
|
schemaId: UppercasedSchemaId,
|
|
4761
4766
|
description: "an uppercase string",
|
|
4767
|
+
jsonSchema: { pattern: "^[^a-z]*$" },
|
|
4762
4768
|
...annotations
|
|
4763
4769
|
})
|
|
4764
4770
|
)
|
|
@@ -4947,7 +4953,7 @@ export type ParseJsonOptions = {
|
|
|
4947
4953
|
const JsonString = String$.annotations({
|
|
4948
4954
|
[AST.IdentifierAnnotationId]: "JsonString",
|
|
4949
4955
|
[AST.TitleAnnotationId]: "JsonString",
|
|
4950
|
-
[AST.DescriptionAnnotationId]: "a JSON
|
|
4956
|
+
[AST.DescriptionAnnotationId]: "a string that will be parsed as JSON"
|
|
4951
4957
|
})
|
|
4952
4958
|
|
|
4953
4959
|
const getParseJsonTransformation = (options?: ParseJsonOptions) =>
|
|
@@ -6401,7 +6407,7 @@ export const minItems = <A>(
|
|
|
6401
6407
|
(a) => a.length >= minItems,
|
|
6402
6408
|
{
|
|
6403
6409
|
schemaId: MinItemsSchemaId,
|
|
6404
|
-
description: `an array of at least ${minItems}
|
|
6410
|
+
description: `an array of at least ${minItems} item(s)`,
|
|
6405
6411
|
jsonSchema: { minItems },
|
|
6406
6412
|
[AST.StableFilterAnnotationId]: true,
|
|
6407
6413
|
...annotations
|
|
@@ -6434,7 +6440,7 @@ export const maxItems = <A>(
|
|
|
6434
6440
|
self.pipe(
|
|
6435
6441
|
filter((a) => a.length <= n, {
|
|
6436
6442
|
schemaId: MaxItemsSchemaId,
|
|
6437
|
-
description: `an array of at most ${n}
|
|
6443
|
+
description: `an array of at most ${n} item(s)`,
|
|
6438
6444
|
jsonSchema: { maxItems: n },
|
|
6439
6445
|
[AST.StableFilterAnnotationId]: true,
|
|
6440
6446
|
...annotations
|
|
@@ -8738,10 +8744,16 @@ const makeClass = ({ Base, annotations, disableToString, fields, identifier, kin
|
|
|
8738
8744
|
disableToString?: boolean | undefined
|
|
8739
8745
|
}): any => {
|
|
8740
8746
|
const classSymbol = Symbol.for(`effect/Schema/${kind}/${identifier}`)
|
|
8747
|
+
|
|
8748
|
+
const ts = typeSchema(schema)
|
|
8749
|
+
const declarationSurrogate = ts.annotations({ identifier, ...annotations })
|
|
8750
|
+
const typeSide = ts.annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Type side)` })
|
|
8751
|
+
const transformationSurrogate = schema.annotations({ ...annotations })
|
|
8741
8752
|
const validateSchema = schema.annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Constructor)` })
|
|
8742
|
-
const encodedSide
|
|
8743
|
-
|
|
8753
|
+
const encodedSide = schema.annotations({ [AST.AutoTitleAnnotationId]: `${identifier} (Encoded side)` })
|
|
8754
|
+
|
|
8744
8755
|
const fallbackInstanceOf = (u: unknown) => Predicate.hasProperty(u, classSymbol) && ParseResult.is(typeSide)(u)
|
|
8756
|
+
|
|
8745
8757
|
const klass = class extends Base {
|
|
8746
8758
|
constructor(
|
|
8747
8759
|
props: { [x: string | symbol]: unknown } = {},
|
|
@@ -8768,6 +8780,7 @@ const makeClass = ({ Base, annotations, disableToString, fields, identifier, kin
|
|
|
8768
8780
|
if (astCache.has(this)) {
|
|
8769
8781
|
return astCache.get(this)!
|
|
8770
8782
|
}
|
|
8783
|
+
|
|
8771
8784
|
const declaration: Schema.Any = declare(
|
|
8772
8785
|
[typeSide],
|
|
8773
8786
|
{
|
|
@@ -8785,21 +8798,23 @@ const makeClass = ({ Base, annotations, disableToString, fields, identifier, kin
|
|
|
8785
8798
|
},
|
|
8786
8799
|
{
|
|
8787
8800
|
identifier,
|
|
8788
|
-
title: identifier,
|
|
8789
|
-
description: `an instance of ${identifier}`,
|
|
8790
8801
|
pretty: (pretty) => (self: any) => `${identifier}(${pretty(self)})`,
|
|
8791
8802
|
// @ts-expect-error
|
|
8792
8803
|
arbitrary: (arb) => (fc) => arb(fc).map((props) => new this(props)),
|
|
8793
8804
|
equivalence: identity,
|
|
8794
|
-
[AST.SurrogateAnnotationId]:
|
|
8805
|
+
[AST.SurrogateAnnotationId]: declarationSurrogate.ast,
|
|
8795
8806
|
...annotations
|
|
8796
8807
|
}
|
|
8797
8808
|
)
|
|
8809
|
+
|
|
8798
8810
|
const transformation = transform(
|
|
8799
8811
|
encodedSide,
|
|
8800
8812
|
declaration,
|
|
8801
8813
|
{ strict: true, decode: (input) => new this(input, true), encode: identity }
|
|
8802
|
-
).annotations({
|
|
8814
|
+
).annotations({
|
|
8815
|
+
[AST.JSONIdentifierAnnotationId]: identifier,
|
|
8816
|
+
[AST.SurrogateAnnotationId]: transformationSurrogate.ast
|
|
8817
|
+
})
|
|
8803
8818
|
astCache.set(this, transformation.ast)
|
|
8804
8819
|
return transformation.ast
|
|
8805
8820
|
}
|