///|
fn validate_keys(
value : Json,
allowed : Array[String],
path : String,
) -> Unit raise InputError {
match value {
Object(fields) =>
for key, _ in fields {
if !allowed.contains(key) {
raise UnknownField(path + "[" + Json(key).stringify() + "]")
}
}
_ => raise InvalidInput("\{path} must be an object")
}
}
///|
fn field(value : Json, key : String) -> Json? raise InputError {
match value {
Object(m) => m.get(key)
_ => raise InvalidInput("expected an object")
}
}
///|
fn string_field(
value : Json,
key : String,
default : String,
) -> String raise InputError {
match field(value, key) {
None => default
Some(String(s)) => s
_ => raise InvalidInput("\{key} must be a string")
}
}
///|
fn integer_field(
value : Json,
key : String,
default : Int64,
maximum : Int64,
) -> Int64 raise InputError {
match field(value, key) {
None => default
Some(Number(n, ..)) if n >= 0.0 && n <= maximum.to_double() => {
let i = n.to_int64()
if i.to_double() != n {
raise InvalidInput("\{key} must be an integer")
}
i
}
_ => raise InvalidInput("\{key} must be an integer in 0..\{maximum}")
}
}
///|
fn parse_duplicates(value : String) -> DuplicatePolicy raise InputError {
match value {
"error" => Reject
"attempts" => Attempts
_ => raise InvalidInput("duplicates must be error or attempts")
}
}
///|
fn input_document(
value : Json,
duplicates : DuplicatePolicy,
path : String,
) -> Report raise InputError {
validate_keys(value, ["xml", "source"], path)
let source = string_field(value, "source", "")
let xml = string_field(value, "xml", "")
parse_report(xml, source~, duplicates~)
}
///|
fn input_run(
value : Json,
duplicates : DuplicatePolicy,
path : String,
) -> Report raise InputError {
match value {
Array(docs) => {
if docs.is_empty() || docs.length() > 50 {
raise InvalidInput("a run requires 1..50 documents")
}
let reports : Array[Report] = []
for i = 0; i < docs.length(); i = i + 1 {
reports.push(input_document(docs[i], duplicates, "\{path}[\{i}]"))
}
merge_reports(reports, duplicates~)
}
_ => input_document(value, duplicates, path)
}
}
///|
/// Batch/embedding protocol. Errors are structured and never converted to empty reports.
pub fn evaluate_request(text : String) -> Json {
evaluate_request_limited(text, 100000000L)
}
///|
fn evaluate_request_limited(text : String, max_report_units : Int64) -> Json {
try {
if text.length() > 20000000 {
raise InvalidInput("JSON request exceeds 20000000 code units")
}
let request = @json.parse(text, reject_duplicate_keys=true) catch {
error => raise InvalidInput("invalid JSON: \{error}")
}
let operation = string_field(request, "op", "compare")
if operation == "parse" {
validate_keys(request, ["op", "document"], "$")
let doc = match field(request, "document") {
Some(doc) => doc
None => raise InvalidInput("missing document")
}
validate_keys(doc, ["xml", "source"], "$[\"document\"]")
let report = parse_document(
string_field(doc, "xml", ""),
source=string_field(doc, "source", ""),
)
return {
"ok": true,
"exit_code": 0,
"report": report.to_json(max_units=max_report_units),
}
}
if operation != "compare" {
raise InvalidInput("unknown operation: \{operation}")
}
validate_keys(
request,
[
"op", "baselines", "candidate", "duplicates", "fail_on", "slowdown_pct",
"slowdown_floor_us",
],
"$",
)
let duplicates = parse_duplicates(
string_field(request, "duplicates", "error"),
)
let history = match field(request, "baselines") {
Some(Array(runs)) if !runs.is_empty() && runs.length() <= 50 => {
let reports : Array[Report] = []
for i = 0; i < runs.length(); i = i + 1 {
reports.push(input_run(runs[i], duplicates, "$[\"baselines\"][\{i}]"))
}
reports
}
_ => raise InvalidInput("baselines must contain 1..50 chronological runs")
}
let candidate = match field(request, "candidate") {
Some(value) => input_run(value, duplicates, "$[\"candidate\"]")
None => raise InvalidInput("missing candidate")
}
let fail_on = match field(request, "fail_on") {
None => default_config().fail_on.iter().to_array()
Some(Array(items)) =>
items.map(item => {
match item {
String(s) => s
_ => raise InvalidInput("fail_on must contain strings")
}
})
_ => raise InvalidInput("fail_on must be an array")
}
let config = diff_config(
slowdown_pct=integer_field(request, "slowdown_pct", 50L, 1000000L).to_int(),
slowdown_floor_us=integer_field(
request, "slowdown_floor_us", 200000L, 9000000000000L,
),
fail_on~,
)
let result = compare(history, candidate, config~)
{
"ok": true,
"exit_code": Json(result.exit_code()),
"report": result.to_json(max_units=max_report_units),
}
} catch {
UnknownField(path) =>
{
"ok": false,
"exit_code": 2,
"error_code": "unknown_field",
"error_path": Json(clipped(path, 2048)),
"error": Json("unknown field at " + clipped(path, 2048)),
}
Rejected(code, message) =>
{
"ok": false,
"exit_code": 2,
"error_code": Json(code),
"error": Json(clipped(message, 2048)),
}
error =>
{
"ok": false,
"exit_code": 2,
"error_code": "invalid_request",
"error": Json(clipped(error.to_string(), 2048)),
}
}
}