///|
fn err(
keyword : String,
ipath : Array[String],
spath : Array[String],
message : String,
) -> ValidationError {
{
keyword,
instance_path: instance_pointer(ipath),
// ajv 风格:schema_path 以触发错误的关键词名结尾,如 "#/properties/age/minimum"
schema_path: "\{schema_pointer(spath)}/\{escape_pointer_token(keyword)}",
message,
}
}
///|
/// `false schema` 等特殊情况:关键词名不是模式里的路径段。
fn err_no_leaf(
keyword : String,
ipath : Array[String],
spath : Array[String],
message : String,
) -> ValidationError {
{
keyword,
instance_path: instance_pointer(ipath),
schema_path: schema_pointer(spath),
message,
}
}
///|
/// 属性键是否已被 `properties` / `patternProperties` "评估"——
/// 决定 `additionalProperties` 是否对其生效。
fn key_is_known(kw : Keywords, k : String) -> Bool {
match kw.properties {
Some(p) => if p.contains(k) { return true }
None => ()
}
match kw.pattern_properties {
Some(pairs) =>
for i in 0.. return true
None => ()
}
}
None => ()
}
false
}
///|
fn validate_node(
root : Schema,
node : Node,
v : Json,
ipath : Array[String],
spath : Array[String],
errors : Array[ValidationError],
) -> Bool {
match node {
BoolNode(true) => true
BoolNode(false) => {
errors.push(
err_no_leaf(
"false schema",
ipath,
spath,
tr(
root.ctx.locale,
"boolean schema is false",
"布尔模式为 false",
),
),
)
false
}
SchemaNode(kw) => validate_keywords(root, kw, v, ipath, spath, errors)
}
}
///|
/// `$ref` 惰性解析:首次校验时编译被引用的子模式并缓存,递归模式因此安全。
fn get_or_compile_ref(root : Schema, pointer : String) -> Schema? {
let ctx = root.ctx
match ctx.cache.get(pointer) {
Some(s) => Some(s)
None =>
match parse_local_ref(pointer) {
Some(tokens) =>
match resolve_pointer(ctx.root, tokens) {
Some(target) => {
let spath : Array[String] = ["#"]
for t in tokens {
spath.push(t)
}
let node : Node? = Some(compile_node(target, ctx, spath)) catch {
_ => None
}
match node {
Some(n) => {
let s : Schema = { node: n, ctx, }
ctx.cache[pointer] = s
Some(s)
}
None => None
}
}
None => None
}
None => None
}
}
}
///|
fn validate_keywords(
root : Schema,
kw : Keywords,
v : Json,
ipath : Array[String],
spath : Array[String],
errors : Array[ValidationError],
) -> Bool {
let start_len = errors.length()
let loc = root.ctx.locale
// 2020-12:$ref 与其余关键词并存,先验证 ref 目标,再验证本地关键词
match kw.ref_ {
Some(r) =>
match get_or_compile_ref(root, r) {
Some(s) => {
let _ = validate_node(root, s.node, v, ipath, spath, errors)
}
None =>
errors.push(
err(
"$ref",
ipath,
spath,
tr(
loc,
"cannot resolve $ref \"\{r}\"",
"无法解析 $ref \"\{r}\"",
),
),
)
}
None => ()
}
match kw.typ {
Some(ts) => {
let mut ok = false
for i in 0.. 0 {
if i == ts.length() - 1 {
b.write_string(" or ")
} else {
b.write_string(", ")
}
}
b.write_string(ts[i].label())
}
errors.push(
err(
"type",
ipath,
spath,
tr(loc, "must be \{b.to_string()}", "必须是 \{b.to_string()}"),
),
)
}
}
None => ()
}
match kw.const_ {
Some(c) =>
if !json_eq(v, c) {
errors.push(
err(
"const",
ipath,
spath,
tr(loc, "must be equal to constant", "必须等于常量值"),
),
)
}
None => ()
}
match kw.enum_ {
Some(vals) => {
let mut ok = false
for i in 0.. ()
}
if v is Number(n, ..) {
match kw.multiple_of {
Some(m) => {
// 浮点求余对 0.0075 % 0.0001 这类十进制场景有精度噪声,
// 改用商到最近整数的相对容差判定(与主流实现一致的工程取舍)
let q = n / m
let frac = q - q.floor()
let mag = if q.abs() > 1.0 { q.abs() } else { 1.0 }
let eps = 1.0e-9 * mag
if !(frac == 0.0 || frac < eps || 1.0 - frac < eps) {
errors.push(
err(
"multipleOf",
ipath,
spath,
tr(loc, "must be multiple of \{m}", "必须是 \{m} 的倍数"),
),
)
}
}
None => ()
}
match kw.maximum {
Some(m) =>
if n > m {
errors.push(
err(
"maximum",
ipath,
spath,
tr(loc, "must be <= \{m}", "必须 <= \{m}"),
),
)
}
None => ()
}
match kw.exclusive_maximum {
Some(m) =>
if n >= m {
errors.push(
err(
"exclusiveMaximum",
ipath,
spath,
tr(loc, "must be < \{m}", "必须 < \{m}"),
),
)
}
None => ()
}
match kw.minimum {
Some(m) =>
if n < m {
errors.push(
err(
"minimum",
ipath,
spath,
tr(loc, "must be >= \{m}", "必须 >= \{m}"),
),
)
}
None => ()
}
match kw.exclusive_minimum {
Some(m) =>
if n <= m {
errors.push(
err(
"exclusiveMinimum",
ipath,
spath,
tr(loc, "must be > \{m}", "必须 > \{m}"),
),
)
}
None => ()
}
}
if v is String(sv) {
match kw.min_length {
Some(ml) =>
if str_code_points(sv) < ml {
errors.push(
err(
"minLength",
ipath,
spath,
tr(
loc,
"must NOT have fewer than \{ml} characters",
"字符数不能少于 \{ml}",
),
),
)
}
None => ()
}
match kw.max_length {
Some(ml) =>
if str_code_points(sv) > ml {
errors.push(
err(
"maxLength",
ipath,
spath,
tr(
loc,
"must NOT have more than \{ml} characters",
"字符数不能超过 \{ml}",
),
),
)
}
None => ()
}
match kw.format {
Some(f) =>
if root.ctx.assert_format {
match check_format(f, sv) {
Some(false) =>
errors.push(
err(
"format",
ipath,
spath,
tr(
loc,
"must match format \"\{f}\"",
"必须符合格式 \"\{f}\"",
),
),
)
_ => ()
}
}
None => ()
}
match kw.pattern {
Some((re, src)) =>
match re.execute(sv) {
Some(_) => ()
None =>
errors.push(
err(
"pattern",
ipath,
spath,
tr(
loc,
"must match pattern \"\{src}\"",
"必须匹配模式 \"\{src}\"",
),
),
)
}
None => ()
}
}
if v is Array(arr) {
match kw.min_items {
Some(m) =>
if arr.length() < m {
errors.push(
err(
"minItems",
ipath,
spath,
tr(
loc,
"must NOT have fewer than \{m} items",
"元素个数不能少于 \{m}",
),
),
)
}
None => ()
}
match kw.max_items {
Some(m) =>
if arr.length() > m {
errors.push(
err(
"maxItems",
ipath,
spath,
tr(
loc,
"must NOT have more than \{m} items",
"元素个数不能超过 \{m}",
),
),
)
}
None => ()
}
match kw.unique_items {
Some(true) => {
let n = arr.length()
let mut dup_found = false
let mut i = 0
while i < n {
if dup_found {
break
}
let mut j = 0
while j < i {
if json_eq(arr[i], arr[j]) {
dup_found = true
break
}
j += 1
}
i += 1
}
if dup_found {
errors.push(
err(
"uniqueItems",
ipath,
spath,
tr(
loc, "must NOT have duplicate items", "不能包含重复元素",
),
),
)
}
}
_ => ()
}
// 2020-12:prefixItems 约束前缀,items 约束其余元素
let pcount = match kw.prefix_items {
Some(p) => p.length()
None => 0
}
for i in 0.. {
spath.push("items")
let _ = validate_node(root, it.node, arr[i], ipath, spath, errors)
ignore(spath.pop())
}
None => ()
}
}
ignore(ipath.pop())
}
match kw.contains {
Some(cs) => {
let min_c = kw.min_contains.unwrap_or(1)
let mut count = 0
for i in 0..
if count > mc {
errors.push(
err(
"maxContains",
ipath,
spath,
tr(
loc,
"must contain no more than \{mc} valid item(s)",
"匹配 contains 的元素不能超过 \{mc} 个",
),
),
)
}
None => ()
}
}
None => ()
}
}
if v is Object(obj) {
match kw.min_properties {
Some(m) =>
if obj.length() < m {
errors.push(
err(
"minProperties",
ipath,
spath,
tr(
loc,
"must NOT have fewer than \{m} properties",
"属性个数不能少于 \{m}",
),
),
)
}
None => ()
}
match kw.max_properties {
Some(m) =>
if obj.length() > m {
errors.push(
err(
"maxProperties",
ipath,
spath,
tr(
loc,
"must NOT have more than \{m} properties",
"属性个数不能超过 \{m}",
),
),
)
}
None => ()
}
match kw.required {
Some(req) =>
for i in 0.. ()
}
match kw.properties {
Some(props) =>
for k, sub in props {
match obj.get(k) {
Some(pv) => {
ipath.push(k)
spath.push("properties")
spath.push(k)
let _ = validate_node(root, sub.node, pv, ipath, spath, errors)
ignore(spath.pop())
ignore(spath.pop())
ignore(ipath.pop())
}
None => ()
}
}
None => ()
}
// patternProperties:键名匹配正则的每个值都要通过对应模式
match kw.pattern_properties {
Some(pairs) =>
for k, ov in obj {
for i in 0.. {
ipath.push(k)
spath.push("patternProperties")
spath.push(pair.1)
let _ = validate_node(
root,
pair.2.node,
ov,
ipath,
spath,
errors,
)
ignore(spath.pop())
ignore(spath.pop())
ignore(ipath.pop())
}
None => ()
}
}
}
None => ()
}
match kw.additional_closed {
Some(true) =>
for k, _ov in obj {
if !key_is_known(kw, k) {
errors.push(
err(
"additionalProperties",
ipath,
spath,
tr(
loc,
"must NOT have additional properties (\"\{k}\")",
"不允许出现多余属性 (\"\{k}\")",
),
),
)
}
}
_ => ()
}
match kw.additional {
Some(add) =>
for k, ov in obj {
if !key_is_known(kw, k) {
ipath.push(k)
spath.push("additionalProperties")
let _ = validate_node(root, add.node, ov, ipath, spath, errors)
ignore(spath.pop())
ignore(ipath.pop())
}
}
None => ()
}
match kw.property_names {
Some(pn) =>
for k, _ov in obj {
ipath.push(k)
spath.push("propertyNames")
let _ = validate_node(
root,
pn.node,
Json::string(k),
ipath,
spath,
errors,
)
ignore(spath.pop())
ignore(ipath.pop())
}
None => ()
}
match kw.dependent_required {
Some(m) =>
for k, deps in m {
if obj.contains(k) {
for i in 0.. ()
}
match kw.dependent_schemas {
Some(m) =>
for k, ds in m {
if obj.contains(k) {
spath.push("dependentSchemas")
spath.push(k)
let _ = validate_node(root, ds.node, v, ipath, spath, errors)
ignore(spath.pop())
ignore(spath.pop())
}
}
None => ()
}
}
match kw.all_of {
Some(list) =>
for i in 0.. ()
}
match kw.any_of {
Some(list) => {
let mut matched = false
for i in 0.. ()
}
match kw.one_of {
Some(list) => {
let mut pass_count = 0
for i in 0.. ()
}
match kw.not_ {
Some(n) => {
let sub : Array[ValidationError] = []
spath.push("not")
let ok = validate_node(root, n.node, v, ipath, spath, sub)
ignore(spath.pop())
if ok {
errors.push(
err(
"not",
ipath,
spath,
tr(loc, "must NOT be valid", "不能匹配 not 模式"),
),
)
}
}
None => ()
}
match kw.if_ {
Some(i) => {
let sub : Array[ValidationError] = []
spath.push("if")
let ok = validate_node(root, i.node, v, ipath, spath, sub)
ignore(spath.pop())
if ok {
match kw.then_ {
Some(t) => {
spath.push("then")
let _ = validate_node(root, t.node, v, ipath, spath, errors)
ignore(spath.pop())
}
None => ()
}
} else {
match kw.else_ {
Some(e) => {
spath.push("else")
let _ = validate_node(root, e.node, v, ipath, spath, errors)
ignore(spath.pop())
}
None => ()
}
}
}
None => ()
}
// 跨字段动态规则(x-rules):在其余关键词之后判定
match kw.rules {
Some(exprs) =>
match kw.rule_srcs {
Some(srcs) =>
for i in 0.. ()
}
None => ()
}
errors.length() == start_len
}