@signe/schema-to-zod 2.8.3 → 2.9.0

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/index.d.ts CHANGED
@@ -2,8 +2,14 @@ import { ZodType } from 'zod';
2
2
  import { JSONSchema7, JSONSchema7Definition } from 'json-schema';
3
3
 
4
4
  /**
5
- * Converts a JSON Schema to a Zod schema
5
+ * Converts a JSON Schema to a full Zod schema.
6
+ * This export should be preferred when the schema uses allOf or if/then/else.
7
+ */
8
+ declare function jsonSchemaToZodSchema(schema: JSONSchema7 | JSONSchema7Definition[]): ZodType<unknown>;
9
+ /**
10
+ * Converts a JSON Schema to a Zod shape.
11
+ * This is kept for compatibility with the previous API.
6
12
  */
7
13
  declare function jsonSchemaToZod(schema: JSONSchema7 | JSONSchema7Definition[]): Record<string, ZodType<unknown>>;
8
14
 
9
- export { jsonSchemaToZod };
15
+ export { jsonSchemaToZod, jsonSchemaToZodSchema };
package/dist/index.js CHANGED
@@ -4075,6 +4075,48 @@ function isJSONSchema7(schema) {
4075
4075
  return typeof schema !== "boolean";
4076
4076
  }
4077
4077
  __name(isJSONSchema7, "isJSONSchema7");
4078
+ function asExtendedJSONSchema7(schema) {
4079
+ return schema;
4080
+ }
4081
+ __name(asExtendedJSONSchema7, "asExtendedJSONSchema7");
4082
+ function unwrapSchemaDefinition(schema) {
4083
+ if (typeof schema === "boolean") {
4084
+ return schema;
4085
+ }
4086
+ if (!schema || typeof schema !== "object") {
4087
+ return null;
4088
+ }
4089
+ if ("schema" in schema) {
4090
+ return unwrapSchemaDefinition(schema.schema);
4091
+ }
4092
+ return schema;
4093
+ }
4094
+ __name(unwrapSchemaDefinition, "unwrapSchemaDefinition");
4095
+ function isPlainObject(value) {
4096
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4097
+ }
4098
+ __name(isPlainObject, "isPlainObject");
4099
+ function areValuesEqual(left, right) {
4100
+ if (Object.is(left, right)) {
4101
+ return true;
4102
+ }
4103
+ if (Array.isArray(left) && Array.isArray(right)) {
4104
+ if (left.length !== right.length) {
4105
+ return false;
4106
+ }
4107
+ return left.every((item, index) => areValuesEqual(item, right[index]));
4108
+ }
4109
+ if (isPlainObject(left) && isPlainObject(right)) {
4110
+ const leftKeys = Object.keys(left);
4111
+ const rightKeys = Object.keys(right);
4112
+ if (leftKeys.length !== rightKeys.length) {
4113
+ return false;
4114
+ }
4115
+ return leftKeys.every((key) => areValuesEqual(left[key], right[key]));
4116
+ }
4117
+ return false;
4118
+ }
4119
+ __name(areValuesEqual, "areValuesEqual");
4078
4120
  var percent = z.number().min(0).max(100);
