code:
import type {
  CodeKeywordDefinition,
  ErrorObject,
  KeywordErrorDefinition,
  SchemaObject,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {propertyInData, allSchemaProperties, isOwnProperty} from "../code"
import {alwaysValidSchema, schemaRefOrVal} from "../../compile/util"
import {_, and, not, Code, Name} from "../../compile/codegen"
import {checkMetadata} from "./metadata"
import {checkNullableObject} from "./nullable"
import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error"

enum PropError {
  Additional = "additional",
  Missing = "missing",
}

type PropKeyword = "properties" | "optionalProperties"

type PropSchema = {[P in string]?: SchemaObject}

export type JTDPropertiesError =
  | _JTDTypeError<PropKeyword, "object", PropSchema>
  | ErrorObject<PropKeyword, {error: PropError.Additional; additionalProperty: string}, PropSchema>
  | ErrorObject<PropKeyword, {error: PropError.Missing; missingProperty: string}, PropSchema>

export const error: KeywordErrorDefinition = {
  message: (cxt) => {
    const {params} = cxt
    return params.propError
      ? params.propError === PropError.Additional
        ? "must NOT have additional properties"
        : `must have property '${params.missingProperty}'`
      : typeErrorMessage(cxt, "object")
  },
  params: (cxt) => {
    const {params} = cxt
    return params.propError
      ? params.propError === PropError.Additional
        ? _`{error: ${params.propError}, additionalProperty: ${params.additionalProperty}}`
        : _`{error: ${params.propError}, missingProperty: ${params.missingProperty}}`
      : typeErrorParams(cxt, "object")
  },
}

const def: CodeKeywordDefinition = {
  keyword: "properties",
  schemaType: "object",
  error,
  code: validateProperties,
}

// const error: KeywordErrorDefinition = {
//   message: "should NOT have additional properties",
//   params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`,
// }

export function validateProperties(cxt: KeywordCxt): void {
  checkMetadata(cxt)
  const {gen, data, parentSchema, it} = cxt
  const {additionalProperties, nullable} = parentSchema
  if (it.jtdDiscriminator && nullable) throw new Error("JTD: nullable inside discriminator mapping")
  if (commonProperties()) {
    throw new Error("JTD: properties and optionalProperties have common members")
  }
  const [allProps, properties] = schemaProperties("properties")
  const [allOptProps, optProperties] = schemaProperties("optionalProperties")
  if (properties.length === 0 && optProperties.length === 0 && additionalProperties) {
    return
  }

  const [valid, cond] =
    it.jtdDiscriminator === undefined
      ? checkNullableObject(cxt, data)
      : [gen.let("valid", false), true]
  gen.if(cond, () =>
    gen.assign(valid, true).block(() => {
      validateProps(properties, "properties", true)
      validateProps(optProperties, "optionalProperties")
      if (!additionalProperties) validateAdditional()
    })
  )
  cxt.pass(valid)

  function commonProperties(): boolean {
    const props = parentSchema.properties as Record<string, any> | undefined
    const optProps = parentSchema.optionalProperties as Record<string, any> | undefined
    if (!(props && optProps)) return false
    for (const p in props) {
      if (Object.prototype.hasOwnProperty.call(optProps, p)) return true
    }
    return false
  }

  function schemaProperties(keyword: string): [string[], string[]] {
    const schema = parentSchema[keyword]
    const allPs = schema ? allSchemaProperties(schema) : []
    if (it.jtdDiscriminator && allPs.some((p) => p === it.jtdDiscriminator)) {
      throw new Error(`JTD: discriminator tag used in ${keyword}`)
    }
    const ps = allPs.filter((p) => !alwaysValidSchema(it, schema[p]))
    return [allPs, ps]
  }

  function validateProps(props: string[], keyword: string, required?: boolean): void {
    const _valid = gen.var("valid")
    for (const prop of props) {
      gen.if(
        propertyInData(gen, data, prop, it.opts.ownProperties),
        () => applyPropertySchema(prop, keyword, _valid),
        () => missingProperty(prop)
      )
      cxt.ok(_valid)
    }

    function missingProperty(prop: string): void {
      if (required) {
        gen.assign(_valid, false)
        cxt.error(false, {propError: PropError.Missing, missingProperty: prop}, {schemaPath: prop})
      } else {
        gen.assign(_valid, true)
      }
    }
  }

  function applyPropertySchema(prop: string, keyword: string, _valid: Name): void {
    cxt.subschema(
      {
        keyword,
        schemaProp: prop,
        dataProp: prop,
      },
      _valid
    )
  }

  function validateAdditional(): void {
    gen.forIn("key", data, (key: Name) => {
      const addProp = isAdditional(key, allProps, "properties", it.jtdDiscriminator)
      const addOptProp = isAdditional(key, allOptProps, "optionalProperties")
      const extra =
        addProp === true ? addOptProp : addOptProp === true ? addProp : and(addProp, addOptProp)
      gen.if(extra, () => {
        if (it.opts.removeAdditional) {
          gen.code(_`delete ${data}[${key}]`)
        } else {
          cxt.error(
            false,
            {propError: PropError.Additional, additionalProperty: key},
            {instancePath: key, parentSchema: true}
          )
          if (!it.opts.allErrors) gen.break()
        }
      })
    })
  }

  function isAdditional(
    key: Name,
    props: string[],
    keyword: string,
    jtdDiscriminator?: string
  ): Code | true {
    let additional: Code | boolean
    if (props.length > 8) {
      // TODO maybe an option instead of hard-coded 8?
      const propsSchema = schemaRefOrVal(it, parentSchema[keyword], keyword)
      additional = not(isOwnProperty(gen, propsSchema as Code, key))
      if (jtdDiscriminator !== undefined) {
        additional = and(additional, _`${key} !== ${jtdDiscriminator}`)
      }
    } else if (props.length || jtdDiscriminator !== undefined) {
      const ps = jtdDiscriminator === undefined ? props : [jtdDiscriminator].concat(props)
      additional = and(...ps.map((p) => _`${key} !== ${p}`))
    } else {
      additional = true
    }
    return additional
  }
}

export default def


code:
import type {CodeKeywordDefinition, AnySchemaObject} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {compileSchema, SchemaEnv} from "../../compile"
import {_, not, nil, stringify} from "../../compile/codegen"
import MissingRefError from "../../compile/ref_error"
import N from "../../compile/names"
import {getValidate, callRef} from "../core/ref"
import {checkMetadata} from "./metadata"

const def: CodeKeywordDefinition = {
  keyword: "ref",
  schemaType: "string",
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, data, schema: ref, parentSchema, it} = cxt
    const {
      schemaEnv: {root},
    } = it
    const valid = gen.name("valid")
    if (parentSchema.nullable) {
      gen.var(valid, _`${data} === null`)
      gen.if(not(valid), validateJtdRef)
    } else {
      gen.var(valid, false)
      validateJtdRef()
    }
    cxt.ok(valid)

    function validateJtdRef(): void {
      const refSchema = (root.schema as AnySchemaObject).definitions?.[ref]
      if (!refSchema) {
        throw new MissingRefError(it.opts.uriResolver, "", ref, `No definition ${ref}`)
      }
      if (hasRef(refSchema) || !it.opts.inlineRefs) callValidate(refSchema)
      else inlineRefSchema(refSchema)
    }

    function callValidate(schema: AnySchemaObject): void {
      const sch = compileSchema.call(
        it.self,
        new SchemaEnv({schema, root, schemaPath: `/definitions/${ref}`})
      )
      const v = getValidate(cxt, sch)
      const errsCount = gen.const("_errs", N.errors)
      callRef(cxt, v, sch, sch.$async)
      gen.assign(valid, _`${errsCount} === ${N.errors}`)
    }

    function inlineRefSchema(schema: AnySchemaObject): void {
      const schName = gen.scopeValue(
        "schema",
        it.opts.code.source === true ? {ref: schema, code: stringify(schema)} : {ref: schema}
      )
      cxt.subschema(
        {
          schema,
          dataTypes: [],
          schemaPath: nil,
          topSchemaRef: schName,
          errSchemaPath: `/definitions/${ref}`,
        },
        valid
      )
    }
  },
}

export function hasRef(schema: AnySchemaObject): boolean {
  for (const key in schema) {
    let sch: AnySchemaObject
    if (key === "ref" || (typeof (sch = schema[key]) == "object" && hasRef(sch))) return true
  }
  return false
}

export default def

license: mit


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, nil, or, Code} from "../../compile/codegen"
import validTimestamp from "../../runtime/timestamp"
import {useFunc} from "../../compile/util"
import {checkMetadata} from "./metadata"
import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error"

export type JTDTypeError = _JTDTypeError<"type", JTDType, JTDType>

export type IntType = "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32"

export const intRange: {[T in IntType]: [number, number, number]} = {
  int8: [-128, 127, 3],
  uint8: [0, 255, 3],
  int16: [-32768, 32767, 5],
  uint16: [0, 65535, 5],
  int32: [-2147483648, 2147483647, 10],
  uint32: [0, 4294967295, 10],
}

export type JTDType = "boolean" | "string" | "timestamp" | "float32" | "float64" | IntType

const error: KeywordErrorDefinition = {
  message: (cxt) => typeErrorMessage(cxt, cxt.schema),
  params: (cxt) => typeErrorParams(cxt, cxt.schema),
}

function timestampCode(cxt: KeywordCxt): Code {
  const {gen, data, it} = cxt
  const {timestamp, allowDate} = it.opts
  if (timestamp === "date") return _`${data} instanceof Date `
  const vts = useFunc(gen, validTimestamp)
  const allowDateArg = allowDate ? _`, true` : nil
  const validString = _`typeof ${data} == "string" && ${vts}(${data}${allowDateArg})`
  return timestamp === "string" ? validString : or(_`${data} instanceof Date`, validString)
}

const def: CodeKeywordDefinition = {
  keyword: "type",
  schemaType: "string",
  error,
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {data, schema, parentSchema, it} = cxt
    let cond: Code
    switch (schema) {
      case "boolean":
      case "string":
        cond = _`typeof ${data} == ${schema}`
        break
      case "timestamp": {
        cond = timestampCode(cxt)
        break
      }
      case "float32":
      case "float64":
        cond = _`typeof ${data} == "number"`
        break
      default: {
        const sch = schema as IntType
        cond = _`typeof ${data} == "number" && isFinite(${data}) && !(${data} % 1)`
        if (!it.opts.int32range && (sch === "int32" || sch === "uint32")) {
          if (sch === "uint32") cond = _`${cond} && ${data} >= 0`
        } else {
          const [min, max] = intRange[sch]
          cond = _`${cond} && ${data} >= ${min} && ${data} <= ${max}`
        }
      }
    }
    cxt.pass(parentSchema.nullable ? or(_`${data} === null`, cond) : cond)
  },
}

export default def


code:
import type {CodeKeywordDefinition} from "../../types"
import {validateUnion} from "../code"

const def: CodeKeywordDefinition = {
  keyword: "union",
  schemaType: "array",
  trackErrors: true,
  code: validateUnion,
  error: {message: "must match a schema in union"},
}

export default def


code:
import type {CodeKeywordDefinition, SchemaObject} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {alwaysValidSchema, Type} from "../../compile/util"
import {not, or, Name} from "../../compile/codegen"
import {checkMetadata} from "./metadata"
import {checkNullableObject} from "./nullable"
import {typeError, _JTDTypeError} from "./error"

export type JTDValuesError = _JTDTypeError<"values", "object", SchemaObject>

const def: CodeKeywordDefinition = {
  keyword: "values",
  schemaType: "object",
  error: typeError("object"),
  code(cxt: KeywordCxt) {
    checkMetadata(cxt)
    const {gen, data, schema, it} = cxt
    const [valid, cond] = checkNullableObject(cxt, data)
    if (alwaysValidSchema(it, schema)) {
      gen.if(not(or(cond, valid)), () => cxt.error())
    } else {
      gen.if(cond)
      gen.assign(valid, validateMap())
      gen.elseIf(not(valid))
      cxt.error()
      gen.endIf()
    }
    cxt.ok(valid)

    function validateMap(): Name | boolean {
      const _valid = gen.name("valid")
      if (it.allErrors) {
        const validMap = gen.let("valid", true)
        validateValues(() => gen.assign(validMap, false))
        return validMap
      }
      gen.var(_valid, true)
      validateValues(() => gen.break())
      return _valid

      function validateValues(notValid: () => void): void {
        gen.forIn("key", data, (key) => {
          cxt.subschema(
            {
              keyword: "values",
              dataProp: key,
              dataPropType: Type.Str,
            },
            _valid
          )
          gen.if(not(_valid), notValid)
        })
      }
    }
  },
}

