///|
fn gate_fields(
value : Json,
allowed : Array[String],
location : String,
) -> Map[String, Json] raise EvalError {
guard value is Object(fields) else {
raise InvalidConfig(location + " must be an object")
}
for key in fields.keys() {
if !allowed.contains(key) {
raise InvalidConfig(location + ": unknown field " + key)
}
}
fields
}
///|
fn gate_string(
fields : Map[String, Json],
key : String,
location : String,
) -> String raise EvalError {
match fields.get(key) {
Some(String(value)) => value
_ => raise InvalidConfig(location + "." + key + " must be a string")
}
}
///|
/// Parse a version-1 policy. Unknown fields are rejected to catch misspelled rules.
pub fn GatePolicy::from_json_string(
source : String,
) -> GatePolicy raise EvalError {
let json = @json.parse(source) catch {
_ => raise InvalidConfig("gate policy must be valid JSON")
}
let fields = gate_fields(
json,
["format_version", "undefined_policy", "rules"],
"gate policy",
)
if gate_string(fields, "format_version", "gate policy") != "1" {
raise InvalidConfig("unsupported gate policy format_version; expected 1")
}
let undefined_policy = if fields.contains("undefined_policy") {
gate_string(fields, "undefined_policy", "gate policy")
} else {
"fail"
}
let entries = match fields.get("rules") {
Some(Array(values)) if values.length() > 0 && values.length() <= 1000 =>
values
_ => raise InvalidConfig("gate policy.rules must contain 1 to 1000 rules")
}
let rules : Array[GateRule] = []
for index, entry in entries {
let location = "gate policy.rules[\{index}]"
let rule = gate_fields(
entry,
["kind", "metric", "threshold", "category_id"],
location,
)
let kind = gate_string(rule, "kind", location)
if kind != "minimum" && kind != "max_drop" {
raise InvalidConfig(location + ".kind must be minimum or max_drop")
}
let metric = gate_string(rule, "metric", location)
let threshold = match rule.get("threshold") {
Some(Number(value, ..)) => value
_ => raise InvalidConfig(location + ".threshold must be a JSON number")
}
let category : Int32? = match rule.get("category_id") {
None | Some(Null) => None
Some(value) => {
guard value is Number(number, ..) &&
finite(number) &&
number >= -2147483648.0 &&
number <= 2147483647.0 else {
raise InvalidConfig(
location + ".category_id must be null or a signed 32-bit integer",
)
}
let id : Int32 = @json.from_json(value) catch {
_ =>
raise InvalidConfig(
location + ".category_id must be null or a signed 32-bit integer",
)
}
if id.to_double() != number {
raise InvalidConfig(location + ".category_id must be an integer")
}
Some(id)
}
}
rules.push(
new_gate_rule(kind, metric, threshold, category) catch {
error => raise InvalidConfig(location + ": " + error.message())
},
)
}
GatePolicy::new(rules, undefined_policy~)
}
///|
pub impl ToJson for GatePolicy with fn to_json(self : GatePolicy) -> Json {
let rules = self.rules.map(rule => {
let fields : Map[String, Json] = {
"kind": Json::string(rule.kind),
"metric": Json::string(rule.metric),
"threshold": Json::number(rule.threshold),
"category_id": match rule.category_id {
Some(id) => Json(id)
None => Json::null()
},
}
Json::object(fields)
})
Json::object({
"format_version": Json::string("1"),
"undefined_policy": Json::string(self.undefined_policy),
"rules": Json::array(rules),
})
}
///|
pub extend GatePolicy with ToJson::{to_json}
///|
/// Serialize ordered rules, including explicit null for an overall scope.
pub fn GatePolicy::to_json_string(self : GatePolicy) -> String {
self.to_json().stringify()
}