Upstream strapi/strapi state of the touched files before b5854586e1

This commit is contained in:
upstream committed 2026-10-17 03:33:16 +02:00
commit 63e22609c4
4 files changed
+32180

No files matched your search

@@ -0,0 +1,40 @@
import type { Core } from '@strapi/types';
import * as z from 'zod/v4';
import { CoreContentTypeRouteValidator } from '../content-type';
describe('CoreContentTypeRouteValidator query parameters', () => {
const strapi = {
getModel: jest.fn(() => ({
attributes: {
title: { type: 'string' },
rating: { type: 'integer' },
},
})),
} as unknown as Core.Strapi;
const validator = new CoreContentTypeRouteValidator(strapi, 'api::article.article');
test('Zod enum-keyed records are exhaustive by default', () => {
const exhaustiveRecord = z.record(z.enum(['title', 'rating']), z.string());
expect(exhaustiveRecord.safeParse({ title: 'asc' }).success).toBe(false);
});
test('sort accepts sparse enum-keyed objects', () => {
const { sort } = validator.queryParams(['sort']);
expect(sort?.parse({ title: 'asc' })).toEqual({ title: 'asc' });
expect(sort?.safeParse({ unknown: 'asc' }).success).toBe(false);
});
test('filters accept sparse enum-keyed objects', () => {
const { filters } = validator.queryParams(['filters']);
expect(filters?.parse({ title: { $eq: 'hello' } })).toEqual({
title: { $eq: 'hello' },
});
expect(filters?.safeParse({ unknown: { $eq: 'hello' } }).success).toBe(false);
});
});
@@ -0,0 +1,723 @@
/**
* @file This module provides a set of functions to convert Strapi schema attributes into Zod schemas.
* It handles various attribute types, including primitive types, components, dynamic zones, media, and relations.
* The module also provides functions to create input schemas for these attributes, which are used for validation
* of incoming data.
*/
import { type Core, type Schema, UID } from '@strapi/types';
import {
relations,
maybeRequired,
maybeReadonly,
maybeWithDefault,
maybeWithMinMax,
augmentSchema,
} from '@strapi/utils';
import { z } from 'zod/v4';
import { BOOLEAN_LITERAL_VALUES } from './constants';
// eslint-disable-next-line import/no-cycle
import { CoreComponentRouteValidator } from './component';
import { CoreContentTypeRouteValidator } from './content-type';
import { safeSchemaCreation } from './utils';
/**
* Converts a BigInteger attribute to a Zod schema.
* @param attribute - The BigInteger attribute object from the Strapi schema.
* @returns A Zod schema representing the BigInteger field.
*/
export const bigIntegerToSchema = (attribute: Schema.Attribute.BigInteger): z.Schema => {
const { writable, required, min, max, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
(schema) => (min !== undefined ? schema.min(min as unknown as number) : schema),
(schema) => (max !== undefined ? schema.max(max as unknown as number) : schema),
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A biginteger field');
};
/**
* Converts a blocks attribute to a Zod schema.
* @returns A Zod schema representing the blocks field.
*/
export const blocksToSchema = (): z.Schema => {
return z.array(z.any()).describe('A blocks field');
};
/**
* Converts a boolean attribute to a Zod schema.
* @param attribute - The Boolean attribute object from the Strapi schema.
* @returns A Zod schema representing the boolean field.
*/
export const booleanToSchema = (attribute: Schema.Attribute.Boolean): z.Schema => {
const { writable, required, default: defaultValue } = attribute;
const schema = augmentSchema(z.boolean().nullable(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A boolean field');
};
/**
* Converts a component attribute to a Zod schema.
* @param attribute - The Component attribute object from the Strapi schema.
* @returns A Zod schema representing the component field.
*/
export const componentToSchema = (
strapi: Core.Strapi,
attribute: Schema.Attribute.Component<UID.Component, boolean>
): z.Schema => {
const { writable, required, min, max, component, repeatable } = attribute;
const componentSchema = safeSchemaCreation(
strapi,
component,
() => new CoreComponentRouteValidator(strapi, component).entry
) as z.ZodType;
const baseSchema = repeatable ? z.array(componentSchema) : componentSchema;
const schema = augmentSchema(baseSchema, [
(schema) => (min !== undefined && schema instanceof z.ZodArray ? schema.min(min) : schema),
(schema) => (max !== undefined && schema instanceof z.ZodArray ? schema.max(max) : schema),
maybeRequired(required),
maybeReadonly(writable),
]);
return schema.describe('A component field');
};
/**
* Converts a date attribute to a Zod schema.
* @param attribute - The Date attribute object from the Strapi schema.
* @returns A Zod schema representing the date field.
*/
export const dateToSchema = (attribute: Schema.Attribute.Date): z.Schema => {
const { writable, required, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A date field');
};
/**
* Converts a datetime attribute to a Zod schema.
* @param attribute - The DateTime attribute object from the Strapi schema.
* @returns A Zod schema representing the datetime field.
*/
export const datetimeToSchema = (attribute: Schema.Attribute.DateTime): z.Schema => {
const { writable, required, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A datetime field');
};
/**
* Converts a decimal attribute to a Zod schema.
* @param attribute - The Decimal attribute object from the Strapi schema.
* @returns A Zod schema representing the decimal field.
*/
export const decimalToSchema = (attribute: Schema.Attribute.Decimal): z.Schema => {
const { writable, required, min, max, default: defaultValue } = attribute;
const schema = augmentSchema(z.number(), [
maybeWithMinMax(min, max),
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A decimal field');
};
/**
* Converts a dynamic zone attribute to a Zod schema.
* @param attribute - The DynamicZone attribute object from the Strapi schema.
* @returns A Zod schema representing the dynamic zone field.
*/
export const dynamicZoneToSchema = (attribute: Schema.Attribute.DynamicZone): z.Schema => {
const { writable, required, min, max } = attribute;
const baseSchema = z.array(z.any());
const schema = augmentSchema(baseSchema, [
maybeWithMinMax(min, max),
maybeRequired(required),
maybeReadonly(writable),
]);
return schema.describe('A dynamic zone field');
};
/**
* Converts an email attribute to a Zod schema.
* @param attribute - The Email attribute object from the Strapi schema.
* @returns A Zod schema representing the email field.
*/
export const emailToSchema = (attribute: Schema.Attribute.Email): z.Schema => {
const { writable, required, default: defaultValue, minLength, maxLength } = attribute;
const baseSchema = z.email();
const schema = augmentSchema(baseSchema, [
maybeWithMinMax(minLength, maxLength),
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('An email field');
};
/**
* Converts an enumeration attribute to a Zod schema.
* @param attribute - The Enumeration attribute object from the Strapi schema.
* @returns A Zod schema representing the enumeration field.
*/
export const enumToSchema = (attribute: Schema.Attribute.Enumeration<string[]>): z.Schema => {
const { writable, required, default: defaultValue, enum: enumValues } = attribute;
const baseSchema = z.enum(enumValues);
const schema = augmentSchema(baseSchema, [
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('An enum field');
};
/**
* Converts a float attribute to a Zod schema.
* @param attribute - The Float attribute object from the Strapi schema.
* @returns A Zod schema representing the float field.
*/
export const floatToSchema = (attribute: Schema.Attribute.Float): z.Schema => {
const { writable, required, min, max, default: defaultValue } = attribute;
const schema = augmentSchema(z.number(), [
maybeWithMinMax(min, max),
maybeRequired(required),
maybeReadonly(writable),
maybeWithDefault(defaultValue),
]);
return schema.describe('A float field');
};
/**
* Converts an integer attribute to a Zod schema.
* @param attribute - The Integer attribute object from the Strapi schema.
* @returns A Zod schema representing the integer field.
*/
export const integerToSchema = (attribute: Schema.Attribute.Integer): z.Schema => {
const { writable, required, min, max, default: defaultValue } = attribute;
const baseSchema = z.number().int();
const schema = augmentSchema(baseSchema, [
maybeWithMinMax(min, max),
maybeRequired(required),
maybeReadonly(writable),
maybeWithDefault(defaultValue),
]);
return schema.describe('An integer field');
};
/**
* Converts a JSON attribute to a Zod schema.
* @param attribute - The JSON attribute object from the Strapi schema.
* @returns A Zod schema representing the JSON field.
*/
export const jsonToSchema = (attribute: Schema.Attribute.JSON): z.Schema => {
const { writable, required, default: defaultValue } = attribute;
const schema = augmentSchema(z.any(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A JSON field');
};
/**
* Converts a media attribute to a Zod schema.
* @param attribute - The Media attribute object from the Strapi schema.
* @returns A Zod schema representing the media field.
*/
export const mediaToSchema = (
strapi: Core.Strapi,
attribute: Schema.Attribute.Media<Schema.Attribute.MediaKind | undefined, boolean>
): z.Schema => {
const { writable, required, multiple } = attribute;
const uploadPlugin = strapi.plugin('upload');
// @ts-expect-error there is a mismatch between a raw module and a loader module
const fileSchema = uploadPlugin.contentTypes.file as Struct.ContentTypeSchema;
const mediaSchema = safeSchemaCreation(
strapi,
fileSchema.uid,
() => new CoreContentTypeRouteValidator(strapi, fileSchema.uid).document
) as z.ZodType;
const baseSchema = multiple ? z.array(mediaSchema) : mediaSchema;
const schema = augmentSchema(baseSchema, [maybeRequired(required), maybeReadonly(writable)]);
return schema.describe('A media field');
};
/**
* Converts a relation attribute to a Zod schema.
* @param attribute - The Relation attribute object from the Strapi schema.
* @returns A Zod schema representing the relational field.
*/
export const relationToSchema = (
strapi: Core.Strapi,
attribute: Schema.Attribute.Relation
): z.Schema => {
if (!('target' in attribute)) {
return z.any();
}
const { writable, required, target } = attribute as Schema.Attribute.RelationWithTarget;
const targetSchema = safeSchemaCreation(
strapi,
target,
() => new CoreContentTypeRouteValidator(strapi, target).document
) as z.ZodType;
const baseSchema = relations.isAnyToMany(attribute) ? z.array(targetSchema) : targetSchema;
const schema = augmentSchema(baseSchema, [maybeRequired(required), maybeReadonly(writable)]);
return schema.describe('A relational field');
};
/**
* Converts a string, text, rich text, or password attribute to a Zod schema.
* @param attribute - The String, Text, RichText, or Password attribute object from the Strapi schema.
* @returns A Zod schema representing the field.
*/
export const stringToSchema = (
attribute:
| Schema.Attribute.String
| Schema.Attribute.Text
| Schema.Attribute.RichText
| Schema.Attribute.Password
): z.Schema => {
const { writable, required, default: defaultValue, minLength, maxLength } = attribute;
const schema = augmentSchema(z.string(), [
maybeWithMinMax(minLength, maxLength),
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe(`A ${attribute.type} field`);
};
/**
* Converts a time attribute to a Zod schema.
* @param attribute - The Time attribute object from the Strapi schema.
* @returns A Zod schema representing the time field.
*/
export const timeToSchema = (attribute: Schema.Attribute.Time): z.Schema => {
const { writable, required, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A time field');
};
/**
* Converts a timestamp attribute to a Zod schema.
* @param attribute - The Timestamp attribute object from the Strapi schema.
* @returns A Zod schema representing the timestamp field.
*/
export const timestampToSchema = (attribute: Schema.Attribute.Timestamp): z.Schema => {
const { writable, required, default: defaultValue } = attribute;
const baseSchema = z.union([z.string(), z.number()]);
const schema = augmentSchema(baseSchema, [
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A timestamp field');
};
/**
* Converts a UID attribute to a Zod schema.
* @param attribute - The UID attribute object from the Strapi schema.
* @returns A Zod schema representing the UID field.
*/
export const uidToSchema = (attribute: Schema.Attribute.UID): z.Schema => {
const { writable, required, default: defaultValue, minLength, maxLength } = attribute;
const schema = augmentSchema(z.string(), [
maybeWithMinMax(minLength, maxLength),
maybeRequired(required),
maybeWithDefault(defaultValue),
maybeReadonly(writable),
]);
return schema.describe('A UID field');
};
/**
* Converts a BigInteger attribute to a Zod schema for input validation.
* @param attribute - The BigInteger attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the BigInteger field.
*/
export const bigIntegerToInputSchema = (attribute: Schema.Attribute.BigInteger) => {
const { required, min, max, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
(schema) => (min !== undefined ? schema.min(min as unknown as number) : schema),
(schema) => (max !== undefined ? schema.max(max as unknown as number) : schema),
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A biginteger field');
};
/**
* Converts a blocks attribute to a Zod schema for input validation.
* @returns A Zod schema for input validation of the blocks field.
*/
export const blocksToInputSchema = () => {
// TODO: better support blocks data structure
return z.array(z.any()).describe('A blocks field');
};
/**
* Converts a boolean attribute to a Zod schema for input validation.
* @param attribute - The Boolean attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the boolean field.
*/
export const booleanToInputSchema = (attribute: Schema.Attribute.Boolean) => {
const { required, default: defaultValue } = attribute;
const baseSchema = z.enum(BOOLEAN_LITERAL_VALUES).nullable();
const schema = augmentSchema(baseSchema, [
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A boolean field');
};
/**
* Converts a component attribute to a Zod schema for input validation.
* @param attribute - The Component attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the component field.
*/
export const componentToInputSchema = (
attribute: Schema.Attribute.Component<UID.Component, boolean>
) => {
const { required, repeatable, min, max } = attribute;
const baseSchema = repeatable ? z.array(z.any()) : z.any();
const schema = augmentSchema(baseSchema, [
(schema) => (min !== undefined && schema instanceof z.ZodArray ? schema.min(min) : schema),
(schema) => (max !== undefined && schema instanceof z.ZodArray ? schema.max(max) : schema),
maybeRequired(required),
]);
return schema.describe('A component field');
};
/**
* Converts a date attribute to a Zod schema for input validation.
* @param attribute - The Date attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the date field.
*/
export const dateToInputSchema = (attribute: Schema.Attribute.Date) => {
const { required, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A date field');
};
/**
* Converts a datetime attribute to a Zod schema for input validation.
* @param attribute - The DateTime attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the datetime field.
*/
export const datetimeToInputSchema = (attribute: Schema.Attribute.DateTime) => {
const { required, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A datetime field');
};
/**
* Converts a decimal attribute to a Zod schema for input validation.
* @param attribute - The Decimal attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the decimal field.
*/
export const decimalToInputSchema = (attribute: Schema.Attribute.Decimal) => {
const { required, min, max, default: defaultValue } = attribute;
const schema = augmentSchema(z.number(), [
maybeWithMinMax(min, max),
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A decimal field');
};
/**
* Converts a dynamic zone attribute to a Zod schema for input validation.
* @param attribute - The DynamicZone attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the dynamic zone field.
*/
export const dynamicZoneToInputSchema = (attribute: Schema.Attribute.DynamicZone) => {
const { required, min, max } = attribute;
const baseSchema = z.array(z.any());
const schema = augmentSchema(baseSchema, [maybeWithMinMax(min, max), maybeRequired(required)]);
return schema.describe('A dynamic zone field');
};
/**
* Converts an email attribute to a Zod schema for input validation.
* @param attribute - The Email attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the email field.
*/
export const emailToInputSchema = (attribute: Schema.Attribute.Email) => {
const { required, default: defaultValue, minLength, maxLength } = attribute;
const baseSchema = z.email();
const schema = augmentSchema(baseSchema, [
maybeWithMinMax(minLength, maxLength),
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('An email field');
};
/**
* Converts an enumeration attribute to a Zod schema for input validation.
* @param attribute - The Enumeration attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the enumeration field.
*/
export const enumerationToInputSchema = (attribute: Schema.Attribute.Enumeration<string[]>) => {
const { required, default: defaultValue, enum: enumValues } = attribute;
const baseSchema = z.enum(enumValues);
const schema = augmentSchema(baseSchema, [
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('An enum field');
};
/**
* Converts a float attribute to a Zod schema for input validation.
* @param attribute - The Float attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the float field.
*/
export const floatToInputSchema = (attribute: Schema.Attribute.Float) => {
const { required, min, max, default: defaultValue } = attribute;
const schema = augmentSchema(z.number(), [
maybeWithMinMax(min, max),
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A float field');
};
/**
* Converts an integer attribute to a Zod schema for input validation.
* @param attribute - The Integer attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the integer field.
*/
export const integerToInputSchema = (attribute: Schema.Attribute.Integer) => {
const { required, min, max, default: defaultValue } = attribute;
const baseSchema = z.number().int();
const schema = augmentSchema(baseSchema, [
maybeWithMinMax(min, max),
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A float field');
};
/**
* Converts a JSON attribute to a Zod schema for input validation.
* @param attribute - The JSON attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the JSON field.
*/
export const jsonToInputSchema = (attribute: Schema.Attribute.JSON) => {
const { required, default: defaultValue } = attribute;
const schema = augmentSchema(z.any(), [maybeRequired(required), maybeWithDefault(defaultValue)]);
return schema.describe('A JSON field');
};
/**
* Converts a media attribute to a Zod schema for input validation.
* @param attribute - The Media attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the media field.
*/
export const mediaToInputSchema = (
attribute: Schema.Attribute.Media<Schema.Attribute.MediaKind | undefined, boolean>
) => {
const { required, multiple } = attribute;
const baseSchema = multiple ? z.array(z.any()) : z.any();
const schema = augmentSchema(baseSchema, [maybeRequired(required)]);
return schema.describe('A media field');
};
/**
* Converts a relation attribute to a Zod schema for input validation.
* @param attribute - The Relation attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the relational field.
*/
export const relationToInputSchema = (attribute: Schema.Attribute.Relation) => {
const { required } = attribute;
const isToMany = relations.isAnyToMany(attribute);
const uuid = z.string().uuid();
const baseSchema = isToMany ? z.array(uuid) : uuid;
const schema = augmentSchema(baseSchema, [maybeRequired(required)]);
return schema.describe('A relational field');
};
/**
* Converts a string, text, rich text, or password attribute to a Zod schema for input validation.
* @param attribute - The String, Text, RichText, or Password attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the field.
*/
export const textToInputSchema = (
attribute:
| Schema.Attribute.String
| Schema.Attribute.Text
| Schema.Attribute.RichText
| Schema.Attribute.Password
) => {
const { required, default: defaultValue, minLength, maxLength } = attribute;
const schema = augmentSchema(z.string(), [
maybeWithMinMax(minLength, maxLength),
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe(`A ${attribute.type} field`);
};
/**
* Converts a time attribute to a Zod schema for input validation.
* @param attribute - The Time attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the time field.
*/
export const timeToInputSchema = (attribute: Schema.Attribute.Time) => {
const { required, default: defaultValue } = attribute;
const schema = augmentSchema(z.string(), [
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A time field');
};
/**
* Converts a timestamp attribute to a Zod schema for input validation.
* @param attribute - The Timestamp attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the timestamp field.
*/
export const timestampToInputSchema = (attribute: Schema.Attribute.Timestamp) => {
const { required, default: defaultValue } = attribute;
const baseSchema = z.union([z.string(), z.number()]);
const schema = augmentSchema(baseSchema, [
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A timestamp field');
};
/**
* Converts a UID attribute to a Zod schema for input validation.
* @param attribute - The UID attribute object from the Strapi schema.
* @returns A Zod schema for input validation of the UID field.
*/
export const uidToInputSchema = (attribute: Schema.Attribute.UID) => {
const { required, default: defaultValue, minLength, maxLength } = attribute;
const schema = augmentSchema(z.string(), [
maybeWithMinMax(minLength, maxLength),
maybeRequired(required),
maybeWithDefault(defaultValue),
]);
return schema.describe('A UID field');
};
@@ -0,0 +1,271 @@
/**
* @fileoverview
* This file contains functions responsible for mapping Strapi attribute definitions to Zod schemas.
*/
import type { Core, Schema } from '@strapi/types';
import * as z from 'zod/v4';
// eslint-disable-next-line import/no-cycle
import * as attributes from './attributes';
const isCustomFieldAttribute = (
attribute: unknown
): attribute is { type: 'customField'; customField: string } => {
return (
!!attribute &&
typeof attribute === 'object' &&
(attribute as any).type === 'customField' &&
typeof (attribute as any).customField === 'string'
);
};
/**
* Creates a Zod schema for a collection of Strapi attributes.
*
* @param attributes - An array of tuples, where each tuple contains the attribute name and its schema definition.
* @returns A Zod object schema representing the combined attributes.
*
* @example
* ```typescript
* const myAttributes = [
* ['title', { type: 'string', required: true }],
* ['description', { type: 'text' }],
* ];
* const schema = createAttributesSchema(myAttributes);
* // schema will be a Zod object with 'title' and 'description' fields,
* // each mapped to their respective Zod schemas based on their Strapi attribute types.
* ```
*/
export const createAttributesSchema = (
strapi: Core.Strapi,
attributes: [name: string, attribute: Schema.Attribute.AnyAttribute][]
) => {
return attributes.reduce((acc, [name, attribute]) => {
return acc.extend({
get [name]() {
return mapAttributeToSchema(strapi, attribute);
},
});
}, z.object({}));
};
/**
* Creates a Zod input schema for a collection of Strapi attributes.
* This is typically used for validating incoming data (e.g., from API requests).
*
* @param attributes - An array of tuples, where each tuple contains the attribute name and its schema definition.
* @returns A Zod object schema representing the combined input attributes.
*
* @example
* ```typescript
* const myInputAttributes = [
* ['email', { type: 'email', required: true }],
* ['description', { type: 'text', minLength: 8 }],
* ];
* const inputSchema = createAttributesInputSchema(myInputAttributes);
* // inputSchema will be a Zod object with 'email' and 'description' fields,
* // mapped to Zod schemas suitable for input validation.
* ```
*/
export const createAttributesInputSchema = (
strapi: Core.Strapi,
attributes: [name: string, attribute: Schema.Attribute.AnyAttribute][]
) => {
return attributes.reduce((acc, [name, attribute]) => {
return acc.extend({
get [name]() {
return mapAttributeToInputSchema(strapi, attribute);
},
});
}, z.object({}));
};
/**
* Maps a Strapi attribute definition to a corresponding Zod validation schema.
*
* This function handles every Strapi attribute types and converts them into
* appropriate Zod validation schemas.
*
* @param attribute - The Strapi attribute configuration object.
* @returns A Zod schema that corresponds to the input attribute's type.
* @throws {Error} Throws an error if an unsupported attribute type is provided.
*
* @example
* ```typescript
* const stringAttribute = { type: 'string', minLength: 3 };
* const stringSchema = mapAttributeToSchema(stringAttribute); // Returns a Zod string schema with minLength.
*
* const booleanAttribute = { type: 'boolean', default: false };
* const booleanSchema = mapAttributeToSchema(booleanAttribute); // Returns a Zod boolean schema with a default.
* ```
*/
export const mapAttributeToSchema = (
strapi: Core.Strapi,
attribute: Schema.Attribute.AnyAttribute
): z.ZodTypeAny => {
switch (attribute.type) {
case 'biginteger':
return attributes.bigIntegerToSchema(attribute);
case 'blocks':
return attributes.blocksToSchema();
case 'boolean':
return attributes.booleanToSchema(attribute);
case 'component':
return attributes.componentToSchema(strapi, attribute);
case 'date':
return attributes.dateToSchema(attribute);
case 'datetime':
return attributes.datetimeToSchema(attribute);
case 'decimal':
return attributes.decimalToSchema(attribute);
case 'dynamiczone':
return attributes.dynamicZoneToSchema(attribute);
case 'email':
return attributes.emailToSchema(attribute);
case 'enumeration':
return attributes.enumToSchema(attribute);
case 'float':
return attributes.floatToSchema(attribute);
case 'integer':
return attributes.integerToSchema(attribute);
case 'json':
return attributes.jsonToSchema(attribute);
case 'media':
return attributes.mediaToSchema(strapi, attribute);
case 'relation':
return attributes.relationToSchema(strapi, attribute);
case 'password':
case 'text':
case 'richtext':
case 'string':
return attributes.stringToSchema(attribute);
case 'time':
return attributes.timeToSchema(attribute);
case 'timestamp':
return attributes.timestampToSchema(attribute);
case 'uid':
return attributes.uidToSchema(attribute);
default: {
if (isCustomFieldAttribute(attribute)) {
const attrCF = attribute as { type: 'customField'; customField: string };
const customField = strapi.get('custom-fields').get(attrCF.customField);
if (!customField) {
throw new Error(`Custom field '${attrCF.customField}' not found`);
}
// Re-dispatch with the resolved underlying Strapi kind
return mapAttributeToSchema(strapi, { ...attrCF, type: customField.type });
}
const { type } = attribute as Schema.Attribute.AnyAttribute;
throw new Error(`Unsupported attribute type: ${type}`);
}
}
};
/**
* Maps a Strapi attribute definition to a corresponding Zod input validation schema.
*
* This function handles every Strapi attribute types and converts them into
* appropriate Zod validation schemas with their respective constraints.
*
* @param attribute - The Strapi attribute configuration object. Contains type information
* and validation rules for the attribute.
*
* @returns A Zod schema that corresponds to the input attribute's type and validation rules
*
* @example
* ```typescript
* // String attribute with constraints
* const stringAttribute = {
* type: 'string',
* minLength: 3,
* maxLength: 50,
* required: true
* };
* const stringSchema = mapAttributeToInputSchema(stringAttribute);
*
* // Enumeration attribute
* const enumAttribute = {
* type: 'enumeration',
* enum: ['draft', 'published', 'archived']
* };
* const enumSchema = mapAttributeToInputSchema(enumAttribute);
*
* // Media attribute with multiple files
* const mediaAttribute = {
* type: 'media',
* multiple: true
* };
* const mediaSchema = mapAttributeToInputSchema(mediaAttribute);
* ```
*
* @throws {Error} Throws an error if an unsupported attribute type is provided
*
*/
export const mapAttributeToInputSchema = (
strapi: Core.Strapi,
attribute: Schema.Attribute.AnyAttribute
): z.ZodTypeAny => {
switch (attribute.type) {
case 'biginteger':
return attributes.bigIntegerToInputSchema(attribute);
case 'blocks':
return attributes.blocksToInputSchema();
case 'boolean':
return attributes.booleanToInputSchema(attribute);
case 'component':
return attributes.componentToInputSchema(attribute);
case 'date':
return attributes.dateToInputSchema(attribute);
case 'datetime':
return attributes.datetimeToInputSchema(attribute);
case 'decimal':
return attributes.decimalToInputSchema(attribute);
case 'dynamiczone':
return attributes.dynamicZoneToInputSchema(attribute);
case 'email':
return attributes.emailToInputSchema(attribute);
case 'enumeration':
return attributes.enumerationToInputSchema(attribute);
case 'float':
return attributes.floatToInputSchema(attribute);
case 'integer':
return attributes.integerToInputSchema(attribute);
case 'json':
return attributes.jsonToInputSchema(attribute);
case 'media':
return attributes.mediaToInputSchema(attribute);
case 'relation':
return attributes.relationToInputSchema(attribute);
case 'password':
case 'text':
case 'richtext':
case 'string':
return attributes.textToInputSchema(attribute);
case 'time':
return attributes.timeToInputSchema(attribute);
case 'timestamp':
return attributes.timestampToInputSchema(attribute);
case 'uid':
return attributes.uidToInputSchema(attribute);
default: {
if (isCustomFieldAttribute(attribute)) {
const attrCF = attribute as { type: 'customField'; customField: string };
const customField = strapi.get('custom-fields').get(attrCF.customField);
if (!customField) {
throw new Error(`Custom field '${attrCF.customField}' not found`);
}
// Re-dispatch with the resolved underlying Strapi kind
return mapAttributeToInputSchema(strapi, { ...attrCF, type: customField.type });
}
const { type } = attribute as Schema.Attribute.AnyAttribute;
throw new Error(`Unsupported attribute type: ${type}`);
}
}
};
File diff suppressed because it is too large. Load diff