@mapbox/mapbox-gl-style-spec
Version:
a specification for mapbox gl styles
121 lines (108 loc) • 4.36 kB
text/typescript
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;
}