UNPKG

@adguard/agtree

Version:
130 lines (127 loc) 5.08 kB
/* * AGTree v3.2.2 (build date: Tue, 08 Jul 2025 13:39:47 GMT) * (c) 2025 Adguard Software Ltd. * Released under the MIT license * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme */ import zod from 'zod'; import { zodToCamelCase } from '../utils/zod-camelcase.js'; import { baseCompatibilityDataSchema, nonEmptyStringSchema, booleanSchema, baseRefineLogic } from './base.js'; import { getErrorMessage } from '../../utils/error.js'; import { EMPTY } from '../../utils/constants.js'; /** * @file Schema for modifier data. */ /** * Known validators that don't need to be validated as regex. */ const KNOWN_VALIDATORS = new Set([ 'csp_value', 'domain', 'permissions_value', 'pipe_separated_apps', 'pipe_separated_denyallow_domains', 'pipe_separated_domains', 'pipe_separated_methods', 'pipe_separated_stealth_options', 'regexp', 'url', ]); /** * Zod schema for modifier data. */ zodToCamelCase(baseCompatibilityDataSchema.extend({ /** * List of modifiers that are incompatible with the actual one. */ conflicts: zod.array(nonEmptyStringSchema).nullable().default(null), /** * The actual modifier is incompatible with all other modifiers, except the ones listed in `conflicts`. */ inverse_conflicts: booleanSchema.default(false), /** * Describes whether the actual modifier supports value assignment. For example, `$domain` is assignable, * so it can be used like this: `$domain=domain.com\|~subdomain.domain.com`, where `=` is the assignment operator * and `domain.com\|~subdomain.domain.com` is the value. */ assignable: booleanSchema.default(false), /** * Describes whether the actual modifier can be negated. For example, `$third-party` is negatable, * so it can be used like this: `$~third-party`. */ negatable: booleanSchema.default(true), /** * The actual modifier can only be used in blocking rules, it cannot be used in exceptions. * If it's value is `true`, then the modifier can be used only in blocking rules. * `exception_only` and `block_only` cannot be used together (they are mutually exclusive). */ block_only: booleanSchema.default(false), /** * The actual modifier can only be used in exceptions, it cannot be used in blocking rules. * If it's value is `true`, then the modifier can be used only in exceptions. * `exception_only` and `block_only` cannot be used together (they are mutually exclusive). */ exception_only: booleanSchema.default(false), /** * Describes whether the *assignable* modifier value is required. * For example, `$cookie` is assignable but it can be used without a value in exception rules: * `@@\|\|example.com^$cookie`. * If `false`, the `value_format` is required, e.g. the value of `$app` should always be specified */ value_optional: booleanSchema.default(false), /** * Describes the format of the value for the *assignable* modifier. * Its value can be a regex pattern or a known validator name (e.g. `domain`, `pipe_separated_domains`, etc.). */ value_format: nonEmptyStringSchema.nullable().default(null), /** * Describes the flags for the `value_format` regex pattern. */ value_format_flags: nonEmptyStringSchema.nullable().default(null), }).superRefine((data, ctx) => { // TODO: find something better, for now we can't add refine logic to the base schema: // https://github.com/colinhacks/zod/issues/454#issuecomment-848370721 baseRefineLogic(data, ctx); if (data.block_only && data.exception_only) { ctx.addIssue({ code: zod.ZodIssueCode.custom, message: 'block_only and exception_only are mutually exclusive', }); } if (data.assignable && !data.value_format) { ctx.addIssue({ code: zod.ZodIssueCode.custom, message: 'value_format is required for assignable modifiers', }); } if (data.value_format) { const valueFormat = data.value_format.trim(); // if it is a known validator, we don't need to validate it further if (KNOWN_VALIDATORS.has(valueFormat)) { if (data.value_format_flags) { ctx.addIssue({ code: zod.ZodIssueCode.custom, message: 'value_format_flags are not allowed for known validators', }); } return; } // otherwise, we need to validate it as a regex try { new RegExp(valueFormat, data.value_format_flags ?? EMPTY); } catch (error) { ctx.addIssue({ code: zod.ZodIssueCode.custom, message: getErrorMessage(error), }); } } else if (data.value_format_flags) { ctx.addIssue({ code: zod.ZodIssueCode.custom, message: 'value_format is required for value_format_flags', }); } })); export { KNOWN_VALIDATORS };