///|
/// Validate a practical, explicit subset of CQL2 JSON for STAC Filter.
/// Unsupported operators fail rather than passing an unexamined expression.
fn validate_cql2_filter(expr : Json, depth : Int) -> Unit raise StacError {
if depth > 16 {
raise Invalid("CQL2 filter too deeply nested")
}
let op = string_field(expr, "op")
let args = match field(expr, "args") {
Some(Array(xs)) => xs
_ => raise Invalid("CQL2 filter requires args")
}
match op {
"and" | "or" => {
if args.length() < 2 {
raise Invalid("CQL2 logical operator needs two args")
}
for arg in args {
validate_cql2_filter(arg, depth + 1)
}
}
"not" => {
if args.length() != 1 {
raise Invalid("CQL2 not needs one arg")
}
validate_cql2_filter(args[0], depth + 1)
}
"=" | "<>" | "<" | "<=" | ">" | ">=" => {
if args.length() != 2 {
raise Invalid("CQL2 comparison needs two args")
}
validate_cql2_property(args[0])
validate_cql2_literal(args[1])
}
"between" => {
if args.length() != 3 {
raise Invalid("CQL2 between needs three args")
}
validate_cql2_property(args[0])
validate_cql2_literal(args[1])
validate_cql2_literal(args[2])
}
_ => raise Invalid("unsupported CQL2 operator")
}
}
///|
fn validate_cql2_property(value : Json) -> Unit raise StacError {
ignore(string_field(value, "property"))
}
///|
fn validate_cql2_literal(value : Json) -> Unit raise StacError {
match value {
String(_) | Number(_, ..) | True | False | Null => ()
Object(_) => {
let timestamp = string_field(value, "timestamp")
validate_timestamp(timestamp)
}
_ => raise Invalid("invalid CQL2 literal")
}
}