///|
fn json_number_value(value : Double, representation : String?) -> Value {
match representation {
Some(text) => {
let is_integral = !text.contains(".") &&
!text.contains("e") &&
!text.contains("E")
if is_integral {
let parsed = @string.parse_int64(text) catch {
_ => return Value::Double(value)
}
return Value::Int(parsed)
}
Value::Double(value)
}
None => Value::Double(value)
}
}
///|
/// Convert JSON-expressible data to CEL values.
pub fn value_from_json(json : Json) -> Value raise JsonBridgeError {
match json {
Json::Null => Value::Null
Json::True => Value::Bool(true)
Json::False => Value::Bool(false)
Json::Number(value, repr~) => json_number_value(value, repr)
Json::String(value) => Value::String(value)
Json::Array(items) => Value::List(items.map(item => value_from_json(item)))
Json::Object(fields) => {
let names = [ for name, _ in fields => name ]
names.sort()
let entries : Array[ValueEntry] = []
for name in names {
let value = fields.get(name).unwrap()
entries.push({ key: Value::String(name), value: value_from_json(value) })
}
Value::Map(entries)
}
}
}
///|
fn tagged_value(kind : String, value : Json) -> Json {
Json::object({ "@type": Json::string(kind), "value": value })
}
///|
/// Convert a CEL value to stable JSON. UInt and Bytes use tagged objects.
pub fn Value::to_json(self : Value) -> Json raise JsonBridgeError {
match self {
Value::Null => Json::null()
Value::Bool(value) => Json::boolean(value)
Value::Int(value) => Json::number(value.to_double(), repr=value.to_string())
Value::UInt(value) => tagged_value("uint", Json::string(value.to_string()))
Value::Double(value) => Json::number(value)
Value::String(value) => Json::string(value)
Value::Bytes(value) =>
tagged_value(
"bytes",
Json::array(
value.to_array().map(byte => Json::number(byte.to_int().to_double())),
),
)
Value::List(values) => Json::array(values.map(value => value.to_json()))
Value::Map(entries) => {
let object : Map[String, Json] = Map([])
for entry in entries {
match entry.key {
Value::String(key) => object[key] = entry.value.to_json()
other =>
raise InvalidJsonValue(
message="JSON output requires string map keys, found \{other.kind_name()}",
)
}
}
Json::object(object)
}
}
}
///|
pub fn Value::to_json_string(
self : Value,
indent? : Int = 2,
) -> String raise JsonBridgeError {
self.to_json().stringify(indent~)
}
///|
fn split_map_types(body : String) -> (String, String)? {
let mut depth = 0
for index = 0; index < body.length(); index = index + 1 {
match body[index] {
'<' => depth = depth + 1
'>' => depth = depth - 1
',' =>
if depth == 0 {
return Some((body[0:index].to_owned(), body[index + 1:].to_owned()))
}
_ => ()
}
}
None
}
///|
/// Parse the compact type syntax accepted by the CLI type environment.
pub fn parse_type_name(name : String) -> CelType raise JsonBridgeError {
let name = name.trim().to_owned()
match name {
"dyn" => CelType::Dyn
"null" => CelType::Null
"bool" => CelType::Bool
"int" => CelType::Int
"uint" => CelType::UInt
"double" => CelType::Double
"string" => CelType::String
"bytes" => CelType::Bytes
_ => {
if name.has_prefix("list<") && name.has_suffix(">") {
let body = name[5:name.length() - 1].to_owned()
return CelType::List(parse_type_name(body))
}
if name.has_prefix("map<") && name.has_suffix(">") {
let body = name[4:name.length() - 1].to_owned()
match split_map_types(body) {
Some((key, value)) =>
return CelType::Map(parse_type_name(key), parse_type_name(value))
None => ()
}
}
raise InvalidTypeName(name~)
}
}
}
///|
pub fn type_env_from_json(json : Json) -> TypeEnv raise JsonBridgeError {
match json {
Json::Object(fields) => {
let mut env = TypeEnv::new()
for name, value in fields {
match value {
Json::String(type_name) =>
env = env.with_variable(name, parse_type_name(type_name))
_ =>
raise InvalidJsonValue(
message="type for '\{name}' must be a string",
)
}
}
env
}
_ =>
raise InvalidJsonValue(message="type environment must be a JSON object")
}
}
///|
pub fn activation_from_json(json : Json) -> Activation raise JsonBridgeError {
match json {
Json::Object(fields) => {
let mut activation = Activation::new()
for name, value in fields {
activation = activation.bind(name, value_from_json(value))
}
activation
}
_ => raise InvalidJsonValue(message="activation must be a JSON object")
}
}
///|
pub fn Activation::type_env(self : Activation) -> TypeEnv {
let mut env = TypeEnv::new()
for name, value in self.values {
env = env.with_variable(name, value.cel_type())
}
env
}
///|
fn diagnostic_json(diagnostic : Diagnostic) -> Json {
Json::object({
"code": Json::string(diagnostic.code),
"message": Json::string(diagnostic.message),
"severity": Json::string(diagnostic.severity.label()),
"span": Json::object({
"start": Json::number(diagnostic.span.start.to_double()),
"end": Json::number(diagnostic.span.end.to_double()),
"line": Json::number(diagnostic.span.line.to_double()),
"column": Json::number(diagnostic.span.column.to_double()),
}),
})
}
///|
pub fn diagnostics_to_json(diagnostics : Array[Diagnostic]) -> Json {
Json::array(diagnostics.map(diagnostic_json))
}