Current section

Files

Jump to
jsonlogic src jsonlogic internal decoding.gleam
Raw

src/jsonlogic/internal/decoding.gleam

import gleam/bool
import gleam/dict
import gleam/dynamic
import gleam/dynamic/decode
import gleam/float
import gleam/int
import gleam/json
import gleam/list
import gleam/option
import gleam/result
import gleam/string
import jsonlogic/error
import jsonlogic/internal/operator
import jsonlogic/internal/rule
import lenient_parse
pub fn decode_rule_string(
rule: String,
) -> Result(rule.Rule, error.EvaluationError) {
json.parse(rule, using: decode.dynamic)
|> result.map_error(error.JsonDecodeError)
|> result.try(decode_rule)
}
pub fn decode_rule(
rule: dynamic.Dynamic,
) -> Result(rule.Rule, error.EvaluationError) {
case dynamic.classify(rule) {
"Bool" | "String" | "Int" | "Float" | "Nil" | "List" ->
decode_literal(rule)
|> result.map(rule.Literal)
"Dict" -> {
let decoding_result =
decode.run(rule, decode.dict(decode.string, decode.dynamic))
|> result.map(dict.to_list)
|> result.map_error(error.DecodeError)
use decoded_entries <- result.try(decoding_result)
let raw_rule =
list.first(decoded_entries)
|> result.map_error(fn(_) {
error.InvalidRuleError("no operator in rule")
})
|> result.try(fn(rule) {
decode_rule(rule.1)
|> result.map(fn(values) {
case values {
rule.Literal(rule.ArrayLiteral(inner_values)) -> #(
rule.0,
inner_values,
)
other_rule -> #(rule.0, [other_rule])
}
})
})
use #(operator, values) <- result.try(raw_rule)
use typed_operator <- result.map(decode_operator(operator))
rule.Operation(typed_operator, values)
}
unrecognized -> panic as { "Unrecognized rule: " <> unrecognized }
}
}
fn decode_literal(
literal: dynamic.Dynamic,
) -> Result(rule.JsonLiteral, error.EvaluationError) {
case dynamic.classify(literal) {
"Bool" -> {
let assert Ok(decoded) = decode.run(literal, decode.bool)
Ok(rule.BoolLiteral(decoded))
}
"String" -> {
let assert Ok(decoded) = decode.run(literal, decode.string)
Ok(rule.StringLiteral(decoded))
}
"Int" -> {
let assert Ok(decoded) = decode.run(literal, decode.int)
Ok(rule.IntLiteral(decoded))
}
"Float" -> {
let assert Ok(decoded) = decode.run(literal, decode.float)
Ok(rule.FloatLiteral(decoded))
}
"Nil" -> Ok(rule.NilLiteral)
"List" -> {
let assert Ok(decoded) = decode.run(literal, decode.list(decode.dynamic))
list.try_map(decoded, decode_rule)
|> result.map(rule.ArrayLiteral)
}
t -> panic as { "Unsupported literal type: " <> t }
}
}
pub fn decode_operator(
operator: String,
) -> Result(operator.Operator, error.EvaluationError) {
case operator {
"val" -> Ok(operator.Value)
"var" -> Ok(operator.Variable)
"missing" -> Ok(operator.Missing)
"missing_some" -> Ok(operator.MissingSome)
"==" -> Ok(operator.AbstractEquals)
"!=" -> Ok(operator.AbstractNotEquals)
"===" -> Ok(operator.StrictEquals)
"!==" -> Ok(operator.StrictNotEquals)
">" -> Ok(operator.GreaterThan)
"<" -> Ok(operator.LessThan)
">=" -> Ok(operator.GreaterThanOrEqual)
"<=" -> Ok(operator.LessThanOrEqual)
"!" -> Ok(operator.Negate)
"!!" -> Ok(operator.DoubleNegate)
"or" -> Ok(operator.Or)
"and" -> Ok(operator.And)
"?:" -> Ok(operator.Conditional)
"in" -> Ok(operator.In)
"cat" -> Ok(operator.Concatenate)
"%" -> Ok(operator.Modulo)
"max" -> Ok(operator.Max)
"min" -> Ok(operator.Min)
"+" -> Ok(operator.Plus)
"*" -> Ok(operator.Multiply)
"-" -> Ok(operator.Minus)
"/" -> Ok(operator.Divide)
"substr" -> Ok(operator.Substring)
"merge" -> Ok(operator.Merge)
"if" -> Ok(operator.If)
"filter" -> Ok(operator.Filter)
"map" -> Ok(operator.Map)
"reduce" -> Ok(operator.Reduce)
"all" -> Ok(operator.All)
"none" -> Ok(operator.None)
"some" -> Ok(operator.Some)
_ -> Error(error.UnknownOperatorError(operator))
}
}
pub fn dynamic_to_int(
input: dynamic.Dynamic,
) -> Result(Int, error.EvaluationError) {
case dynamic.classify(input) {
"String" -> {
let assert Ok(decoded) = decode.run(input, decode.string)
case decoded {
"" -> Ok(0)
_ ->
float.parse(decoded)
|> result.map(float.truncate)
|> result.try_recover(fn(_) { int.parse(decoded) })
|> result.map_error(fn(_) { error.NaNError })
}
}
"Int" -> {
let assert Ok(decoded) = decode.run(input, decode.int)
Ok(decoded)
}
"Float" -> {
let assert Ok(decoded) = decode.run(input, decode.float)
Ok(float.truncate(decoded))
}
"Bool" -> {
let assert Ok(decoded) = decode.run(input, decode.bool)
bool_to_float(decoded)
|> float.truncate
|> Ok
}
t -> panic as { "Cannot convert type: " <> t }
}
}
pub fn dynamic_to_float(
input: dynamic.Dynamic,
) -> Result(Float, error.EvaluationError) {
case dynamic.classify(input) {
"String" -> {
let assert Ok(decoded) = decode.run(input, decode.string)
case decoded {
"" -> Ok(0.0)
_ ->
lenient_parse.to_float(decoded)
|> result.map_error(fn(_) { error.NaNError })
}
}
"Float" -> {
let assert Ok(decoded) = decode.run(input, decode.float)
Ok(decoded)
}
"Int" -> {
let assert Ok(decoded) = decode.run(input, decode.int)
Ok(int.to_float(decoded))
}
"Bool" -> {
let assert Ok(decoded) = decode.run(input, decode.bool)
bool_to_float(decoded)
|> Ok
}
"Nil" -> Ok(0.0)
_ -> Error(error.NaNError)
}
}
pub fn dynamic_to_bool(
input: dynamic.Dynamic,
) -> Result(#(Bool, dynamic.Dynamic), error.EvaluationError) {
case dynamic.classify(input) {
"Bool" -> {
let assert Ok(decoded) = decode.run(input, decode.bool)
Ok(#(decoded, input))
}
"Int" -> {
let assert Ok(decoded) = decode.run(input, decode.int)
Ok(#(decoded != 0, input))
}
"Float" -> {
let assert Ok(decoded) = decode.run(input, decode.float)
Ok(#(decoded != 0.0, input))
}
"String" -> {
let assert Ok(decoded) = decode.run(input, decode.string)
Ok(#(decoded != "", input))
}
"List" -> {
let assert Ok(decoded) = decode.run(input, decode.list(decode.dynamic))
Ok(#(decoded != [], input))
}
"Nil" -> Ok(#(False, input))
"Dict" -> {
let assert Ok(decoded) =
decode.run(input, decode.dict(decode.dynamic, decode.dynamic))
Ok(#(dict.size(decoded) != 0, input))
}
t -> panic as { "Cannot convert type: " <> t }
}
}
pub fn dynamic_to_string(
input: dynamic.Dynamic,
) -> Result(String, error.EvaluationError) {
decode.run(
input,
decode.one_of(decode.string, or: [
decode.float |> decode.map(float.to_string),
decode.int |> decode.map(int.to_string),
decode.bool |> decode.map(bool.to_string) |> decode.map(string.lowercase),
]),
)
|> result.map_error(error.DecodeError)
}
pub fn dynamic_to_array(
input: dynamic.Dynamic,
) -> Result(List(dynamic.Dynamic), error.EvaluationError) {
decode.run(
input,
decode.one_of(
decode.optional(decode.list(decode.dynamic))
|> decode.map(fn(value) {
case value {
option.None -> []
option.Some(value) -> value
}
}),
or: [
decode.dynamic |> decode.map(list.wrap),
],
),
)
|> result.map_error(error.DecodeError)
}
pub fn bool_to_float(value: Bool) -> Float {
case value {
True -> 1.0
False -> 0.0
}
}
pub fn decode_data(
key: dynamic.Dynamic,
data: dynamic.Dynamic,
or default: option.Option(dynamic.Dynamic),
) -> Result(dynamic.Dynamic, error.EvaluationError) {
case
key == dynamic.nil(),
key == dynamic.string(""),
key == dynamic.list([])
{
True, _, _ | _, True, _ | _, _, True -> Ok(data)
_, _, _ -> {
use key <- result.map(dynamic_to_string(key))
let keys = string.split(key, on: ".")
list.map(keys, fn(k) {
int.parse(k)
|> result.map(dynamic.int)
|> result.unwrap(dynamic.string(k))
})
|> do_decode_data(data, default)
}
}
}
pub fn decode_data_val(
key: List(dynamic.Dynamic),
data: dynamic.Dynamic,
) -> Result(dynamic.Dynamic, error.EvaluationError) {
Ok(do_decode_data(key, data, option.None))
}
fn do_decode_data(
key: List(dynamic.Dynamic),
data: dynamic.Dynamic,
or default: option.Option(dynamic.Dynamic),
) -> dynamic.Dynamic {
decode.run(data, decode.at(key, decode.dynamic))
|> result.lazy_unwrap(fn() {
case default {
option.Some(default) -> default
option.None -> dynamic.nil()
}
})
}