4079
4121
  var formatMap = {
4080
4122
  "date": /* @__PURE__ */ __name(() => z.string().regex(/^\d{4}-\d{2}-\d{2}$/), "date"),
@@ -4094,45 +4136,32 @@ var formatMap = {
4094
4136
  "code": /* @__PURE__ */ __name(() => z.string(), "code"),
4095
4137
  "percent": /* @__PURE__ */ __name(() => percent, "percent")
4096
4138
  };
4097
- var typeMap = {
4098
- "string": /* @__PURE__ */ __name((schema) => {
4099
- if (schema.format && formatMap[schema.format]) {
4100
- return formatMap[schema.format](schema);
4101
- }
4102
- return z.string();
4103
- }, "string"),
4104
- "number": /* @__PURE__ */ __name((schema) => {
4105
- if (schema.format === "percent") {
4106
- return percent;
4107
- }
4108
- return z.number();
4109
- }, "number"),
4110
- "integer": /* @__PURE__ */ __name(() => z.number().int(), "integer"),
4111
- "boolean": /* @__PURE__ */ __name(() => z.boolean(), "boolean"),
4112
- "null": /* @__PURE__ */ __name(() => z.null(), "null"),
4113
- "array": /* @__PURE__ */ __name((schema) => {
4114
- if (!schema.items || Array.isArray(schema.items)) {
4115
- throw new Error("Invalid array items");
4116
- }
4117
- if (!isJSONSchema7(schema.items)) {
4118
- throw new Error("Boolean schema is not supported for array items");
4119
- }
4120
- if (schema.items.type === "object") {
4121
- return z.array(z.object(jsonSchemaToZod(schema.items)));
4122
- }
4123
- const itemType = schema.items.type;
4124
- if (!itemType || !isValidSchemaType(itemType)) {
4125
- throw new Error(`Unsupported array item type: ${itemType}`);
4126
- }
4127
- return z.array(typeMap[itemType](schema.items));
4128
- }, "array"),
4129
- "object": /* @__PURE__ */ __name((schema) => {
4130
- if (!schema.properties) {
4131
- throw new Error("Invalid object schema: missing properties");
4132
- }
4133
- return z.object(jsonSchemaToZod(schema));
4134
- }, "object")
4135
- };
4139
+ function createRequiredUnknownSchema() {
4140
+ return z.custom((value) => value !== void 0);
4141
+ }
4142
+ __name(createRequiredUnknownSchema, "createRequiredUnknownSchema");
4143
+ function inferSchemaType(schema) {
4144
+ if (schema.type !== void 0 && !isValidSchemaType(schema.type)) {
4145
+ throw new Error(`Unsupported type: ${schema.type}`);
4146
+ }
4147
+ if (schema.type && isValidSchemaType(schema.type)) {
4148
+ return schema.type;
4149
+ }
4150
+ if (schema.properties || schema.required || schema.if || schema.then || schema.else || schema.allOf) {
4151
+ return "object";
4152
+ }
4153
+ if (schema.items) {
4154
+ return "array";
4155
+ }
4156
+ if (schema.pattern || schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.format && schema.format !== "percent") {
4157
+ return "string";
4158
+ }
4159
+ if (schema.minimum !== void 0 || schema.maximum !== void 0 || schema.format === "percent") {
4160
+ return "number";
4161
+ }
4162
+ return null;
4163
+ }
4164
+ __name(inferSchemaType, "inferSchemaType");
4136
4165
  function applyValidators(zodType, schema, required) {
4137
4166
  let zodTypeWithValidators = zodType;
4138
4167
  if (schema.type === "string" && required) {
@@ -4160,40 +4189,277 @@ function applyValidators(zodType, schema, required) {
4160
4189
  zodTypeWithValidators = zodTypeWithValidators.max(schema.maximum);
4161
4190
  }
4162
4191
  }
4163
- if (schema.enum) {
4164
- zodTypeWithValidators = zodTypeWithValidators.refine((value) => schema.enum.includes(value), {
4165
- message: `Must be one of: ${schema.enum.join(", ")}`
4166
- });
4167
- }
4168
4192
  if (schema.pattern) {
4169
4193
  zodTypeWithValidators = zodTypeWithValidators.regex(new RegExp(schema.pattern));
4170
4194
  }
4195
+ if (schema.const !== void 0) {
4196
+ zodTypeWithValidators = zodTypeWithValidators.refine((value) => areValuesEqual(value, schema.const), {
4197
+ message: `Must be equal to: ${JSON.stringify(schema.const)}`
4198
+ });
4199
+ }
4200
+ if (schema.enum) {
4201
+ zodTypeWithValidators = zodTypeWithValidators.refine((value) => schema.enum.some((enumValue) => areValuesEqual(value, enumValue)), {
4202
+ message: `Must be one of: ${schema.enum.map((value) => JSON.stringify(value)).join(", ")}`
4203
+ });
4204
+ }
4171
4205
  return zodTypeWithValidators;
4172
4206
  }
4173
4207
  __name(applyValidators, "applyValidators");
4174
- function getTypeFunction(schema) {
4175
- if (!schema.type || !isValidSchemaType(schema.type)) {
4176
- throw new Error(`Unsupported type: ${schema.type}`);
4208
+ function applyLogicalCompositionValidators(zodType, schema) {
4209
+ let composedType = zodType;
4210
+ if (schema.anyOf && schema.anyOf.length > 0) {
4211
+ composedType = composedType.superRefine((value, context) => {
4212
+ const matchesAnySchema = schema.anyOf.some((subSchema) => matchesSchemaDefinition(subSchema, value));
4213
+ if (!matchesAnySchema) {
4214
+ context.addIssue({
4215
+ code: z.ZodIssueCode.custom,
4216
+ message: "Must match at least one schema in anyOf"
4217
+ });
4218
+ }
4219
+ });
4220
+ }
4221
+ if (schema.not !== void 0) {
4222
+ composedType = composedType.superRefine((value, context) => {
4223
+ if (matchesSchemaDefinition(schema.not, value)) {
4224
+ context.addIssue({
4225
+ code: z.ZodIssueCode.custom,
4226
+ message: "Must not match schema in not"
4227
+ });
4228
+ }
4229
+ });
4177
4230
  }
4178
- return typeMap[schema.type](schema);
4231
+ return composedType;
4179
4232
  }
4180
- __name(getTypeFunction, "getTypeFunction");
4233
+ __name(applyLogicalCompositionValidators, "applyLogicalCompositionValidators");
4234
+ function matchesSchemaDefinition(schema, value) {
4235
+ if (!isJSONSchema7(schema)) {
4236
+ return schema;
4237
+ }
4238
+ return jsonSchemaDefinitionToZod(schema).safeParse(value).success;
4239
+ }
4240
+ __name(matchesSchemaDefinition, "matchesSchemaDefinition");
4241
+ function addSchemaIssues(schema, value, context) {
4242
+ if (!isJSONSchema7(schema)) {
4243
+ if (!schema) {
4244
+ context.addIssue({
4245
+ code: z.ZodIssueCode.custom,
4246
+ message: "Schema condition rejected the current value"
4247
+ });
4248
+ }
4249
+ return;
4250
+ }
4251
+ const result = jsonSchemaDefinitionToZod(schema).safeParse(value);
4252
+ if (result.success) {
4253
+ return;
4254
+ }
4255
+ for (const issue of result.error.issues) {
4256
+ context.addIssue(issue);
4257
+ }
4258
+ }
4259
+ __name(addSchemaIssues, "addSchemaIssues");
4260
+ function collectComposablePropertyKeys(schema) {
4261
+ const extendedSchema = asExtendedJSONSchema7(schema);
4262
+ const keys = /* @__PURE__ */ new Set();
4263
+ const visit = /* @__PURE__ */ __name((definition) => {
4264
+ if (!definition || !isJSONSchema7(definition)) {
4265
+ return;
4266
+ }
4267
+ const extendedDefinition = asExtendedJSONSchema7(definition);
4268
+ if (extendedDefinition.properties) {
4269
+ for (const key of Object.keys(extendedDefinition.properties)) {
4270
+ keys.add(key);
4271
+ }
4272
+ }
4273
+ if (extendedDefinition.required) {
4274
+ for (const key of extendedDefinition.required) {
4275
+ keys.add(key);
4276
+ }
4277
+ }
4278
+ if (extendedDefinition.dependentRequired) {
4279
+ for (const [key, requiredKeys] of Object.entries(extendedDefinition.dependentRequired)) {
4280
+ keys.add(key);
4281
+ for (const requiredKey of requiredKeys) {
4282
+ keys.add(requiredKey);
4283
+ }
4284
+ }
4285
+ }
4286
+ if (extendedDefinition.dependentSchemas) {
4287
+ for (const [key, dependentSchema] of Object.entries(extendedDefinition.dependentSchemas)) {
4288
+ keys.add(key);
4289
+ visit(dependentSchema);
4290
+ }
4291
+ }
4292
+ if (extendedDefinition.anyOf) {
4293
+ for (const item of extendedDefinition.anyOf) {
4294
+ visit(item);
4295
+ }
4296
+ }
4297
+ if (extendedDefinition.allOf) {
4298
+ for (const item of extendedDefinition.allOf) {
4299
+ visit(item);
4300
+ }
4301
+ }
4302
+ visit(extendedDefinition.not);
4303
+ visit(extendedDefinition.if);
4304
+ visit(extendedDefinition.then);
4305
+ visit(extendedDefinition.else);
4306
+ }, "visit");
4307
+ visit(extendedSchema);
4308
+ return keys;
4309
+ }
4310
+ __name(collectComposablePropertyKeys, "collectComposablePropertyKeys");
4311
+ function hasComposableValidation(schema) {
4312
+ const extendedSchema = asExtendedJSONSchema7(schema);
4313
+ return Boolean(extendedSchema.dependentRequired || extendedSchema.dependentSchemas || extendedSchema.if || extendedSchema.then || extendedSchema.else || Array.isArray(extendedSchema.allOf) && extendedSchema.allOf.length > 0);
4314
+ }
4315
+ __name(hasComposableValidation, "hasComposableValidation");
4316
+ function createPrimitiveType(schema, type) {
4317
+ switch (type) {
4318
+ case "string":
4319
+ if (schema.format && formatMap[schema.format]) {
4320
+ return formatMap[schema.format](schema);
4321
+ }
4322
+ return z.string();
4323
+ case "number":
4324
+ if (schema.format === "percent") {
4325
+ return percent;
4326
+ }
4327
+ return z.number();
4328
+ case "integer":
4329
+ return z.number().int();
4330
+ case "boolean":
4331
+ return z.boolean();
4332
+ case "null":
4333
+ return z.null();
4334
+ }
4335
+ }
4336
+ __name(createPrimitiveType, "createPrimitiveType");
4337
+ function buildObjectSchema(schema) {
4338
+ const extendedSchema = asExtendedJSONSchema7(schema);
4339
+ const shape = {};
4340
+ if (extendedSchema.properties) {
4341
+ for (const [key, propertySchema] of Object.entries(extendedSchema.properties)) {
4342
+ if (!isJSONSchema7(propertySchema)) {
4343
+ continue;
4344
+ }
4345
+ shape[key] = convertPropertyToZod(propertySchema, key, extendedSchema);
4346
+ }
4347
+ }
4348
+ if (extendedSchema.required) {
4349
+ for (const key of extendedSchema.required) {
4350
+ if (!shape[key]) {
4351
+ shape[key] = createRequiredUnknownSchema();
4352
+ }
4353
+ }
4354
+ }
4355
+ for (const key of collectComposablePropertyKeys(extendedSchema)) {
4356
+ if (!shape[key]) {
4357
+ shape[key] = z.unknown().optional();
4358
+ }
4359
+ }
4360
+ const baseObject = z.object(shape);
4361
+ if (!hasComposableValidation(extendedSchema)) {
4362
+ return baseObject;
4363
+ }
4364
+ return baseObject.superRefine((value, context) => {
4365
+ if (extendedSchema.dependentRequired) {
4366
+ for (const [triggerKey, requiredKeys] of Object.entries(extendedSchema.dependentRequired)) {
4367
+ if (!Object.prototype.hasOwnProperty.call(value, triggerKey)) {
4368
+ continue;
4369
+ }
4370
+ for (const requiredKey of requiredKeys) {
4371
+ if (!Object.prototype.hasOwnProperty.call(value, requiredKey)) {
4372
+ context.addIssue({
4373
+ code: z.ZodIssueCode.custom,
4374
+ message: `Property "${requiredKey}" is required when "${triggerKey}" is present`,
4375
+ path: [
4376
+ requiredKey
4377
+ ]
4378
+ });
4379
+ }
4380
+ }
4381
+ }
4382
+ }
4383
+ if (extendedSchema.dependentSchemas) {
4384
+ for (const [triggerKey, dependentSchema] of Object.entries(extendedSchema.dependentSchemas)) {
4385
+ if (!Object.prototype.hasOwnProperty.call(value, triggerKey)) {
4386
+ continue;
4387
+ }
4388
+ addSchemaIssues(dependentSchema, value, context);
4389
+ }
4390
+ }
4391
+ if (extendedSchema.if) {
4392
+ const matches = matchesSchemaDefinition(extendedSchema.if, value);
4393
+ if (matches && extendedSchema.then) {
4394
+ addSchemaIssues(extendedSchema.then, value, context);
4395
+ }
4396
+ if (!matches && extendedSchema.else) {
4397
+ addSchemaIssues(extendedSchema.else, value, context);
4398
+ }
4399
+ }
4400
+ if (extendedSchema.allOf) {
4401
+ for (const item of extendedSchema.allOf) {
4402
+ addSchemaIssues(item, value, context);
4403
+ }
4404
+ }
4405
+ });
4406
+ }
4407
+ __name(buildObjectSchema, "buildObjectSchema");
4408
+ function buildArraySchema(schema) {
4409
+ if (!schema.items || Array.isArray(schema.items)) {
4410
+ throw new Error("Invalid array items");
4411
+ }
4412
+ return applyValidators(z.array(jsonSchemaDefinitionToZod(schema.items, true)), schema, true);
4413
+ }
4414
+ __name(buildArraySchema, "buildArraySchema");
4415
+ function jsonSchemaDefinitionToZod(schema, required = true) {
4416
+ if (!isJSONSchema7(schema)) {
4417
+ return schema ? z.any() : z.never();
4418
+ }
4419
+ const inferredType = inferSchemaType(schema);
4420
+ let zodType;
4421
+ if (inferredType === "object") {
4422
+ zodType = buildObjectSchema(schema);
4423
+ return applyLogicalCompositionValidators(zodType, schema);
4424
+ }
4425
+ if (inferredType === "array") {
4426
+ zodType = buildArraySchema(schema);
4427
+ return applyLogicalCompositionValidators(zodType, schema);
4428
+ }
4429
+ if (inferredType) {
4430
+ zodType = applyValidators(createPrimitiveType(schema, inferredType), schema, required);
4431
+ return applyLogicalCompositionValidators(zodType, schema);
4432
+ }
4433
+ zodType = applyValidators(z.any(), schema, required);
4434
+ return applyLogicalCompositionValidators(zodType, schema);
4435
+ }
4436
+ __name(jsonSchemaDefinitionToZod, "jsonSchemaDefinitionToZod");
4181
4437
  function convertPropertyToZod(schema, key, parentSchema) {
4182
4438
  const required = Array.isArray(parentSchema.required) && parentSchema.required.includes(key);
4183
- if (schema.type === "object") {
4184
- const objectSchema = z.object(jsonSchemaToZod(schema));
4185
- return required ? objectSchema : objectSchema.optional();
4186
- }
4187
- const typeValidator = applyValidators(getTypeFunction(schema), schema, required);
4188
- return required ? typeValidator : typeValidator.optional();
4439
+ const propertySchema = jsonSchemaDefinitionToZod(schema, required);
4440
+ return required ? propertySchema : propertySchema.optional();
4189
4441
  }
4190
4442
  __name(convertPropertyToZod, "convertPropertyToZod");
4443
+ function jsonSchemaToZodSchema(schema) {
4444
+ if (Array.isArray(schema)) {
4445
+ const definitions = schema.map(unwrapSchemaDefinition).filter((definition) => definition !== null);
4446
+ return jsonSchemaDefinitionToZod({
4447
+ type: "object",
4448
+ allOf: definitions
4449
+ });
4450
+ }
4451
+ return jsonSchemaDefinitionToZod(schema);
4452
+ }
4453
+ __name(jsonSchemaToZodSchema, "jsonSchemaToZodSchema");
4191
4454
  function jsonSchemaToZod(schema) {
4192
4455
  const zodSchema = {};
4193
4456
  if (Array.isArray(schema)) {
4194
4457
  for (const item of schema) {
4195
- if (!isJSONSchema7(item)) continue;
4196
- Object.assign(zodSchema, jsonSchemaToZod(item));
4458
+ const definition = unwrapSchemaDefinition(item);
4459
+ if (!definition || !isJSONSchema7(definition)) {
4460
+ continue;
4461
+ }
4462
+ Object.assign(zodSchema, jsonSchemaToZod(definition));
4197
4463
  }
4198
4464
  } else if (schema.type === "object" && schema.properties) {
4199
4465
  for (const [key, prop] of Object.entries(schema.properties)) {
@@ -4205,6 +4471,7 @@ function jsonSchemaToZod(schema) {
4205
4471
  }
4206
4472
  __name(jsonSchemaToZod, "jsonSchemaToZod");
4207
4473
  export {
4208
- jsonSchemaToZod
4474
+ jsonSchemaToZod,
4475
+ jsonSchemaToZodSchema
4209
4476
  };
4210
4477
  //# sourceMappingURL=index.js.map