UNPKG

@mapbox/mapbox-gl-style-spec

Version:

a specification for mapbox gl styles

121 lines (108 loc) 4.36 kB
import ValidationError from '../error/validation_error'; import validateExpression from './validate_expression'; import validateEnum from './validate_enum'; import {getType, isString, isNumber, isBoolean} from '../util/get_type'; import {unbundle, deepUnbundle} from '../util/unbundle_jsonlint'; import {isExpressionFilter} from '../feature_filter/index'; import type {StyleReference} from '../reference/latest'; import type {StyleSpecification} from '../types'; type FilterValidatorOptions = { key: string; value: unknown; style: Partial<StyleSpecification>; styleSpec: StyleReference; layerType?: string; object?: { type?: string, id?: string } }; export default function validateFilter(options: FilterValidatorOptions): ValidationError[] { if (isExpressionFilter(deepUnbundle(options.value))) { // We default to a layerType of `fill` because that points to a non-dynamic filter definition within the style-spec. const layerType = options.layerType || 'fill'; return validateExpression(Object.assign({}, options, { expressionContext: 'filter' as const, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment valueSpec: options.styleSpec[`filter_${layerType}`] })); } else { return validateNonExpressionFilter(options); } } function validateNonExpressionFilter(options: FilterValidatorOptions): ValidationError[] { const value = options.value; const key = options.key; if (!Array.isArray(value)) { return [new ValidationError(key, value, `array expected, ${getType(value)} found`)]; } if (value.length < 1) { return [new ValidationError(key, value, 'filter array must have at least 1 element')]; } const styleSpec = options.styleSpec; let errors: ValidationError[] = validateEnum({ key: `${key}[0]`, value: value[0], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment valueSpec: styleSpec.filter_operator }); switch (unbundle(value[0])) { case '<': case '<=': case '>': // @ts-expect-error - falls through case '>=': if (value.length >= 2 && unbundle(value[1]) === '$type') { errors.push(new ValidationError(key, value, `"$type" cannot be use with operator "${value[0]}"`)); } /* falls through */ case '==': // @ts-expect-error - falls through case '!=': if (value.length !== 3) { errors.push(new ValidationError(key, value, `filter array for operator "${value[0]}" must have 3 elements`)); } /* falls through */ case 'in': case '!in': if (value.length >= 2) { if (!isString(value[1])) { errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${getType(value[1])} found`)); } } for (let i = 2; i < value.length; i++) { if (unbundle(value[1]) === '$type') { errors = errors.concat(validateEnum({ key: `${key}[${i}]`, value: value[i], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment valueSpec: styleSpec.geometry_type })); } else if (!isString(value[i]) && !isNumber(value[i]) && !isBoolean(value[i])) { errors.push(new ValidationError(`${key}[${i}]`, value[i], `string, number, or boolean expected, ${getType(value[i])} found.`)); } } break; case 'any': case 'all': case 'none': for (let i = 1; i < value.length; i++) { errors = errors.concat(validateNonExpressionFilter({ key: `${key}[${i}]`, value: value[i], style: options.style, styleSpec: options.styleSpec })); } break; case 'has': case '!has': if (value.length !== 2) { errors.push(new ValidationError(key, value, `filter array for "${value[0]}" operator must have 2 elements`)); } else if (!isString(value[1])) { errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${getType(value[1])} found`)); } break; } return errors; }