///|
fn field(fields : Map[String, Json], key : String) -> Json raise QueueError {
match fields.get(key) {
Some(value) => value
None => raise Invalid("missing required field")
}
}
///|
fn[T : @json.FromJson] decode(value : Json) -> T raise QueueError {
@json.from_json(value) catch {
_ => raise Invalid("wrong field type")
}
}
///|
fn integer(value : Json) -> Int raise QueueError {
let x : Double = decode(value)
if x < -2147483648.0 || x > 2147483647.0 || x != x.to_int().to_double() {
raise Invalid("expected exact 32-bit integer")
}
x.to_int()
}
///|
fn number(fields : Map[String, Json], key : String) -> Double raise QueueError {
decode(field(fields, key))
}
///|
fn count(fields : Map[String, Json], key : String) -> Int raise QueueError {
integer(field(fields, key))
}
///|
fn known(
fields : Map[String, Json],
names : Array[String],
) -> Unit raise QueueError {
for key, _ in fields {
if !names.contains(key) {
raise Invalid("unknown operation field")
}
}
}
///|
fn mmc_fields(fields : Map[String, Json]) -> Mmc raise QueueError {
{
arrival: number(fields, "arrival"),
service: number(fields, "service"),
servers: count(fields, "servers"),
}
}
///|
fn request(request : Json) -> Json raise QueueError {
guard request is Object(fields) else {
raise Invalid("request must be object")
}
let op : String = decode(field(fields, "op"))
let result = Map([])
result["ok"] = @json.to_json(true)
result["operation"] = @json.to_json(op)
match op {
"mmc" => {
known(fields, [
"op", "arrival", "service", "servers", "time", "probability", "state",
])
let q = mmc_fields(fields)
result["metrics"] = @json.to_json(q.analyze())
let time = number(fields, "time")
result["wait_survival"] = @json.to_json(q.wait_survival(time))
result["response_survival"] = @json.to_json(q.response_survival(time))
let p = number(fields, "probability")
result["wait_quantile"] = @json.to_json(q.wait_quantile(p))
result["response_quantile"] = @json.to_json(q.response_quantile(p))
let state = count(fields, "state")
result["state_probability"] = @json.to_json(q.state_probability(state))
result["state_tail"] = @json.to_json(q.state_tail(state))
}
"finite" => {
known(fields, [
"op", "arrival", "service", "servers", "capacity", "abandonment",
])
let q : FiniteQueue = {
arrival: number(fields, "arrival"),
service: number(fields, "service"),
servers: count(fields, "servers"),
capacity: count(fields, "capacity"),
abandonment: number(fields, "abandonment"),
}
result["metrics"] = @json.to_json(q.analyze())
result["outcomes"] = @json.to_json(q.outcomes())
}
"engset" => {
known(fields, ["op", "sources", "channels", "per_source", "service"])
let q : Engset = {
sources: count(fields, "sources"),
channels: count(fields, "channels"),
per_source: number(fields, "per_source"),
service: number(fields, "service"),
}
result["metrics"] = @json.to_json(q.analyze())
}
"mg1" => {
known(fields, ["op", "arrival", "mean_service", "second_moment"])
result["metrics"] = @json.to_json(
mg1(
number(fields, "arrival"),
number(fields, "mean_service"),
number(fields, "second_moment"),
),
)
}
"staffing" => {
known(fields, [
"op", "arrival", "service", "target", "time", "maximum_utilization", "maximum_servers",
])
result["staffing"] = @json.to_json(
sla_staffing(
number(fields, "arrival"),
number(fields, "service"),
number(fields, "target"),
number(fields, "time"),
maximum_utilization=number(fields, "maximum_utilization"),
maximum_servers=count(fields, "maximum_servers"),
),
)
}
"loss" => {
known(fields, ["op", "offered", "channels"])
result["blocking"] = @json.to_json(
erlang_b(number(fields, "offered"), count(fields, "channels")),
)
}
_ => network_request(op, fields, result)
}
@json.to_json(result)
}
///|
fn failure(code : String) -> Json {
@json.to_json({ "ok": @json.to_json(false), "code": @json.to_json(code) })
}
///|
fn json_budget(text : String) -> Unit raise QueueError {
if text.length() > 1000000 {
raise Limit("request exceeds one million code units")
}
let mut depth = 0
let mut quoted = false
let mut escaped = false
for c in text.iter() {
if quoted {
if escaped {
escaped = false
} else if c == '\\' {
escaped = true
} else if c == '"' {
quoted = false
}
} else if c == '"' {
quoted = true
} else if c == '[' || c == '{' {
depth += 1
if depth > 16 {
raise Limit("request nesting exceeds sixteen")
}
} else if c == ']' || c == '}' {
depth -= 1
}
}
}
///|
/// Bounded pure JSON transport. Errors do not echo supplied data. Duplicate
/// keys use core JSON's last-value behavior. CLI wraps this function verbatim.
pub fn process_json(text : String) -> String {
let result = try {
json_budget(text)
let parsed = @json.parse(text) catch {
_ => raise Invalid("malformed JSON")
}
request(parsed)
} catch {
Invalid(_) => failure("invalid")
Numerical(_) => failure("numerical")
Limit(_) => failure("limit")
Unstable(_) => failure("unstable")
Infeasible(_) => failure("infeasible")
}
result.stringify()
}