UNPKG

@sanity/schema

Version:
200 lines (175 loc) • 6.27 kB
import {flatten, isPlainObject} from 'lodash' import {type SchemaValidationResult} from '../../typedefs' import {error, HELP_IDS} from '../createValidationResult' import {getDupes} from '../utils/getDupes' function normalizeToProp(typeDef: any) { if (Array.isArray(typeDef.to)) { return typeDef.to } return typeDef.to ? [typeDef.to] : typeDef.to } export function isValidResourceType(resourceType: string): string | true { if (!resourceType) { return 'The resource type must be a non-empty string' } if (resourceType != 'media-library' && resourceType != 'dataset') { return 'The resource type must be either "media-library" or "dataset"' } return true } export function isValidResourceId(resourceType: string, resourceId: string): string | true { if (!resourceId) { return 'The resource ID must be a non-empty string' } if (resourceType === 'dataset') { const parts = resourceId.split('.') if (parts.length !== 2) { return 'The resource ID for a dataset reference must be on the form "<projectId>.<datasetName>"' } return true } if (resourceType === 'media-library') { return true } return `Cannot validate resource ID for resource type: ${resourceType}` } export default (typeDef: any, visitorContext: any) => { const isValidTo = Array.isArray(typeDef.to) || isPlainObject(typeDef.to) const normalizedTo = normalizeToProp(typeDef) const problems = flatten([ isValidTo ? getDupes(normalizedTo, (t) => `${t.name};${t.type}`).map((dupes) => error( `Found ${dupes.length} members with same type, but not unique names "${dupes[0].type}" in reference. This makes it impossible to tell their values apart and you should consider naming them`, HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ) : error( 'The global document reference type is missing or having an invalid value for the required "to" property. It should be an array of accepted types.', HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ]) if (isValidTo && normalizedTo.length === 0) { problems.push( error( 'The global document reference type should define at least one referenced type. Please check the "to" property.', HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ) } normalizedTo.forEach((crossDatasetTypeDef: any, index: any) => { if (!crossDatasetTypeDef.type) { problems.push( error( `The referenced type at index ${index} must be named. Specify the name of the type you want to create references to.`, HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ) } if (!isPlainObject(crossDatasetTypeDef.preview)) { problems.push( error( `Missing required preview config for the referenced type "${ crossDatasetTypeDef.type || '<unknown type>' }"`, HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ) } }) if (typeof typeDef.resourceType === 'string') { const validation = isValidResourceType(typeDef.resourceType) if (validation !== true) { problems.push(error(validation, HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID)) } } else { problems.push( error( 'A global document reference must specify a `resourceType`', HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ) } if (typeof typeDef.resourceId === 'string') { const datasetValidation = isValidResourceId(typeDef.resourceType, typeDef.resourceId) if (datasetValidation !== true) { problems.push(error(datasetValidation, HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID)) } } else { problems.push( error( 'A global document reference must specify a `resourceId`', HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ) } if ( typeDef.studioUrl && typeof typeDef.studioUrl !== 'function' && typeof typeDef.studioUrl !== 'string' ) { problems.push( error( 'The "studioUrl" property on a global document reference must either be a function taking "{id, type}" as argument and returning a studio url, or a string being the base url pointing to a studio.', HELP_IDS.GLOBAL_DOCUMENT_REFERENCE_INVALID, ), ) } problems.push(...getOptionErrors(typeDef)) return { ...typeDef, _problems: problems, } } function getOptionErrors(typeDef: any): SchemaValidationResult[] { const {options} = typeDef const problems = [] as SchemaValidationResult[] problems.push( ...['filter', 'filterParams'] .filter((key) => key in typeDef) .map((key) => error( `\`${key}\` is not allowed on a reference type definition - did you mean \`options.${key}\`?`, HELP_IDS.REFERENCE_INVALID_OPTIONS_LOCATION, ), ), ) if (!options) { return problems } if (!isPlainObject(options)) { return problems.concat( error( 'The reference type expects `options` to be an object', HELP_IDS.REFERENCE_INVALID_OPTIONS, ), ) } if (typeof options.filter === 'function' && typeof options.filterParams !== 'undefined') { return problems.concat( error( '`filterParams` cannot be used if `filter` is a function. Either statically define `filter` as a string, or return `params` from the `filter`-function.', HELP_IDS.REFERENCE_INVALID_FILTER_PARAMS_COMBINATION, ), ) } if (typeof options.filter === 'function' || (!options.filter && !options.filterParams)) { return problems } if (typeof options.filter !== 'string') { return problems.concat( error(`If set, \`filter\` must be a string. Got ${typeof options.filter}`), ) } if (typeof options.filterParams !== 'undefined' && !isPlainObject(options.filterParams)) { return problems.concat(error(`If set, \`filterParams\` must be an object.`)) } if (options.filterParams) { return problems.concat( Object.keys(options.filterParams) .filter((key) => key.startsWith('__') || key.startsWith('$')) .map((key) => error(`Filter parameter cannot be prefixed with "$" or "__". Got ${key}".`)), ) } return problems }