export default def


code:
import type {Vocabulary} from "../types"

export const metadataVocabulary: Vocabulary = [
  "title",
  "description",
  "default",
  "deprecated",
  "readOnly",
  "writeOnly",
  "examples",
]

export const contentVocabulary: Vocabulary = [
  "contentMediaType",
  "contentEncoding",
  "contentSchema",
]


code:
import type {Vocabulary} from "../types"
import dependentRequired from "./validation/dependentRequired"
import dependentSchemas from "./applicator/dependentSchemas"
import limitContains from "./validation/limitContains"

const next: Vocabulary = [dependentRequired, dependentSchemas, limitContains]

export default next


code:
import type {Vocabulary} from "../../types"
import unevaluatedProperties from "./unevaluatedProperties"
import unevaluatedItems from "./unevaluatedItems"

const unevaluated: Vocabulary = [unevaluatedProperties, unevaluatedItems]

export default unevaluated


code:
import type {
  CodeKeywordDefinition,
  ErrorObject,
  KeywordErrorDefinition,
  AnySchema,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, not, Name} from "../../compile/codegen"
import {alwaysValidSchema, Type} from "../../compile/util"

export type UnevaluatedItemsError = ErrorObject<"unevaluatedItems", {limit: number}, AnySchema>

const error: KeywordErrorDefinition = {
  message: ({params: {len}}) => str`must NOT have more than ${len} items`,
  params: ({params: {len}}) => _`{limit: ${len}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "unevaluatedItems",
  type: "array",
  schemaType: ["boolean", "object"],
  error,
  code(cxt: KeywordCxt) {
    const {gen, schema, data, it} = cxt
    const items = it.items || 0
    if (items === true) return
    const len = gen.const("len", _`${data}.length`)
    if (schema === false) {
      cxt.setParams({len: items})
      cxt.fail(_`${len} > ${items}`)
    } else if (typeof schema == "object" && !alwaysValidSchema(it, schema)) {
      const valid = gen.var("valid", _`${len} <= ${items}`)
      gen.if(not(valid), () => validateItems(valid, items))
      cxt.ok(valid)
    }
    it.items = true

    function validateItems(valid: Name, from: Name | number): void {
      gen.forRange("i", from, len, (i) => {
        cxt.subschema({keyword: "unevaluatedItems", dataProp: i, dataPropType: Type.Num}, valid)
        if (!it.allErrors) gen.if(not(valid), () => gen.break())
      })
    }
  },
}

export default def


code:
import type {
  CodeKeywordDefinition,
  KeywordErrorDefinition,
  ErrorObject,
  AnySchema,
} from "../../types"
import {_, not, and, Name, Code} from "../../compile/codegen"
import {alwaysValidSchema, Type} from "../../compile/util"
import N from "../../compile/names"

export type UnevaluatedPropertiesError = ErrorObject<
  "unevaluatedProperties",
  {unevaluatedProperty: string},
  AnySchema
>

const error: KeywordErrorDefinition = {
  message: "must NOT have unevaluated properties",
  params: ({params}) => _`{unevaluatedProperty: ${params.unevaluatedProperty}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "unevaluatedProperties",
  type: "object",
  schemaType: ["boolean", "object"],
  trackErrors: true,
  error,
  code(cxt) {
    const {gen, schema, data, errsCount, it} = cxt
    /* istanbul ignore if */
    if (!errsCount) throw new Error("ajv implementation error")
    const {allErrors, props} = it
    if (props instanceof Name) {
      gen.if(_`${props} !== true`, () =>
        gen.forIn("key", data, (key: Name) =>
          gen.if(unevaluatedDynamic(props, key), () => unevaluatedPropCode(key))
        )
      )
    } else if (props !== true) {
      gen.forIn("key", data, (key: Name) =>
        props === undefined
          ? unevaluatedPropCode(key)
          : gen.if(unevaluatedStatic(props, key), () => unevaluatedPropCode(key))
      )
    }
    it.props = true
    cxt.ok(_`${errsCount} === ${N.errors}`)

    function unevaluatedPropCode(key: Name): void {
      if (schema === false) {
        cxt.setParams({unevaluatedProperty: key})
        cxt.error()
        if (!allErrors) gen.break()
        return
      }

      if (!alwaysValidSchema(it, schema)) {
        const valid = gen.name("valid")
        cxt.subschema(
          {
            keyword: "unevaluatedProperties",
            dataProp: key,
            dataPropType: Type.Str,
          },
          valid
        )
        if (!allErrors) gen.if(not(valid), () => gen.break())
      }
    }

    function unevaluatedDynamic(evaluatedProps: Name, key: Name): Code {
      return _`!${evaluatedProps} || !${evaluatedProps}[${key}]`
    }

    function unevaluatedStatic(evaluatedProps: {[K in string]?: true}, key: Name): Code {
      const ps: Code[] = []
      for (const p in evaluatedProps) {
        if (evaluatedProps[p] === true) ps.push(_`${key} !== ${p}`)
      }
      return and(...ps)
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import equal from "../../runtime/equal"

export type ConstError = ErrorObject<"const", {allowedValue: any}>

const error: KeywordErrorDefinition = {
  message: "must be equal to constant",
  params: ({schemaCode}) => _`{allowedValue: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "const",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, $data, schemaCode, schema} = cxt
    if ($data || (schema && typeof schema == "object")) {
      cxt.fail$data(_`!${useFunc(gen, equal)}(${data}, ${schemaCode})`)
    } else {
      cxt.fail(_`${schema} !== ${data}`)
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject} from "../../types"
import {
  validatePropertyDeps,
  error,
  DependenciesErrorParams,
  PropertyDependencies,
} from "../applicator/dependencies"

export type DependentRequiredError = ErrorObject<
  "dependentRequired",
  DependenciesErrorParams,
  PropertyDependencies
>

const def: CodeKeywordDefinition = {
  keyword: "dependentRequired",
  type: "object",
  schemaType: "object",
  error,
  code: (cxt) => validatePropertyDeps(cxt),
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, or, Name, Code} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import equal from "../../runtime/equal"

export type EnumError = ErrorObject<"enum", {allowedValues: any[]}, any[] | {$data: string}>

const error: KeywordErrorDefinition = {
  message: "must be equal to one of the allowed values",
  params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "enum",
  schemaType: "array",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, $data, schema, schemaCode, it} = cxt
    if (!$data && schema.length === 0) throw new Error("enum must have non-empty array")
    const useLoop = schema.length >= it.opts.loopEnum
    let eql: Name | undefined
    const getEql = (): Name => (eql ??= useFunc(gen, equal))

    let valid: Code
    if (useLoop || $data) {
      valid = gen.let("valid")
      cxt.block$data(valid, loopEnum)
    } else {
      /* istanbul ignore if */
      if (!Array.isArray(schema)) throw new Error("ajv implementation error")
      const vSchema = gen.const("vSchema", schemaCode)
      valid = or(...schema.map((_x: unknown, i: number) => equalCode(vSchema, i)))
    }
    cxt.pass(valid)

    function loopEnum(): void {
      gen.assign(valid, false)
      gen.forOf("v", schemaCode as Code, (v) =>
        gen.if(_`${getEql()}(${data}, ${v})`, () => gen.assign(valid, true).break())
      )
    }

    function equalCode(vSchema: Name, i: number): Code {
      const sch = schema[i]
      return typeof sch === "object" && sch !== null
        ? _`${getEql()}(${data}, ${vSchema}[${i}])`
        : _`${data} === ${sch}`
    }
  },
}

export default def


code:
import type {ErrorObject, Vocabulary} from "../../types"
import limitNumber, {LimitNumberError} from "./limitNumber"
import multipleOf, {MultipleOfError} from "./multipleOf"
import limitLength from "./limitLength"
import pattern, {PatternError} from "./pattern"
import limitProperties from "./limitProperties"
import required, {RequiredError} from "./required"
import limitItems from "./limitItems"
import uniqueItems, {UniqueItemsError} from "./uniqueItems"
import constKeyword, {ConstError} from "./const"
import enumKeyword, {EnumError} from "./enum"

const validation: Vocabulary = [
  // number
  limitNumber,
  multipleOf,
  // string
  limitLength,
  pattern,
  // object
  limitProperties,
  required,
  // array
  limitItems,
  uniqueItems,
  // any
  {keyword: "type", schemaType: ["string", "array"]},
  {keyword: "nullable", schemaType: "boolean"},
  constKeyword,
  enumKeyword,
]

export default validation

type LimitError = ErrorObject<
  "maxItems" | "minItems" | "minProperties" | "maxProperties" | "minLength" | "maxLength",
  {limit: number},
  number | {$data: string}
>

export type ValidationKeywordError =
  | LimitError
  | LimitNumberError
  | MultipleOfError
  | PatternError
  | RequiredError
  | UniqueItemsError
  | ConstError
  | EnumError


code:
import type {CodeKeywordDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {checkStrictMode} from "../../compile/util"

const def: CodeKeywordDefinition = {
  keyword: ["maxContains", "minContains"],
  type: "array",
  schemaType: "number",
  code({keyword, parentSchema, it}: KeywordCxt) {
    if (parentSchema.contains === undefined) {
      checkStrictMode(it, `"${keyword}" without "contains" is ignored`)
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators} from "../../compile/codegen"

const error: KeywordErrorDefinition = {
  message({keyword, schemaCode}) {
    const comp = keyword === "maxItems" ? "more" : "fewer"
    return str`must NOT have ${comp} than ${schemaCode} items`
  },
  params: ({schemaCode}) => _`{limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: ["maxItems", "minItems"],
  type: "array",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode} = cxt
    const op = keyword === "maxItems" ? operators.GT : operators.LT
    cxt.fail$data(_`${data}.length ${op} ${schemaCode}`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import ucs2length from "../../runtime/ucs2length"

const error: KeywordErrorDefinition = {
  message({keyword, schemaCode}) {
    const comp = keyword === "maxLength" ? "more" : "fewer"
    return str`must NOT have ${comp} than ${schemaCode} characters`
  },
  params: ({schemaCode}) => _`{limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: ["maxLength", "minLength"],
  type: "string",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode, it} = cxt
    const op = keyword === "maxLength" ? operators.GT : operators.LT
    const len =
      it.opts.unicode === false ? _`${data}.length` : _`${useFunc(cxt.gen, ucs2length)}(${data})`
    cxt.fail$data(_`${len} ${op} ${schemaCode}`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators, Code} from "../../compile/codegen"

const ops = operators

type Kwd = "maximum" | "minimum" | "exclusiveMaximum" | "exclusiveMinimum"

type Comparison = "<=" | ">=" | "<" | ">"

const KWDs: {[K in Kwd]: {okStr: Comparison; ok: Code; fail: Code}} = {
  maximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT},
  minimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT},
  exclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE},
  exclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE},
}

export type LimitNumberError = ErrorObject<
  Kwd,
  {limit: number; comparison: Comparison},
  number | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({keyword, schemaCode}) => str`must be ${KWDs[keyword as Kwd].okStr} ${schemaCode}`,
  params: ({keyword, schemaCode}) =>
    _`{comparison: ${KWDs[keyword as Kwd].okStr}, limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: Object.keys(KWDs),
  type: "number",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode} = cxt
    cxt.fail$data(_`${data} ${KWDs[keyword as Kwd].fail} ${schemaCode} || isNaN(${data})`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str, operators} from "../../compile/codegen"

const error: KeywordErrorDefinition = {
  message({keyword, schemaCode}) {
    const comp = keyword === "maxProperties" ? "more" : "fewer"
    return str`must NOT have ${comp} than ${schemaCode} properties`
  },
  params: ({schemaCode}) => _`{limit: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: ["maxProperties", "minProperties"],
  type: "object",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {keyword, data, schemaCode} = cxt
    const op = keyword === "maxProperties" ? operators.GT : operators.LT
    cxt.fail$data(_`Object.keys(${data}).length ${op} ${schemaCode}`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str} from "../../compile/codegen"

export type MultipleOfError = ErrorObject<
  "multipleOf",
  {multipleOf: number},
  number | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({schemaCode}) => str`must be multiple of ${schemaCode}`,
  params: ({schemaCode}) => _`{multipleOf: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "multipleOf",
  type: "number",
  schemaType: "number",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, schemaCode, it} = cxt
    // const bdt = bad$DataType(schemaCode, <string>def.schemaType, $data)
    const prec = it.opts.multipleOfPrecision
    const res = gen.let("res")
    const invalid = prec
      ? _`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}`
      : _`${res} !== parseInt(${res})`
    cxt.fail$data(_`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid}))`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {usePattern} from "../code"
import {_, str} from "../../compile/codegen"

export type PatternError = ErrorObject<"pattern", {pattern: string}, string | {$data: string}>

const error: KeywordErrorDefinition = {
  message: ({schemaCode}) => str`must match pattern "${schemaCode}"`,
  params: ({schemaCode}) => _`{pattern: ${schemaCode}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "pattern",
  type: "string",
  schemaType: "string",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {data, $data, schema, schemaCode, it} = cxt
    // TODO regexp should be wrapped in try/catchs
    const u = it.opts.unicodeRegExp ? "u" : ""
    const regExp = $data ? _`(new RegExp(${schemaCode}, ${u}))` : usePattern(cxt, schema)
    cxt.fail$data(_`!${regExp}.test(${data})`)
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {
  checkReportMissingProp,
  checkMissingProp,
  reportMissingProp,
  propertyInData,
  noPropertyInData,
} from "../code"
import {_, str, nil, not, Name, Code} from "../../compile/codegen"
import {checkStrictMode} from "../../compile/util"

export type RequiredError = ErrorObject<
  "required",
  {missingProperty: string},
  string[] | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({params: {missingProperty}}) => str`must have required property '${missingProperty}'`,
  params: ({params: {missingProperty}}) => _`{missingProperty: ${missingProperty}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "required",
  type: "object",
  schemaType: "array",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, schema, schemaCode, data, $data, it} = cxt
    const {opts} = it
    if (!$data && schema.length === 0) return
    const useLoop = schema.length >= opts.loopRequired
    if (it.allErrors) allErrorsMode()
    else exitOnErrorMode()

    if (opts.strictRequired) {
      const props = cxt.parentSchema.properties
      const {definedProperties} = cxt.it
      for (const requiredKey of schema) {
        if (props?.[requiredKey] === undefined && !definedProperties.has(requiredKey)) {
          const schemaPath = it.schemaEnv.baseId + it.errSchemaPath
          const msg = `required property "${requiredKey}" is not defined at "${schemaPath}" (strictRequired)`
          checkStrictMode(it, msg, it.opts.strictRequired)
        }
      }
    }

    function allErrorsMode(): void {
      if (useLoop || $data) {
        cxt.block$data(nil, loopAllRequired)
      } else {
        for (const prop of schema) {
          checkReportMissingProp(cxt, prop)
        }
      }
    }

    function exitOnErrorMode(): void {
      const missing = gen.let("missing")
      if (useLoop || $data) {
        const valid = gen.let("valid", true)
        cxt.block$data(valid, () => loopUntilMissing(missing, valid))
        cxt.ok(valid)
      } else {
        gen.if(checkMissingProp(cxt, schema, missing))
        reportMissingProp(cxt, missing)
        gen.else()
      }
    }

    function loopAllRequired(): void {
      gen.forOf("prop", schemaCode as Code, (prop) => {
        cxt.setParams({missingProperty: prop})
        gen.if(noPropertyInData(gen, data, prop, opts.ownProperties), () => cxt.error())
      })
    }

    function loopUntilMissing(missing: Name, valid: Name): void {
      cxt.setParams({missingProperty: missing})
      gen.forOf(
        missing,
        schemaCode as Code,
        () => {
          gen.assign(valid, propertyInData(gen, data, missing, opts.ownProperties))
          gen.if(not(valid), () => {
            cxt.error()
            gen.break()
          })
        },
        nil
      )
    }
  },
}

export default def


code:
import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {checkDataTypes, getSchemaTypes, DataType} from "../../compile/validate/dataType"
import {_, str, Name} from "../../compile/codegen"
import {useFunc} from "../../compile/util"
import equal from "../../runtime/equal"

export type UniqueItemsError = ErrorObject<
  "uniqueItems",
  {i: number; j: number},
  boolean | {$data: string}
>

const error: KeywordErrorDefinition = {
  message: ({params: {i, j}}) =>
    str`must NOT have duplicate items (items ## ${j} and ${i} are identical)`,
  params: ({params: {i, j}}) => _`{i: ${i}, j: ${j}}`,
}

const def: CodeKeywordDefinition = {
  keyword: "uniqueItems",
  type: "array",
  schemaType: "boolean",
  $data: true,
  error,
  code(cxt: KeywordCxt) {
    const {gen, data, $data, schema, parentSchema, schemaCode, it} = cxt
    if (!$data && !schema) return
    const valid = gen.let("valid")
    const itemTypes = parentSchema.items ? getSchemaTypes(parentSchema.items) : []
    cxt.block$data(valid, validateUniqueItems, _`${schemaCode} === false`)
    cxt.ok(valid)

    function validateUniqueItems(): void {
      const i = gen.let("i", _`${data}.length`)
      const j = gen.let("j")
      cxt.setParams({i, j})
      gen.assign(valid, true)
      gen.if(_`${i} > 1`, () => (canOptimize() ? loopN : loopN2)(i, j))
    }

    function canOptimize(): boolean {
      return itemTypes.length > 0 && !itemTypes.some((t) => t === "object" || t === "array")
    }

    function loopN(i: Name, j: Name): void {
      const item = gen.name("item")
      const wrongType = checkDataTypes(itemTypes, item, it.opts.strictNumbers, DataType.Wrong)
      const indices = gen.const("indices", _`{}`)
      gen.for(_`;${i}--;`, () => {
        gen.let(item, _`${data}[${i}]`)
        gen.if(wrongType, _`continue`)
        if (itemTypes.length > 1) gen.if(_`typeof ${item} == "string"`, _`${item} += "_"`)
        gen
          .if(_`typeof ${indices}[${item}] == "number"`, () => {
            gen.assign(j, _`${indices}[${item}]`)
            cxt.error()
            gen.assign(valid, false).break()
          })
          .code(_`${indices}[${item}] = ${i}`)
      })
    }

    function loopN2(i: Name, j: Name): void {
      const eql = useFunc(gen, equal)
      const outer = gen.name("outer")
      gen.label(outer).for(_`;${i}--;`, () =>
        gen.for(_`${j} = ${i}; ${j}--;`, () =>
          gen.if(_`${eql}(${data}[${i}], ${data}[${j}])`, () => {
            cxt.error()
            gen.assign(valid, false).break(outer)
          })
        )
      )
    }
  },
}

export default def


ajv

description: Another JSON Schema Validator

files: lib/ dist/ .runkit_example.js

eslint: eslint "lib/**/*.ts" "spec/**/*.*s" --ignore-pattern spec/JSON-Schema-Test-Suite

test-spec: cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register "spec/**/*.spec.{ts,js}" -R dot

test-codegen: nyc cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register 'spec/codegen.spec.ts' -R spec

test-debug: npm run test-spec -- --inspect-brk

test-cov: nyc npm run test-spec

rollup: rm -rf bundle && rollup -c

bundle: rm -rf bundle && node ./scripts/bundle.js ajv ajv7 ajv7 && node ./scripts/bundle.js 2019 ajv2019 ajv2019 && node ./scripts/bundle.js 2020 ajv2020 ajv2020 && node ./scripts/bundle.js jtd ajvJTD ajvJTD

build: rm -rf dist && tsc && cp -r lib/refs dist && rm dist/refs/json-schema-2019-09/index.ts && rm dist/refs/json-schema-2020-12/index.ts && rm dist/refs/jtd-schema.ts

json-tests: rm -rf spec/_json/*.js && node scripts/jsontests

test-karma: karma start

test-browser: rm -rf .browser && npm run bundle && scripts/prepare-tests && karma start

test-all: npm run test-cov && if-node-version 12 npm run test-browser

test: npm run json-tests && npm run prettier:check && npm run eslint && npm link && npm link --legacy-peer-deps ajv && npm run test-cov

test-ci: AJV_FULL_TEST=true npm test

prepublish: npm run build

benchmark: npm i && npm run build && npm link && cd ./benchmark && npm link --legacy-peer-deps ajv && npm i && node ./jtd

docs:dev: ./scripts/prepare-site && vuepress dev docs

docs:build: ./scripts/prepare-site && vuepress build docs

reporter: lcov text-summary

repository: ajv-validator/ajv

keywords: JSON schema validator validation jsonschema json-schema json-schema-validator json-schema-validation

author: Evgeny Poberezkin

license: MIT

type: opencollective

type: github

pre-commit: lint-staged && npm test

*.{json,yaml,js,ts}: prettier --write


code:
extends: eslint:recommended
env:
  node: true
  browser: true
rules:
  block-scoped-var: 2
  complexity: [2, 15]
  curly: [2, multi-or-nest, consistent]
  dot-location: [2, property]
  dot-notation: 2
  indent: [2, 2, SwitchCase: 1]
  linebreak-style: [2, unix]
  new-cap: 2
  no-console: [2, allow: [warn, error]]
  no-else-return: 2
  no-eq-null: 2
  no-fallthrough: 2
  no-invalid-this: 2
  no-return-assign: 2
  no-shadow: 1
  no-trailing-spaces: 2
  no-use-before-define: [2, nofunc]
  quotes: [2, single, avoid-escape]
  semi: [2, always]
  strict: [2, global]
  valid-jsdoc: [2, requireReturn: false]
  no-control-regex: 0


code:
github: epoberezkin
tidelift: "npm/json-schema-traverse"


code:
name: build

on:
  push:
    branches: [master]
  pull_request:
    branches: ["*"]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [10.x, 12.x, 14.x]

    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install
      - run: npm test
      - name: Coveralls
        uses: coverallsapp/github-action@master
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}


code:
name: publish

on:
  release:
    types: [published]

jobs:
  publish-npm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 14
          registry-url: https://registry.npmjs.org/
      - run: npm install
      - run: npm test
      - name: Publish beta version to npm
        if: "github.event.release.prerelease"
        run: npm publish --tag beta
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
      - name: Publish to npm
        if: "!github.event.release.prerelease"
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}


code:
# json-schema-traverse
Traverse JSON Schema passing each schema object to callback

[![build](https://github.com/epoberezkin/json-schema-traverse/workflows/build/badge.svg)](https://github.com/epoberezkin/json-schema-traverse/actions?query=workflow%3Abuild)
[![npm](https://img.shields.io/npm/v/json-schema-traverse)](https://www.npmjs.com/package/json-schema-traverse)
[![coverage](https://coveralls.io/repos/github/epoberezkin/json-schema-traverse/badge.svg?branch=master)](https://coveralls.io/github/epoberezkin/json-schema-traverse?branch=master)

## Install

```
npm install json-schema-traverse
```

## Usage

```javascript
const traverse = require('json-schema-traverse');
const schema = {
  properties: {
    foo: {type: 'string'},
    bar: {type: 'integer'}
  }
};

traverse(schema, {cb});
// cb is called 3 times with:
// 1. root schema
// 2. {type: 'string'}
// 3. {type: 'integer'}

// Or:

traverse(schema, {cb: {pre, post}});
// pre is called 3 times with:
// 1. root schema
// 2. {type: 'string'}
// 3. {type: 'integer'}
//
// post is called 3 times with:
// 1. {type: 'string'}
// 2. {type: 'integer'}
// 3. root schema

```

Callback function `cb` is called for each schema object (not including draft-06 boolean schemas), including the root schema, in pre-order traversal. Schema references ($ref) are not resolved, they are passed as is.  Alternatively, you can pass a `{pre, post}` object as `cb`, and then `pre` will be called before traversing child elements, and `post` will be called after all child elements have been traversed.

Callback is passed these parameters:

- _schema_: the current schema object
- _JSON pointer_: from the root schema to the current schema object
- _root schema_: the schema passed to `traverse` object
- _parent JSON pointer_: from the root schema to the parent schema object (see below)
- _parent keyword_: the keyword inside which this schema appears (e.g. `properties`, `anyOf`, etc.)
- _parent schema_: not necessarily parent object/array; in the example above the parent schema for `{type: 'string'}` is the root schema
- _index/property_: index or property name in the array/object containing multiple schemas; in the example above for `{type: 'string'}` the property name is `'foo'`

## Traverse objects in all unknown keywords

```javascript
const traverse = require('json-schema-traverse');
const schema = {
  mySchema: {
    minimum: 1,
    maximum: 2
  }
};

traverse(schema, {allKeys: true, cb});
// cb is called 2 times with:
// 1. root schema
// 2. mySchema
```

Without option `allKeys: true` callback will be called only with root schema.

## Enterprise support

json-schema-traverse package is a part of [Tidelift enterprise subscription](https://tidelift.com/subscription/pkg/npm-json-schema-traverse?utm_source=npm-json-schema-traverse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) - it provides a centralised commercial support to open-source software users, in addition to the support provided by software maintainers.

## Security contact

To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerability via GitHub issues.

## License

[MIT](https://github.com/epoberezkin/json-schema-traverse/blob/master/LICENSE)


code:
declare function traverse(
  schema: traverse.SchemaObject,
  opts: traverse.Options,
  cb?: traverse.Callback
): void;

declare function traverse(
  schema: traverse.SchemaObject,
  cb: traverse.Callback
): void;

declare namespace traverse {
  interface SchemaObject {
    $id?: string;
    $schema?: string;
    [x: string]: any;
  }

  type Callback = (
    schema: SchemaObject,
    jsonPtr: string,
    rootSchema: SchemaObject,
    parentJsonPtr?: string,
    parentKeyword?: string,
    parentSchema?: SchemaObject,
    keyIndex?: string | number
  ) => void;

  interface Options {
    allKeys?: boolean;
    cb?:
      | Callback
      | {
          pre?: Callback;
          post?: Callback;
        };
  }
}

export = traverse;


code:
'use strict';

var traverse = module.exports = function (schema, opts, cb) {
  // Legacy support for v0.3.1 and earlier.
  if (typeof opts == 'function') {
    cb = opts;
    opts = {};
  }

  cb = opts.cb || cb;
  var pre = (typeof cb == 'function') ? cb : cb.pre || function() {};
  var post = cb.post || function() {};

  _traverse(opts, pre, post, schema, '', schema);
};

traverse.keywords = {
  additionalItems: true,
  items: true,
  contains: true,
  additionalProperties: true,
  propertyNames: true,
  not: true,
  if: true,
  then: true,
  else: true
};

traverse.arrayKeywords = {
  items: true,
  allOf: true,
  anyOf: true,
  oneOf: true
};

traverse.propsKeywords = {
  $defs: true,
  definitions: true,
  properties: true,
  patternProperties: true,
  dependencies: true
};

traverse.skipKeywords = {
  default: true,
  enum: true,
  const: true,
  required: true,
  maximum: true,
  minimum: true,
  exclusiveMaximum: true,
  exclusiveMinimum: true,
  multipleOf: true,
  maxLength: true,
  minLength: true,
  pattern: true,
  format: true,
  maxItems: true,
  minItems: true,
  uniqueItems: true,
  maxProperties: true,
  minProperties: true
};

function _traverse(opts, pre, post, schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {
  if (schema && typeof schema == 'object' && !Array.isArray(schema)) {
    pre(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
    for (var key in schema) {
      var sch = schema[key];
      if (Array.isArray(sch)) {
        if (key in traverse.arrayKeywords) {
          for (var i=0; i<sch.length; i++)
            _traverse(opts, pre, post, sch[i], jsonPtr + '/' + key + '/' + i, rootSchema, jsonPtr, key, schema, i);
        }
      } else if (key in traverse.propsKeywords) {
        if (sch && typeof sch == 'object') {
          for (var prop in sch)
            _traverse(opts, pre, post, sch[prop], jsonPtr + '/' + key + '/' + escapeJsonPtr(prop), rootSchema, jsonPtr, key, schema, prop);
        }
      } else if (key in traverse.keywords || (opts.allKeys && !(key in traverse.skipKeywords))) {
        _traverse(opts, pre, post, sch, jsonPtr + '/' + key, rootSchema, jsonPtr, key, schema);
      }
    }
    post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
  }
}

function escapeJsonPtr(str) {
  return str.replace(/~/g, '~0').replace(/\//g, '~1');
}


json-schema-traverse

description: Traverse JSON Schema passing each schema object to callback

eslint: eslint index.js spec

test-spec: mocha spec -R spec

test: npm run eslint && nyc npm run test-spec

type: git

keywords: JSON-Schema traverse iterate


code:
parserOptions:
  ecmaVersion: 6
globals:
  beforeEach: false
  describe: false
  it: false


code:
'use strict';

var schema = {
  additionalItems: subschema('additionalItems'),
  items: subschema('items'),
  contains: subschema('contains'),
  additionalProperties: subschema('additionalProperties'),
  propertyNames: subschema('propertyNames'),
  not: subschema('not'),
  allOf: [
    subschema('allOf_0'),
    subschema('allOf_1'),
    {
      items: [
        subschema('items_0'),
        subschema('items_1'),
      ]
    }
  ],
  anyOf: [
    subschema('anyOf_0'),
    subschema('anyOf_1'),
  ],
  oneOf: [
    subschema('oneOf_0'),
    subschema('oneOf_1'),
  ],
  definitions: {
    foo: subschema('definitions_foo'),
    bar: subschema('definitions_bar'),
  },
  properties: {
    foo: subschema('properties_foo'),
    bar: subschema('properties_bar'),
  },
  patternProperties: {
    foo: subschema('patternProperties_foo'),
    bar: subschema('patternProperties_bar'),
  },
  dependencies: {
    foo: subschema('dependencies_foo'),
    bar: subschema('dependencies_bar'),
  },
  required: ['foo', 'bar']
};

function subschema(keyword) {
  var sch = {
    properties: {},
    additionalProperties: false,
    additionalItems: false,
    anyOf: [
      {format: 'email'},
      {format: 'hostname'}
    ]
  };
  sch.properties['foo_' + keyword] = {title: 'foo'};
  sch.properties['bar_' + keyword] = {title: 'bar'};
  return sch;
}

module.exports = {
  schema: schema,

  // schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
  expectedCalls: [[schema, '', schema, undefined, undefined, undefined, undefined]]
    .concat(expectedCalls('additionalItems'))
    .concat(expectedCalls('items'))
    .concat(expectedCalls('contains'))
    .concat(expectedCalls('additionalProperties'))
    .concat(expectedCalls('propertyNames'))
    .concat(expectedCalls('not'))
    .concat(expectedCallsChild('allOf', 0))
    .concat(expectedCallsChild('allOf', 1))
    .concat([
      [schema.allOf[2], '/allOf/2', schema, '', 'allOf', schema, 2],
      [schema.allOf[2].items[0], '/allOf/2/items/0', schema, '/allOf/2', 'items', schema.allOf[2], 0],
      [schema.allOf[2].items[0].properties.foo_items_0, '/allOf/2/items/0/properties/foo_items_0', schema, '/allOf/2/items/0', 'properties', schema.allOf[2].items[0], 'foo_items_0'],
      [schema.allOf[2].items[0].properties.bar_items_0, '/allOf/2/items/0/properties/bar_items_0', schema, '/allOf/2/items/0', 'properties', schema.allOf[2].items[0], 'bar_items_0'],
      [schema.allOf[2].items[0].anyOf[0], '/allOf/2/items/0/anyOf/0', schema, '/allOf/2/items/0', 'anyOf', schema.allOf[2].items[0], 0],
      [schema.allOf[2].items[0].anyOf[1], '/allOf/2/items/0/anyOf/1', schema, '/allOf/2/items/0', 'anyOf', schema.allOf[2].items[0], 1],

      [schema.allOf[2].items[1], '/allOf/2/items/1', schema, '/allOf/2', 'items', schema.allOf[2], 1],
      [schema.allOf[2].items[1].properties.foo_items_1, '/allOf/2/items/1/properties/foo_items_1', schema, '/allOf/2/items/1', 'properties', schema.allOf[2].items[1], 'foo_items_1'],
      [schema.allOf[2].items[1].properties.bar_items_1, '/allOf/2/items/1/properties/bar_items_1', schema, '/allOf/2/items/1', 'properties', schema.allOf[2].items[1], 'bar_items_1'],
      [schema.allOf[2].items[1].anyOf[0], '/allOf/2/items/1/anyOf/0', schema, '/allOf/2/items/1', 'anyOf', schema.allOf[2].items[1], 0],
      [schema.allOf[2].items[1].anyOf[1], '/allOf/2/items/1/anyOf/1', schema, '/allOf/2/items/1', 'anyOf', schema.allOf[2].items[1], 1]
    ])
    .concat(expectedCallsChild('anyOf', 0))
    .concat(expectedCallsChild('anyOf', 1))
    .concat(expectedCallsChild('oneOf', 0))
    .concat(expectedCallsChild('oneOf', 1))
    .concat(expectedCallsChild('definitions', 'foo'))
    .concat(expectedCallsChild('definitions', 'bar'))
    .concat(expectedCallsChild('properties', 'foo'))
    .concat(expectedCallsChild('properties', 'bar'))
    .concat(expectedCallsChild('patternProperties', 'foo'))
    .concat(expectedCallsChild('patternProperties', 'bar'))
    .concat(expectedCallsChild('dependencies', 'foo'))
    .concat(expectedCallsChild('dependencies', 'bar'))
};

function expectedCalls(keyword) {
  return [
    [schema[keyword], `/${keyword}`, schema, '', keyword, schema, undefined],
    [schema[keyword].properties[`foo_${keyword}`], `/${keyword}/properties/foo_${keyword}`, schema, `/${keyword}`, 'properties', schema[keyword], `foo_${keyword}`],
    [schema[keyword].properties[`bar_${keyword}`], `/${keyword}/properties/bar_${keyword}`, schema, `/${keyword}`, 'properties', schema[keyword], `bar_${keyword}`],
    [schema[keyword].anyOf[0], `/${keyword}/anyOf/0`, schema, `/${keyword}`, 'anyOf', schema[keyword], 0],
    [schema[keyword].anyOf[1], `/${keyword}/anyOf/1`, schema, `/${keyword}`, 'anyOf', schema[keyword], 1]
  ];
}

function expectedCallsChild(keyword, i) {
  return [
    [schema[keyword][i], `/${keyword}/${i}`, schema, '', keyword, schema, i],
    [schema[keyword][i].properties[`foo_${keyword}_${i}`], `/${keyword}/${i}/properties/foo_${keyword}_${i}`, schema, `/${keyword}/${i}`, 'properties', schema[keyword][i], `foo_${keyword}_${i}`],
    [schema[keyword][i].properties[`bar_${keyword}_${i}`], `/${keyword}/${i}/properties/bar_${keyword}_${i}`, schema, `/${keyword}/${i}`, 'properties', schema[keyword][i], `bar_${keyword}_${i}`],
    [schema[keyword][i].anyOf[0], `/${keyword}/${i}/anyOf/0`, schema, `/${keyword}/${i}`, 'anyOf', schema[keyword][i], 0],
    [schema[keyword][i].anyOf[1], `/${keyword}/${i}/anyOf/1`, schema, `/${keyword}/${i}`, 'anyOf', schema[keyword][i], 1]
  ];
}


code:
'use strict';

var traverse = require('../index');
var assert = require('assert');

describe('json-schema-traverse', function() {
  var calls;

  beforeEach(function() {
    calls = [];
  });

  it('should traverse all keywords containing schemas recursively', function() {
    var schema = require('./fixtures/schema').schema;
    var expectedCalls = require('./fixtures/schema').expectedCalls;

    traverse(schema, {cb: callback});
    assert.deepStrictEqual(calls, expectedCalls);
  });

  describe('Legacy v0.3.1 API', function() {
    it('should traverse all keywords containing schemas recursively', function() {
      var schema = require('./fixtures/schema').schema;
      var expectedCalls = require('./fixtures/schema').expectedCalls;

      traverse(schema, callback);
      assert.deepStrictEqual(calls, expectedCalls);
    });

    it('should work when an options object is provided', function() {
      // schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
      var schema = require('./fixtures/schema').schema;
      var expectedCalls = require('./fixtures/schema').expectedCalls;

      traverse(schema, {}, callback);
      assert.deepStrictEqual(calls, expectedCalls);
    });
  });

  describe('allKeys option', function() {
    var schema = {
      someObject: {
        minimum: 1,
        maximum: 2
      }
    };

    it('should traverse objects with allKeys: true option', function() {
      // schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
      var expectedCalls = [
        [schema, '', schema, undefined, undefined, undefined, undefined],
        [schema.someObject, '/someObject', schema, '', 'someObject', schema, undefined]
      ];

      traverse(schema, {allKeys: true, cb: callback});
      assert.deepStrictEqual(calls, expectedCalls);
    });

    it('should NOT traverse objects with allKeys: false option', function() {
      // schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
      var expectedCalls = [
        [schema, '', schema, undefined, undefined, undefined, undefined]
      ];

      traverse(schema, {allKeys: false, cb: callback});
      assert.deepStrictEqual(calls, expectedCalls);
    });

    it('should NOT traverse objects without allKeys option', function() {
      // schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
      var expectedCalls = [
        [schema, '', schema, undefined, undefined, undefined, undefined]
      ];

      traverse(schema, {cb: callback});
      assert.deepStrictEqual(calls, expectedCalls);
    });

    it('should NOT travers objects in standard keywords which value is not a schema', function() {
      var schema2 = {
        const: {foo: 'bar'},
        enum: ['a', 'b'],
        required: ['foo'],
        another: {

        },
        patternProperties: {}, // will not traverse - no properties
        dependencies: true, // will not traverse - invalid
        properties: {
          smaller: {
            type: 'number'
          },
          larger: {
            type: 'number',
            minimum: {$data: '1/smaller'}
          }
        }
      };

      // schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
      var expectedCalls = [
        [schema2, '', schema2, undefined, undefined, undefined, undefined],
        [schema2.another, '/another', schema2, '', 'another', schema2, undefined],
        [schema2.properties.smaller, '/properties/smaller', schema2, '', 'properties', schema2, 'smaller'],
        [schema2.properties.larger, '/properties/larger', schema2, '', 'properties', schema2, 'larger'],
      ];

      traverse(schema2, {allKeys: true, cb: callback});
      assert.deepStrictEqual(calls, expectedCalls);
    });
  });

  describe('pre and post', function() {
    var schema = {
      type: 'object',
      properties: {
        name: {type: 'string'},
        age: {type: 'number'}
      }
    };

    it('should traverse schema in pre-order', function() {
      traverse(schema, {cb: {pre}});
      var expectedCalls = [
        ['pre', schema, '', schema, undefined, undefined, undefined, undefined],
        ['pre', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
        ['pre', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
      ];
      assert.deepStrictEqual(calls, expectedCalls);
    });

    it('should traverse schema in post-order', function() {
      traverse(schema, {cb: {post}});
      var expectedCalls = [
        ['post', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
        ['post', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
        ['post', schema, '', schema, undefined, undefined, undefined, undefined],
      ];
      assert.deepStrictEqual(calls, expectedCalls);
    });

    it('should traverse schema in pre- and post-order at the same time', function() {
      traverse(schema, {cb: {pre, post}});
      var expectedCalls = [
        ['pre', schema, '', schema, undefined, undefined, undefined, undefined],
        ['pre', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
        ['post', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
        ['pre', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
        ['post', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
        ['post', schema, '', schema, undefined, undefined, undefined, undefined],
      ];
      assert.deepStrictEqual(calls, expectedCalls);
    });
  });

  function callback() {
    calls.push(Array.prototype.slice.call(arguments));
  }

  function pre() {
    calls.push(['pre'].concat(Array.prototype.slice.call(arguments)));
  }

  function post() {
    calls.push(['post'].concat(Array.prototype.slice.call(arguments)));
  }
});


ajv-formats

description: Format validation for Ajv v7+

files: src/ dist/

build: tsc

eslint: eslint --ext .ts ./src/**/*

test-spec: jest

test-cov: jest --coverage

test: npm run prettier:check && npm run build && npm run eslint && npm run test-cov

ci-test: npm run test

keywords: Ajv JSON-Schema format validation

*.{md,json,yaml,js,ts}: prettier --write


code:
import {
  DefinedFormats,
  FormatMode,
  FormatName,
  formatNames,
  fastFormats,
  fullFormats,
} from "./formats"
import formatLimit from "./limit"
import type Ajv from "ajv"
import type {Plugin, Format} from "ajv"
import {_, Name} from "ajv/dist/compile/codegen"

export {FormatMode, FormatName} from "./formats"
export {LimitFormatError} from "./limit"
export interface FormatOptions {
  mode?: FormatMode
  formats?: FormatName[]
  keywords?: boolean
}

export type FormatsPluginOptions = FormatName[] | FormatOptions

export interface FormatsPlugin extends Plugin<FormatsPluginOptions> {
  get: (format: FormatName, mode?: FormatMode) => Format
}

const fullName = new Name("fullFormats")
const fastName = new Name("fastFormats")

const formatsPlugin: FormatsPlugin = (
  ajv: Ajv,
  opts: FormatsPluginOptions = {keywords: true}
): Ajv => {
  if (Array.isArray(opts)) {
    addFormats(ajv, opts, fullFormats, fullName)
    return ajv
  }
  const [formats, exportName] =
    opts.mode === "fast" ? [fastFormats, fastName] : [fullFormats, fullName]
  const list = opts.formats || formatNames
  addFormats(ajv, list, formats, exportName)
  if (opts.keywords) formatLimit(ajv)
  return ajv
}

formatsPlugin.get = (name: FormatName, mode: FormatMode = "full"): Format => {
  const formats = mode === "fast" ? fastFormats : fullFormats
  const f = formats[name]
  if (!f) throw new Error(`Unknown format "${name}"`)
  return f
}

function addFormats(ajv: Ajv, list: FormatName[], fs: DefinedFormats, exportName: Name): void {
  ajv.opts.code.formats ??= _`require("ajv-formats/dist/formats").${exportName}`
  for (const f of list) ajv.addFormat(f, fs[f])
}

module.exports = exports = formatsPlugin
Object.defineProperty(exports, "__esModule", {value: true})

export default formatsPlugin


code:
import type Ajv from "ajv"
import type {
  Plugin,
  CodeKeywordDefinition,
  KeywordErrorDefinition,
  Code,
  Name,
  ErrorObject,
} from "ajv"
import type {AddedFormat} from "ajv/dist/types"
import type {Rule} from "ajv/dist/compile/rules"
import {KeywordCxt} from "ajv"
import {_, str, or, getProperty, operators} from "ajv/dist/compile/codegen"

type Kwd = "formatMaximum" | "formatMinimum" | "formatExclusiveMaximum" | "formatExclusiveMinimum"

type Comparison = "<=" | ">=" | "<" | ">"

const ops = operators

const KWDs: {[K in Kwd]: {okStr: Comparison; ok: Code; fail: Code}} = {
  formatMaximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT},
  formatMinimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT},
  formatExclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE},
  formatExclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE},
}

export type LimitFormatError = ErrorObject<Kwd, {limit: string; comparison: Comparison}>

const error: KeywordErrorDefinition = {
  message: ({keyword, schemaCode}) => str`should be ${KWDs[keyword as Kwd].okStr} ${schemaCode}`,
  params: ({keyword, schemaCode}) =>
    _`{comparison: ${KWDs[keyword as Kwd].okStr}, limit: ${schemaCode}}`,
}

export const formatLimitDefinition: CodeKeywordDefinition = {
  keyword: Object.keys(KWDs),
  type: "string",
  schemaType: "string",
  $data: true,
  error,
  code(cxt) {
    const {gen, data, schemaCode, keyword, it} = cxt
    const {opts, self} = it
    if (!opts.validateFormats) return

    const fCxt = new KeywordCxt(it, (self.RULES.all.format as Rule).definition, "format")
    if (fCxt.$data) validate$DataFormat()
    else validateFormat()

    function validate$DataFormat(): void {
      const fmts = gen.scopeValue("formats", {
        ref: self.formats,
        code: opts.code.formats,
      })
      const fmt = gen.const("fmt", _`${fmts}[${fCxt.schemaCode}]`)
      cxt.fail$data(
        or(
          _`typeof ${fmt} != "object"`,
          _`${fmt} instanceof RegExp`,
          _`typeof ${fmt}.compare != "function"`,
          compareCode(fmt)
        )
      )
    }

    function validateFormat(): void {
      const format = fCxt.schema as string
      const fmtDef: AddedFormat | undefined = self.formats[format]
      if (!fmtDef || fmtDef === true) return
      if (
        typeof fmtDef != "object" ||
        fmtDef instanceof RegExp ||
        typeof fmtDef.compare != "function"
      ) {
        throw new Error(`"${keyword}": format "${format}" does not define "compare" function`)
      }
      const fmt = gen.scopeValue("formats", {
        key: format,
        ref: fmtDef,
        code: opts.code.formats ? _`${opts.code.formats}${getProperty(format)}` : undefined,
      })

      cxt.fail$data(compareCode(fmt))
    }

    function compareCode(fmt: Name): Code {
      return _`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword as Kwd].fail} 0`
    }
  },
  dependencies: ["format"],
}

const formatLimitPlugin: Plugin<undefined> = (ajv: Ajv): Ajv => {
  ajv.addKeyword(formatLimitDefinition)
  return ajv
}

export default formatLimitPlugin


code:
# ajv-keywords

Custom JSON-Schema keywords for [Ajv](https://github.com/epoberezkin/ajv) validator

[![Build Status](https://travis-ci.org/ajv-validator/ajv-keywords.svg?branch=master)](https://travis-ci.org/ajv-validator/ajv-keywords)
[![npm](https://img.shields.io/npm/v/ajv-keywords.svg)](https://www.npmjs.com/package/ajv-keywords)
[![npm downloads](https://img.shields.io/npm/dm/ajv-keywords.svg)](https://www.npmjs.com/package/ajv-keywords)
[![Coverage Status](https://coveralls.io/repos/github/ajv-validator/ajv-keywords/badge.svg?branch=master)](https://coveralls.io/github/ajv-validator/ajv-keywords?branch=master)
[![Dependabot](https://api.dependabot.com/badges/status?host=github&repo=ajv-validator/ajv-keywords)](https://app.dependabot.com/accounts/ajv-validator/repos/60477053)
[![Gitter](https://img.shields.io/gitter/room/ajv-validator/ajv.svg)](https://gitter.im/ajv-validator/ajv)

## Contents

- [Install](#install)
- [Usage](#usage)
- [Keywords](#keywords)
  - [Types](#types)
    - [typeof](#typeof)
    - [instanceof](#instanceof)
  - [Keywords for numbers](#keywords-for-numbers)
    - [range and exclusiveRange](#range-and-exclusiverange)
  - [Keywords for strings](#keywords-for-strings)
    - [regexp](#regexp)
    - [formatMaximum / formatMinimum and formatExclusiveMaximum / formatExclusiveMinimum](#formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum)
    - [transform](#transform)\*
  - [Keywords for arrays](#keywords-for-arrays)
    - [uniqueItemProperties](#uniqueitemproperties)
  - [Keywords for objects](#keywords-for-objects)
    - [allRequired](#allrequired)
    - [anyRequired](#anyrequired)
    - [oneRequired](#onerequired)
    - [patternRequired](#patternrequired)
    - [prohibited](#prohibited)
    - [deepProperties](#deepproperties)
    - [deepRequired](#deeprequired)
  - [Compound keywords](#compound-keywords)
    - [switch](#switch) (deprecated)
    - [select/selectCases/selectDefault](#selectselectcasesselectdefault) (BETA)
  - [Keywords for all types](#keywords-for-all-types)
    - [dynamicDefaults](#dynamicdefaults)\*
- [Security contact](#security-contact)
- [Open-source software support](#open-source-software-support)
- [License](#license)

\* - keywords that modify data

## Install

```
npm install ajv-keywords
```

## Usage

To add all available keywords:

```javascript
var Ajv = require('ajv');
var ajv = new Ajv;
require('ajv-keywords')(ajv);

ajv.validate({ instanceof: 'RegExp' }, /.*/); // true
ajv.validate({ instanceof: 'RegExp' }, '.*'); // false
```

To add a single keyword:

```javascript
require('ajv-keywords')(ajv, 'instanceof');
```

To add multiple keywords:

```javascript
require('ajv-keywords')(ajv, ['typeof', 'instanceof']);
```

To add a single keyword in browser (to avoid adding unused code):

```javascript
require('ajv-keywords/keywords/instanceof')(ajv);
```

## Keywords

### Types

#### `typeof`

Based on JavaScript `typeof` operation.

The value of the keyword should be a string (`"undefined"`, `"string"`, `"number"`, `"object"`, `"function"`, `"boolean"` or `"symbol"`) or array of strings.

To pass validation the result of `typeof` operation on the value should be equal to the string (or one of the strings in the array).

```
ajv.validate({ typeof: 'undefined' }, undefined); // true
ajv.validate({ typeof: 'undefined' }, null); // false
ajv.validate({ typeof: ['undefined', 'object'] }, null); // true
```

#### `instanceof`

Based on JavaScript `instanceof` operation.

The value of the keyword should be a string (`"Object"`, `"Array"`, `"Function"`, `"Number"`, `"String"`, `"Date"`, `"RegExp"`, `"Promise"` or `"Buffer"`) or array of strings.

To pass validation the result of `data instanceof ...` operation on the value should be true:

```
ajv.validate({ instanceof: 'Array' }, []); // true
ajv.validate({ instanceof: 'Array' }, {}); // false
ajv.validate({ instanceof: ['Array', 'Function'] }, function(){}); // true
```

You can add your own constructor function to be recognised by this keyword:

```javascript
function MyClass() {}
var instanceofDefinition = require('ajv-keywords').get('instanceof').definition;
// or require('ajv-keywords/keywords/instanceof').definition;
instanceofDefinition.CONSTRUCTORS.MyClass = MyClass;

ajv.validate({ instanceof: 'MyClass' }, new MyClass); // true
```

### Keywords for numbers

#### `range` and `exclusiveRange`

Syntax sugar for the combination of minimum and maximum keywords, also fails schema compilation if there are no numbers in the range.

The value of this keyword must be the array consisting of two numbers, the second must be greater or equal than the first one.

If the validated value is not a number the validation passes, otherwise to pass validation the value should be greater (or equal) than the first number and smaller (or equal) than the second number in the array. If `exclusiveRange` keyword is present in the same schema and its value is true, the validated value must not be equal to the range boundaries.

```javascript
var schema = { range: [1, 3] };
ajv.validate(schema, 1); // true
ajv.validate(schema, 2); // true
ajv.validate(schema, 3); // true
ajv.validate(schema, 0.99); // false
ajv.validate(schema, 3.01); // false

var schema = { range: [1, 3], exclusiveRange: true };
ajv.validate(schema, 1.01); // true
ajv.validate(schema, 2); // true
ajv.validate(schema, 2.99); // true
ajv.validate(schema, 1); // false
ajv.validate(schema, 3); // false
```

### Keywords for strings

#### `regexp`

This keyword allows to use regular expressions with flags in schemas (the standard `pattern` keyword does not support flags).

This keyword applies only to strings. If the data is not a string, the validation succeeds.

The value of this keyword can be either a string (the result of `regexp.toString()`) or an object with the properties `pattern` and `flags` (the same strings that should be passed to RegExp constructor).

```javascript
var schema = {
  type: 'object',
  properties: {
    foo: { regexp: '/foo/i' },
    bar: { regexp: { pattern: 'bar', flags: 'i' } }
  }
};

var validData = {
  foo: 'Food',
  bar: 'Barmen'
};

var invalidData = {
  foo: 'fog',
  bar: 'bad'
};
```

#### `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum`

These keywords allow to define minimum/maximum constraints when the format keyword defines ordering.

These keywords apply only to strings. If the data is not a string, the validation succeeds.

The value of keyword `formatMaximum` (`formatMinimum`) should be a string. This value is the maximum (minimum) allowed value for the data to be valid as determined by `format` keyword. If `format` is not present schema compilation will throw exception.

When this keyword is added, it defines comparison rules for formats `"date"`, `"time"` and `"date-time"`. Custom formats also can have comparison rules. See [addFormat](https://github.com/epoberezkin/ajv#api-addformat) method.

The value of keyword `formatExclusiveMaximum` (`formatExclusiveMinimum`) should be a boolean value. These keyword cannot be used without `formatMaximum` (`formatMinimum`). If this keyword value is equal to `true`, the data to be valid should not be equal to the value in `formatMaximum` (`formatMinimum`) keyword.

```javascript
require('ajv-keywords')(ajv, ['formatMinimum', 'formatMaximum']);

var schema = {
  format: 'date',
  formatMinimum: '2016-02-06',
  formatMaximum: '2016-12-27',
  formatExclusiveMaximum: true
}

var validDataList = ['2016-02-06', '2016-12-26', 1];

var invalidDataList = ['2016-02-05', '2016-12-27', 'abc'];
```

#### `transform`

This keyword allows a string to be modified before validation.

These keywords apply only to strings. If the data is not a string, the transform is skipped.

There are limitation due to how ajv is written:
- a stand alone string cannot be transformed. ie `data = 'a'; ajv.validate(schema, data);`
- currently cannot work with `ajv-pack`

**Supported options:**
- `trim`: remove whitespace from start and end
- `trimLeft`: remove whitespace from start
- `trimRight`: remove whitespace from end
- `toLowerCase`: case string to all lower case
- `toUpperCase`: case string to all upper case
- `toEnumCase`: case string to match case in schema

Options are applied in the order they are listed.

Note: `toEnumCase` requires that all allowed values are unique when case insensitive.

**Example: multiple options**
```javascript
require('ajv-keywords')(ajv, ['transform']);

var schema = {
  type: 'array',
  items: {
    type:'string',
    transform:['trim','toLowerCase']
  }
};

var data = ['  MixCase  '];
ajv.validate(schema, data);
console.log(data); // ['mixcase']

```

**Example: `enumcase`**
```javascript
require('ajv-keywords')(ajv, ['transform']);

var schema = {
  type: 'array',
  items: {
    type:'string',
    transform:['trim','toEnumCase'],
    enum:['pH']
  }
};

var data = ['ph',' Ph','PH','pH '];
ajv.validate(schema, data);
console.log(data); // ['pH','pH','pH','pH']
```

### Keywords for arrays

#### `uniqueItemProperties`

The keyword allows to check that some properties in array items are unique.

This keyword applies only to arrays. If the data is not an array, the validation succeeds.

The value of this keyword must be an array of strings - property names that should have unique values across all items.

```javascript
var schema = { uniqueItemProperties: [ "id", "name" ] };

var validData = [
  { id: 1 },
  { id: 2 },
  { id: 3 }
];

var invalidData1 = [
  { id: 1 },
  { id: 1 }, // duplicate "id"
  { id: 3 }
];

var invalidData2 = [
  { id: 1, name: "taco" },
  { id: 2, name: "taco" }, // duplicate "name"
  { id: 3, name: "salsa" }
];
```

This keyword is contributed by [@blainesch](https://github.com/blainesch).

### Keywords for objects

#### `allRequired`

This keyword allows to require the presence of all properties used in `properties` keyword in the same schema object.

This keyword applies only to objects. If the data is not an object, the validation succeeds.

The value of this keyword must be boolean.

If the value of the keyword is `false`, the validation succeeds.

If the value of the keyword is `true`, the validation succeeds if the data contains all properties defined in `properties` keyword (in the same schema object).

If the `properties` keyword is not present in the same schema object, schema compilation will throw exception.

```javascript
var schema = {
  properties: {
    foo: {type: 'number'},
    bar: {type: 'number'}
  }
  allRequired: true
};

var validData = { foo: 1, bar: 2 };
var alsoValidData = { foo: 1, bar: 2, baz: 3 };

var invalidDataList = [ {}, { foo: 1 }, { bar: 2 } ];
```

#### `anyRequired`

This keyword allows to require the presence of any (at least one) property from the list.

This keyword applies only to objects. If the data is not an object, the validation succeeds.

The value of this keyword must be an array of strings, each string being a property name. For data object to be valid at least one of the properties in this array should be present in the object.

```javascript
var schema = {
  anyRequired: ['foo', 'bar']
};

var validData = { foo: 1 };
var alsoValidData = { foo: 1, bar: 2 };

var invalidDataList = [ {}, { baz: 3 } ];
```

#### `oneRequired`

This keyword allows to require the presence of only one property from the list.

This keyword applies only to objects. If the data is not an object, the validation succeeds.

The value of this keyword must be an array of strings, each string being a property name. For data object to be valid exactly one of the properties in this array should be present in the object.

```javascript
var schema = {
  oneRequired: ['foo', 'bar']
};

var validData = { foo: 1 };
var alsoValidData = { bar: 2, baz: 3 };

var invalidDataList = [ {}, { baz: 3 }, { foo: 1, bar: 2 } ];
```

#### `patternRequired`

This keyword allows to require the presence of properties that match some pattern(s).

This keyword applies only to objects. If the data is not an object, the validation succeeds.

The value of this keyword should be an array of strings, each string being a regular expression. For data object to be valid each regular expression in this array should match at least one property name in the data object.

If the array contains multiple regular expressions, more than one expression can match the same property name.

```javascript
var schema = { patternRequired: [ 'f.*o', 'b.*r' ] };

var validData = { foo: 1, bar: 2 };
var alsoValidData = { foobar: 3 };

var invalidDataList = [ {}, { foo: 1 }, { bar: 2 } ];
```

#### `prohibited`

This keyword allows to prohibit that any of the properties in the list is present in the object.

This keyword applies only to objects. If the data is not an object, the validation succeeds.

The value of this keyword should be an array of strings, each string being a property name. For data object to be valid none of the properties in this array should be present in the object.

```
var schema = { prohibited: ['foo', 'bar']};

var validData = { baz: 1 };
var alsoValidData = {};

var invalidDataList = [
  { foo: 1 },
  { bar: 2 },
  { foo: 1, bar: 2}
];
```

__Please note__: `{prohibited: ['foo', 'bar']}` is equivalent to `{not: {anyRequired: ['foo', 'bar']}}` (i.e. it has the same validation result for any data).

#### `deepProperties`

This keyword allows to validate deep properties (identified by JSON pointers).

This keyword applies only to objects. If the data is not an object, the validation succeeds.

The value should be an object, where keys are JSON pointers to the data, starting from the current position in data, and the values are JSON schemas. For data object to be valid the value of each JSON pointer should be valid according to the corresponding schema.

```javascript
var schema = {
  type: 'object',
  deepProperties: {
    "/users/1/role": { "enum": ["admin"] }
  }
};

var validData = {
  users: [
    {},
    {
      id: 123,
      role: 'admin'
    }
  ]
};

var alsoValidData = {
  users: {
    "1": {
      id: 123,
      role: 'admin'
    }
  }
};

var invalidData = {
  users: [
    {},
    {
      id: 123,
      role: 'user'
    }
  ]
};

var alsoInvalidData = {
  users: {
    "1": {
      id: 123,
      role: 'user'
    }
  }
};
```

#### `deepRequired`

This keyword allows to check that some deep properties (identified by JSON pointers) are available.

This keyword applies only to objects. If the data is not an object, the validation succeeds.

The value should be an array of JSON pointers to the data, starting from the current position in data. For data object to be valid each JSON pointer should be some existing part of the data.

```javascript
var schema = {
  type: 'object',
  deepRequired: ["/users/1/role"]
};

var validData = {
  users: [
    {},
    {
      id: 123,
      role: 'admin'
    }
  ]
};

var invalidData = {
  users: [
    {},
    {
      id: 123
    }
  ]
};
```

See [json-schema-org/json-schema-spec#203](https://github.com/json-schema-org/json-schema-spec/issues/203#issue-197211916) for an example of the equivalent schema without `deepRequired` keyword.

### Compound keywords

#### `switch` (deprecated)

__Please note__: this keyword is provided to preserve backward compatibility with previous versions of Ajv. It is strongly recommended to use `if`/`then`/`else` keywords instead, as they have been added to the draft-07 of JSON Schema specification.

This keyword allows to perform advanced conditional validation.

The value of the keyword is the array of if/then clauses. Each clause is the object with the following properties:

- `if` (optional) - the value is JSON-schema
- `then` (required) - the value is JSON-schema or boolean
- `continue` (optional) - the value is boolean

The validation process is dynamic; all clauses are executed sequentially in the following way:

1. `if`:
    1.  `if` property is JSON-schema according to which the data is:
        1.  valid => go to step 2.
        2.  invalid => go to the NEXT clause, if this was the last clause the validation of `switch` SUCCEEDS.
    2.  `if` property is absent => go to step 2.
2. `then`:
    1.  `then` property is `true` or it is JSON-schema according to which the data is valid => go to step 3.
    2.  `then` property is `false` or it is JSON-schema according to which the data is invalid => the validation of `switch` FAILS.
3. `continue`:
    1.  `continue` property is `true` => go to the NEXT clause, if this was the last clause the validation of `switch` SUCCEEDS.
    2.  `continue` property is `false` or absent => validation of `switch` SUCCEEDS.

```javascript
require('ajv-keywords')(ajv, 'switch');

var schema = {
  type: 'array',
  items: {
    type: 'integer',
    'switch': [
      { if: { not: { minimum: 1 } }, then: false },
      { if: { maximum: 10 }, then: true },
      { if: { maximum: 100 }, then: { multipleOf: 10 } },
      { if: { maximum: 1000 }, then: { multipleOf: 100 } },
      { then: false }
    ]
  }
};

var validItems = [1, 5, 10, 20, 50, 100, 200, 500, 1000];

var invalidItems = [1, 0, 2000, 11, 57, 123, 'foo'];
```

The above schema is equivalent to (for example):

```javascript
{
  type: 'array',
  items: {
    type: 'integer',
    if: { minimum: 1, maximum: 10 },
    then: true,
    else: {
      if: { maximum: 100 },
      then: { multipleOf: 10 },
      else: {
        if: { maximum: 1000 },
        then: { multipleOf: 100 },
        else: false
      }
    }
  }
}
```

#### `select`/`selectCases`/`selectDefault`

These keywords allow to choose the schema to validate the data based on the value of some property in the validated data.

These keywords must be present in the same schema object (`selectDefault` is optional).

The value of `select` keyword should be a [$data reference](https://github.com/epoberezkin/ajv/tree/5.0.2-beta.0#data-reference) that points to any primitive JSON type (string, number, boolean or null) in the data that is validated. You can also use a constant of primitive type as the value of this keyword (e.g., for debugging purposes).

The value of `selectCases` keyword must be an object where each property name is a possible string representation of the value of `select` keyword and each property value is a corresponding schema (from draft-06 it can be boolean) that must be used to validate the data.

The value of `selectDefault` keyword is a schema (from draft-06 it can be boolean) that must be used to validate the data in case `selectCases` has no key equal to the stringified value of `select` keyword.

The validation succeeds in one of the following cases:
- the validation of data using selected schema succeeds,
- none of the schemas is selected for validation,
- the value of select is undefined (no property in the data that the data reference points to).

If `select` value (in data) is not a primitive type the validation fails.

__Please note__: these keywords require Ajv `$data` option to support [$data reference](https://github.com/epoberezkin/ajv/tree/5.0.2-beta.0#data-reference).

```javascript
require('ajv-keywords')(ajv, 'select');

var schema = {
  type: object,
  required: ['kind'],
  properties: {
    kind: { type: 'string' }
  },
  select: { $data: '0/kind' },
  selectCases: {
    foo: {
      required: ['foo'],
      properties: {
        kind: {},
        foo: { type: 'string' }
      },
      additionalProperties: false
    },
    bar: {
      required: ['bar'],
      properties: {
        kind: {},
        bar: { type: 'number' }
      },
      additionalProperties: false
    }
  },
  selectDefault: {
    propertyNames: {
      not: { enum: ['foo', 'bar'] }
    }
  }
};

var validDataList = [
  { kind: 'foo', foo: 'any' },
  { kind: 'bar', bar: 1 },
  { kind: 'anything_else', not_bar_or_foo: 'any value' }
];

var invalidDataList = [
  { kind: 'foo' }, // no propery foo
  { kind: 'bar' }, // no propery bar
  { kind: 'foo', foo: 'any', another: 'any value' }, // additional property
  { kind: 'bar', bar: 1, another: 'any value' }, // additional property
  { kind: 'anything_else', foo: 'any' } // property foo not allowed
  { kind: 'anything_else', bar: 1 } // property bar not allowed
];
```

__Please note__: the current implementation is BETA. It does not allow using relative URIs in $ref keywords in schemas in `selectCases` and `selectDefault` that point outside of these schemas. The workaround is to use absolute URIs (that can point to any (sub-)schema added to Ajv, including those inside the current root schema where `select` is used). See [tests](https://github.com/epoberezkin/ajv-keywords/blob/v2.0.0/spec/tests/select.json#L314).

### Keywords for all types

#### `dynamicDefaults`

This keyword allows to assign dynamic defaults to properties, such as timestamps, unique IDs etc.

This keyword only works if `useDefaults` options is used and not inside `anyOf` keywords etc., in the same way as [default keyword treated by Ajv](https://github.com/epoberezkin/ajv#assigning-defaults).

The keyword should be added on the object level. Its value should be an object with each property corresponding to a property name, in the same way as in standard `properties` keyword. The value of each property can be:

- an identifier of default function (a string)
- an object with properties `func` (an identifier) and `args` (an object with parameters that will be passed to this function during schema compilation - see examples).

The properties used in `dynamicDefaults` should not be added to `required` keyword (or validation will fail), because unlike `default` this keyword is processed after validation.

There are several predefined dynamic default functions:

- `"timestamp"` - current timestamp in milliseconds
- `"datetime"` - current date and time as string (ISO, valid according to `date-time` format)
- `"date"` - current date as string (ISO, valid according to `date` format)
- `"time"` - current time as string (ISO, valid according to `time` format)
- `"random"` - pseudo-random number in [0, 1) interval
- `"randomint"` - pseudo-random integer number. If string is used as a property value, the function will randomly return 0 or 1. If object `{ func: 'randomint', args: { max: N } }` is used then the default will be an integer number in [0, N) interval.
- `"seq"` - sequential integer number starting from 0. If string is used as a property value, the default sequence will be used. If object `{ func: 'seq', args: { name: 'foo'} }` is used then the sequence with name `"foo"` will be used. Sequences are global, even if different ajv instances are used.

```javascript
var schema = {
  type: 'object',
  dynamicDefaults: {
    ts: 'datetime',
    r: { func: 'randomint', args: { max: 100 } },
    id: { func: 'seq', args: { name: 'id' } }
  },
  properties: {
    ts: {
      type: 'string',
      format: 'date-time'
    },
    r: {
      type: 'integer',
      minimum: 0,
      exclusiveMaximum: 100
    },
    id: {
      type: 'integer',
      minimum: 0
    }
  }
};

var data = {};
ajv.validate(data); // true
data; // { ts: '2016-12-01T22:07:28.829Z', r: 25, id: 0 }

var data1 = {};
ajv.validate(data1); // true
data1; // { ts: '2016-12-01T22:07:29.832Z', r: 68, id: 1 }

ajv.validate(data1); // true
data1; // didn't change, as all properties were defined
```

When using the `useDefaults` option value `"empty"`, properties and items equal to `null` or `""` (empty string) will be considered missing and assigned defaults.  Use the `allOf` [compound keyword](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#compound-keywords) to execute `dynamicDefaults` before validation.

```javascript
var schema = {
  allOf: [
    {
      dynamicDefaults: {
        ts: 'datetime',
        r: { func: 'randomint', args: { min: 5, max: 100 } },
        id: { func: 'seq', args: { name: 'id' } }
      }
    },
    {
      type: 'object',
      properties: {
        ts: {
          type: 'string'
        },
        r: {
          type: 'number',
          minimum: 5,
          exclusiveMaximum: 100
        },
        id: {
          type: 'integer',
          minimum: 0
        }
      }
    }
  ]
};

var data = { ts: '', r: null };
ajv.validate(data); // true
data; // { ts: '2016-12-01T22:07:28.829Z', r: 25, id: 0 }
```

You can add your own dynamic default function to be recognised by this keyword:

```javascript
var uuid = require('uuid');

function uuidV4() { return uuid.v4(); }

var definition = require('ajv-keywords').get('dynamicDefaults').definition;
// or require('ajv-keywords/keywords/dynamicDefaults').definition;
definition.DEFAULTS.uuid = uuidV4;

var schema = {
  dynamicDefaults: { id: 'uuid' },
  properties: { id: { type: 'string', format: 'uuid' } }
};

var data = {};
ajv.validate(schema, data); // true
data; // { id: 'a1183fbe-697b-4030-9bcc-cfeb282a9150' };

var data1 = {};
ajv.validate(schema, data1); // true
data1; // { id: '5b008de7-1669-467a-a5c6-70fa244d7209' }
```

You also can define dynamic default that accepts parameters, e.g. version of uuid:

```javascript
var uuid = require('uuid');

function getUuid(args) {
  var version = 'v' + (arvs && args.v || 4);
  return function() {
    return uuid[version]();
  };
}

var definition = require('ajv-keywords').get('dynamicDefaults').definition;
definition.DEFAULTS.uuid = getUuid;

var schema = {
  dynamicDefaults: {
    id1: 'uuid', // v4
    id2: { func: 'uuid', v: 4 }, // v4
    id3: { func: 'uuid', v: 1 } // v1
  }
};
```

## Security contact

To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure.

Please do NOT report security vulnerabilities via GitHub issues.

## Open-source software support

Ajv-keywords is a part of [Tidelift subscription](https://tidelift.com/subscription/pkg/npm-ajv-keywords?utm_source=npm-ajv-keywords&utm_medium=referral&utm_campaign=readme) - it provides a centralised support to open-source software users, in addition to the support provided by software maintainers.

## License

[MIT](https://github.com/epoberezkin/ajv-keywords/blob/master/LICENSE)


code:
declare module 'ajv-keywords' {
  import { Ajv } from 'ajv';

  function keywords(ajv: Ajv, include?: string | string[]): Ajv;

  export = keywords;
}


code:
'use strict';

var KEYWORDS = require('./keywords');

module.exports = defineKeywords;

/**
 * Defines one or several keywords in ajv instance
 * @param  {Ajv} ajv validator instance
 * @param  {String|Array<String>|undefined} keyword keyword(s) to define
 * @return {Ajv} ajv instance (for chaining)
 */
function defineKeywords(ajv, keyword) {
  if (Array.isArray(keyword)) {
    for (var i=0; i<keyword.length; i++)
      get(keyword[i])(ajv);
    return ajv;
  }
  if (keyword) {
    get(keyword)(ajv);
    return ajv;
  }
  for (keyword in KEYWORDS) get(keyword)(ajv);
  return ajv;
}

defineKeywords.get = get;

function get(keyword) {
  var defFunc = KEYWORDS[keyword];
  if (!defFunc) throw new Error('Unknown keyword ' + keyword);
  return defFunc;
}


code:
'use strict';

var TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d:\d\d)?$/i;
var DATE_TIME_SEPARATOR = /t|\s/i;

var COMPARE_FORMATS = {
  date: compareDate,
  time: compareTime,
  'date-time': compareDateTime
};

var $dataMetaSchema = {
  type: 'object',
  required: [ '$data' ],
  properties: {
    $data: {
      type: 'string',
      anyOf: [
        { format: 'relative-json-pointer' },
        { format: 'json-pointer' }
      ]
    }
  },
  additionalProperties: false
};

module.exports = function (minMax) {
  var keyword = 'format' + minMax;
  return function defFunc(ajv) {
    defFunc.definition = {
      type: 'string',
      inline: require('./dotjs/_formatLimit'),
      statements: true,
      errors: 'full',
      dependencies: ['format'],
      metaSchema: {
        anyOf: [
          {type: 'string'},
          $dataMetaSchema
        ]
      }
    };

    ajv.addKeyword(keyword, defFunc.definition);
    ajv.addKeyword('formatExclusive' + minMax, {
      dependencies: ['format' + minMax],
      metaSchema: {
        anyOf: [
          {type: 'boolean'},
          $dataMetaSchema
        ]
      }
    });
    extendFormats(ajv);
    return ajv;
  };
};

function extendFormats(ajv) {
  var formats = ajv._formats;
  for (var name in COMPARE_FORMATS) {
    var format = formats[name];
    // the last condition is needed if it's RegExp from another window
    if (typeof format != 'object' || format instanceof RegExp || !format.validate)
      format = formats[name] = { validate: format };
    if (!format.compare)
      format.compare = COMPARE_FORMATS[name];
  }
}

function compareDate(d1, d2) {
  if (!(d1 && d2)) return;
  if (d1 > d2) return 1;
  if (d1 < d2) return -1;
  if (d1 === d2) return 0;
}

function compareTime(t1, t2) {
  if (!(t1 && t2)) return;
  t1 = t1.match(TIME);
  t2 = t2.match(TIME);
  if (!(t1 && t2)) return;
  t1 = t1[1] + t1[2] + t1[3] + (t1[4]||'');
  t2 = t2[1] + t2[2] + t2[3] + (t2[4]||'');
  if (t1 > t2) return 1;
  if (t1 < t2) return -1;
  if (t1 === t2) return 0;
}

function compareDateTime(dt1, dt2) {
  if (!(dt1 && dt2)) return;
  dt1 = dt1.split(DATE_TIME_SEPARATOR);
  dt2 = dt2.split(DATE_TIME_SEPARATOR);
  var res = compareDate(dt1[0], dt2[0]);
  if (res === undefined) return;
  return res || compareTime(dt1[1], dt2[1]);
}


code:
'use strict';

module.exports = {
  metaSchemaRef: metaSchemaRef
};

var META_SCHEMA_ID = 'http://json-schema.org/draft-07/schema';

function metaSchemaRef(ajv) {
  var defaultMeta = ajv._opts.defaultMeta;
  if (typeof defaultMeta == 'string') return { $ref: defaultMeta };
  if (ajv.getSchema(META_SCHEMA_ID)) return { $ref: META_SCHEMA_ID };
  console.warn('meta schema not defined');
  return {};
}


code:
'use strict';

module.exports = function defFunc(ajv) {
  defFunc.definition = {
    type: 'object',
    macro: function (schema, parentSchema) {
      if (!schema) return true;
      var properties = Object.keys(parentSchema.properties);
      if (properties.length == 0) return true;
      return {required: properties};
    },
    metaSchema: {type: 'boolean'},
    dependencies: ['properties']
  };

  ajv.addKeyword('allRequired', defFunc.definition);
  return ajv;
};


code:
'use strict';

module.exports = function defFunc(ajv) {
  defFunc.definition = {
    type: 'object',
    macro: function (schema) {
      if (schema.length == 0) return true;
      if (schema.length == 1) return {required: schema};
      var schemas = schema.map(function (prop) {
        return {required: [prop]};
      });
      return {anyOf: schemas};
    },
    metaSchema: {
      type: 'array',
      items: {
        type: 'string'
      }
    }
  };

  ajv.addKeyword('anyRequired', defFunc.definition);
  return ajv;
};


code:
'use strict';

var util = require('./_util');

module.exports = function defFunc(ajv) {
  defFunc.definition = {
    type: 'object',
    macro: function (schema) {
      var schemas = [];
      for (var pointer in schema)
        schemas.push(getSchema(pointer, schema[pointer]));
      return {'allOf': schemas};
    },
    metaSchema: {
      type: 'object',
      propertyNames: {
        type: 'string',
        format: 'json-pointer'
      },
      additionalProperties: util.metaSchemaRef(ajv)
    }
  };

  ajv.addKeyword('deepProperties', defFunc.definition);
  return ajv;
};

function getSchema(jsonPointer, schema) {
  var segments = jsonPointer.split('/');
  var rootSchema = {};
  var pointerSchema = rootSchema;
  for (var i=1; i<segments.length; i++) {
    var segment = segments[i];
    var isLast = i == segments.length - 1;
    segment = unescapeJsonPointer(segment);
    var properties = pointerSchema.properties = {};
    var items = undefined;
    if (/[0-9]+/.test(segment)) {
      var count = +segment;
      items = pointerSchema.items = [];
      while (count--) items.push({});
    }
    pointerSchema = isLast ? schema : {};
    properties[segment] = pointerSchema;
    if (items) items.push(pointerSchema);
  }
  return rootSchema;
}

function unescapeJsonPointer(str) {
  return str.replace(/~1/g, '/').replace(/~0/g, '~');
}


code:
'use strict';

module.exports = function defFunc(ajv) {
  defFunc.definition = {
    type: 'object',
    inline: function (it, keyword, schema) {
      var expr = '';
      for (var i=0; i<schema.length; i++) {
        if (i) expr += ' && ';
        expr += '(' + getData(schema[i], it.dataLevel) + ' !== undefined)';
      }
      return expr;
    },
    metaSchema: {
      type: 'array',
      items: {
        type: 'string',
        format: 'json-pointer'
      }
    }
  };

  ajv.addKeyword('deepRequired', defFunc.definition);
  return ajv;
};

function getData(jsonPointer, lvl) {
  var data = 'data' + (lvl || '');
  if (!jsonPointer) return data;

  var expr = data;
  var segments = jsonPointer.split('/');
  for (var i=1; i<segments.length; i++) {
    var segment = segments[i];
    data += getProperty(unescapeJsonPointer(segment));
    expr += ' && ' + data;
  }
  return expr;
}

var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
var INTEGER = /^[0-9]+$/;
var SINGLE_QUOTE = /'|\\/g;
function getProperty(key) {
  return INTEGER.test(key)
          ? '[' + key + ']'
          : IDENTIFIER.test(key)
            ? '.' + key
            : "['" + key.replace(SINGLE_QUOTE, '\\$&') + "']";
}

function unescapeJsonPointer(str) {
  return str.replace(/~1/g, '/').replace(/~0/g, '~');
}


code:
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}

var {{=$valid}} = undefined;

{{## def.skipFormatLimit:
  {{=$valid}} = true;
  {{ return out; }}
#}}

{{## def.compareFormat:
  {{? $isData }}
    if ({{=$schemaValue}} === undefined) {{=$valid}} = true;
    else if (typeof {{=$schemaValue}} != 'string') {{=$valid}} = false;
    else {
    {{ $closingBraces += '}'; }}
  {{?}}

  {{? $isDataFormat }}
    if (!{{=$compare}}) {{=$valid}} = true;
    else {
    {{ $closingBraces += '}'; }}
  {{?}}

  var {{=$result}} = {{=$compare}}({{=$data}}, {{# def.schemaValueQS }});

  if ({{=$result}} === undefined) {{=$valid}} = false;
#}}

{{? it.opts.format === false }}{{# def.skipFormatLimit }}{{?}}

{{
  var $schemaFormat = it.schema.format
    , $isDataFormat = it.opts.$data && $schemaFormat.$data
    , $closingBraces = '';
}}

{{? $isDataFormat }}
  {{
    var $schemaValueFormat = it.util.getData($schemaFormat.$data, $dataLvl, it.dataPathArr)
      , $format = 'format' + $lvl
      , $compare = 'compare' + $lvl;
  }}

  var {{=$format}} = formats[{{=$schemaValueFormat}}]
    , {{=$compare}} = {{=$format}} && {{=$format}}.compare;
{{??}}
  {{ var $format = it.formats[$schemaFormat]; }}
  {{? !($format && $format.compare) }}
    {{# def.skipFormatLimit }}
  {{?}}
  {{ var $compare = 'formats' + it.util.getProperty($schemaFormat) + '.compare'; }}
{{?}}

{{
  var $isMax = $keyword == 'formatMaximum'
    , $exclusiveKeyword = 'formatExclusive' + ($isMax ? 'Maximum' : 'Minimum')
    , $schemaExcl = it.schema[$exclusiveKeyword]
    , $isDataExcl = it.opts.$data && $schemaExcl && $schemaExcl.$data
    , $op = $isMax ? '<' : '>'
    , $result = 'result' + $lvl;
}}

{{# def.$data }}

{{? $isDataExcl }}
  {{
    var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr)
      , $exclusive = 'exclusive' + $lvl
      , $opExpr = 'op' + $lvl
      , $opStr = '\' + ' + $opExpr + ' + \'';
  }}
  var schemaExcl{{=$lvl}} = {{=$schemaValueExcl}};
  {{ $schemaValueExcl = 'schemaExcl' + $lvl; }}

  if (typeof {{=$schemaValueExcl}} != 'boolean' && {{=$schemaValueExcl}} !== undefined) {
    {{=$valid}} = false;
    {{ var $errorKeyword = $exclusiveKeyword; }}
    {{# def.error:'_formatExclusiveLimit' }}
  }

  {{# def.elseIfValid }}

  {{# def.compareFormat }}
  var {{=$exclusive}} = {{=$schemaValueExcl}} === true;

  if ({{=$valid}} === undefined) {
    {{=$valid}} = {{=$exclusive}}
                  ? {{=$result}} {{=$op}} 0
                  : {{=$result}} {{=$op}}= 0;
  }

  if (!{{=$valid}}) var op{{=$lvl}} = {{=$exclusive}} ? '{{=$op}}' : '{{=$op}}=';
{{??}}
  {{
    var $exclusive = $schemaExcl === true
      , $opStr = $op;  /*used in error*/
    if (!$exclusive) $opStr += '=';
    var $opExpr = '\'' + $opStr + '\''; /*used in error*/
  }}

  {{# def.compareFormat }}

  if ({{=$valid}} === undefined)
    {{=$valid}} = {{=$result}} {{=$op}}{{?!$exclusive}}={{?}} 0;
{{?}}

{{= $closingBraces }}

if (!{{=$valid}}) {
  {{ var $errorKeyword = $keyword; }}
  {{# def.error:'_formatLimit' }}
}


code:
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}

{{
  var $key = 'key' + $lvl
    , $idx = 'idx' + $lvl
    , $matched = 'patternMatched' + $lvl
    , $dataProperties = 'dataProperties' + $lvl
    , $closingBraces = ''
    , $ownProperties = it.opts.ownProperties;
}}

var {{=$valid}} = true;
{{? $ownProperties }}
  var {{=$dataProperties}} = undefined;
{{?}}

{{~ $schema:$pProperty }}
  var {{=$matched}} = false;
  {{# def.iterateProperties }}
    {{=$matched}} = {{= it.usePattern($pProperty) }}.test({{=$key}});
    if ({{=$matched}}) break;
  }

  {{ var $missingPattern = it.util.escapeQuotes($pProperty); }}
  if (!{{=$matched}}) {
    {{=$valid}} = false;
    {{# def.addError:'patternRequired' }}
  } {{# def.elseIfValid }}
{{~}}

{{= $closingBraces }}