// QueryX — query DSL with to_json / from_json.
// ---------------------------------------------------------------------------
// Order / Page / Query — top-level query types
// ---------------------------------------------------------------------------
///|
pub(all) struct Order(String) derive(Debug)
///|
pub(all) struct Page {
after : String
size : Int
} derive(Debug, ToJson, FromJson)
///|
pub(all) struct Query {
filter : Expr
order_by : Order?
page : Page?
set : Json?
} derive(Debug)
// ---------------------------------------------------------------------------
// Order :: ToJson / FromJson
// ---------------------------------------------------------------------------
///|
pub impl ToJson for Order with fn to_json(self : Order) -> Json {
let s = self.0
if s == "" {
return Json::array([])
}
let parts = split_by(s, ',')
let arr : Array[Json] = []
for part in parts {
let kv = split_by(part, ':')
if kv.length() >= 2 {
let map = Map([], capacity=1)
map.set(kv[0], Json::string(kv[1]))
arr.push(Json::object(map))
}
}
Json::array(arr)
}
///|
pub impl @json.FromJson for Order with fn from_json(
json : Json,
path : @json.JsonPath,
) -> Order raise @json.JsonDecodeError {
match json {
Json::Array(arr) => {
let parts : Array[String] = []
for i = 0; i < arr.length(); i = i + 1 {
let entry = arr[i]
match entry {
Json::Object(obj) =>
for field, dir in obj {
match dir {
Json::String(d) => parts.push(field + ":" + d)
_ =>
raise @json.JsonDecodeError(
(path.add_index(i), "direction must be a string"),
)
}
}
_ =>
raise @json.JsonDecodeError(
(path.add_index(i), "each entry must be a JSON object"),
)
}
}
Order(join_with(parts, ","))
}
_ => raise @json.JsonDecodeError((path, "expected a JSON array"))
}
}
// ---------------------------------------------------------------------------
// Query :: ToJson / FromJson
// ---------------------------------------------------------------------------
///|
pub impl ToJson for Query with fn to_json(self : Query) -> Json {
let map = Map([])
map.set("filter", self.filter.to_json())
match self.order_by {
Some(o) => map.set("order_by", o.to_json())
None => ()
}
match self.page {
Some(p) => map.set("page", p.to_json())
None => ()
}
match self.set {
Some(s) => map.set("set", s)
None => ()
}
Json::object(map)
}
///|
pub impl @json.FromJson for Query with fn from_json(
json : Json,
path : @json.JsonPath,
) -> Query raise @json.JsonDecodeError {
match json {
Json::Object(map) => {
let filter = match map.get("filter") {
Some(f) => @json.from_json(f)
None => Expr::Empty
}
let order_by : Order? = match map.get("order_by") {
Some(o) => Some(@json.from_json(o))
None => None
}
let page : Page? = match map.get("page") {
Some(p) => Some(@json.from_json(p))
None => None
}
let set = match map.get("set") {
Some(s) => Some(s)
None => None
}
{ filter, order_by, page, set }
}
_ => raise @json.JsonDecodeError((path, "expected a JSON object"))
}
}
// ---------------------------------------------------------------------------
// String helpers (shared by Order)
// ---------------------------------------------------------------------------
///|
fn split_by(s : String, delim : Char) -> Array[String] {
split_rec(s, delim, 0, 0, [])
}
///|
fn split_rec(
s : String,
delim : Char,
start : Int,
pos : Int,
acc : Array[String],
) -> Array[String] {
if pos >= s.length() {
let last = s[start:].trim().to_owned()
if last != "" {
acc.push(last)
}
acc
} else if s[pos:pos + 1] == delim.to_string() {
let part = s[start:pos].trim().to_owned()
if part != "" {
acc.push(part)
}
split_rec(s, delim, pos + 1, pos + 1, acc)
} else {
split_rec(s, delim, start, pos + 1, acc)
}
}
///|
fn join_with(parts : Array[String], sep : String) -> String {
if parts.length() == 0 {
return ""
}
let buf = StringBuilder(size_hint=0)
buf.write_string(parts[0])
for i = 1; i < parts.length(); i = i + 1 {
buf.write_string(sep)
buf.write_string(parts[i])
}
buf.to_string()
}