use super::QueryOrder;
use crate::{
extension::{JsonObjectExt, JsonValueExt},
validation::Validation,
JsonValue, Map, SharedString,
};
#[derive(Debug, Clone)]
pub struct Query {
fields: Vec<String>,
filters: Map,
sort_order: Vec<QueryOrder>,
offset: usize,
limit: usize,
extra: Map,
}
impl Query {
#[inline]
pub fn new(filters: impl Into<JsonValue>) -> Self {
let filters = filters.into().into_map_opt().unwrap_or_default();
Self {
fields: Vec::new(),
filters,
sort_order: Vec::new(),
offset: 0,
limit: 0,
extra: Map::new(),
}
}
#[inline]
pub fn from_entry(key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
Self::new(Map::from_entry(key, value))
}
#[must_use]
pub fn read_map(&mut self, data: &Map) -> Validation {
let mut validation = Validation::new();
let mut pagination_current_page = None;
let filters = &mut self.filters;
let extra = &mut self.extra;
for (key, value) in data.iter().filter(|(_, v)| !v.is_ignorable()) {
match key.as_str() {
"fields" | "columns" => {
if let Some(fields) = value.parse_str_array() {
self.fields.clear();
self.fields.extend(fields.into_iter().map(|s| s.to_owned()));
}
}
"order_by" | "sort_by" => {
if let Some(sort_order) = value.parse_str_array() {
self.sort_order.clear();
self.sort_order.extend(sort_order.into_iter().map(|s| {
if let Some(sort) = s.strip_suffix("|asc") {
QueryOrder::new(sort.to_owned(), false)
} else if let Some(sort) = s.strip_suffix("|desc") {
QueryOrder::new(sort.to_owned(), true)
} else {
QueryOrder::new(s.to_owned(), true)
}
}));
}
}
"offset" | "skip" => {
if let Some(result) = value.parse_usize() {
match result {
Ok(offset) => self.offset = offset,
Err(err) => validation.record_fail("offset", err),
}
}
}
"limit" | "page_size" => {
if let Some(result) = value.parse_isize() {
match result {
Ok(limit) => self.limit = usize::MIN.saturating_add_signed(limit),
Err(err) => validation.record_fail("limit", err),
}
}
}
"current_page" => {
if let Some(result) = value.parse_usize() {
match result {
Ok(current_page) => pagination_current_page = Some(current_page),
Err(err) => validation.record_fail("current_page", err),
}
}
}
"populate" | "translate" | "show_deleted" | "validate_only" | "no_check" => {
if let Some(result) = value.parse_bool() {
match result {
Ok(flag) => {
extra.upsert(key, flag);
}
Err(err) => validation.record_fail(key.to_owned(), err),
}
}
}
"timestamp" | "nonce" | "signature" => {
extra.upsert(key, value.clone());
}
_ => {
if let Some(value) = value.as_str().filter(|&s| s != "all") {
if key.starts_with('$') {
if let Some(expr) = value.strip_prefix('(') {
filters.upsert(key, Self::parse_logical_query(expr));
} else {
filters.upsert(key, value);
}
} else if value.starts_with('$') {
if let Some((operator, value)) = value.split_once('.') {
filters.upsert(key, Map::from_entry(operator, value));
} else {
filters.upsert(key, value);
}
} else {
filters.upsert(key, value);
}
} else {
filters.upsert(key, value.clone());
}
}
}
}
if let Some(current_page) = pagination_current_page {
self.offset = self.limit * current_page.saturating_sub(1);
}
validation
}
fn parse_logical_query(expr: &str) -> Vec<Map> {
let mut filters = Vec::new();
for expr in expr.trim_end_matches(')').split(',') {
if let Some((key, expr)) = expr.split_once('.') {
if let Some((operator, value)) = expr.split_once('.') {
let value = if value.starts_with('$') {
if let Some((operator, expr)) = value.split_once('(') {
Map::from_entry(operator, Self::parse_logical_query(expr)).into()
} else {
JsonValue::from(value)
}
} else {
JsonValue::from(value)
};
let filter = Map::from_entry(key, Map::from_entry(operator, value));
filters.push(filter);
}
}
}
filters
}
#[inline]
pub fn set_fields(&mut self, fields: Vec<String>) {
self.fields = fields;
}
#[inline]
pub fn allow_fields(&mut self, fields: &[&str]) {
if self.fields.is_empty() {
self.fields.extend(fields.iter().map(|&key| key.to_owned()));
} else {
self.fields.retain(|field| fields.contains(&field.as_str()))
}
}
#[inline]
pub fn deny_fields(&mut self, fields: &[&str]) {
self.fields
.retain(|field| !fields.contains(&field.as_str()))
}
#[inline]
pub fn add_field_alias(&mut self, expr: impl Into<String>, alias: impl Into<String>) {
self.fields.push([alias.into(), expr.into()].join(":"));
}
#[inline]
pub fn add_filter(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
self.filters.upsert(key, value);
}
#[inline]
pub fn append_filters(&mut self, filters: &mut Map) {
self.filters.append(filters);
}
#[inline]
pub fn remove_filter(&mut self, key: &str) -> Option<JsonValue> {
self.filters.remove(key)
}
#[inline]
pub fn set_extra_flag(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
self.extra.upsert(key, value);
}
#[inline]
pub fn append_extra_flags(&mut self, flags: &mut Map) {
self.extra.append(flags);
}
#[inline]
pub fn set_order(&mut self, sort_order: Vec<QueryOrder>) {
self.sort_order = sort_order;
}
#[inline]
pub fn order_by(&mut self, field: impl Into<SharedString>, descending: bool) {
let field = field.into();
self.sort_order
.retain(|order| order.field() != field.as_ref());
self.sort_order.push(QueryOrder::new(field, descending));
}
pub fn order_by_with_nulls(
&mut self,
field: impl Into<SharedString>,
descending: bool,
nulls_first: bool,
) {
let field = field.into();
self.sort_order
.retain(|order| order.field() != field.as_ref());
let mut order = QueryOrder::new(field, descending);
if nulls_first {
order.set_nulls_first();
} else {
order.set_nulls_last();
}
self.sort_order.push(order);
}
#[inline]
pub fn order_asc(&mut self, field: impl Into<SharedString>) {
let field = field.into();
self.sort_order
.retain(|order| order.field() != field.as_ref());
self.sort_order.push(QueryOrder::new(field, false));
}
#[inline]
pub fn order_desc(&mut self, field: impl Into<SharedString>) {
let field = field.into();
self.sort_order
.retain(|order| order.field() != field.as_ref());
self.sort_order.push(QueryOrder::new(field, true));
}
#[inline]
pub fn set_offset(&mut self, offset: usize) {
self.offset = offset;
}
#[inline]
pub fn set_limit(&mut self, limit: usize) {
self.limit = limit;
}
#[inline]
pub fn disable_limit(&mut self) {
self.limit = 0;
}
#[inline]
pub fn fields(&self) -> &[String] {
self.fields.as_slice()
}
#[inline]
pub fn filters(&self) -> &Map {
&self.filters
}
#[inline]
pub fn sort_order(&self) -> &[QueryOrder] {
self.sort_order.as_slice()
}
#[inline]
pub fn offset(&self) -> usize {
self.offset
}
#[inline]
pub fn limit(&self) -> usize {
self.limit
}
#[inline]
pub fn enabled(&self, flag: &str) -> bool {
self.extra.get_bool(flag).is_some_and(|b| b)
}
#[inline]
pub fn populate_enabled(&self) -> bool {
self.enabled("populate")
}
#[inline]
pub fn translate_enabled(&self) -> bool {
self.enabled("translate")
}
#[inline]
pub fn show_deleted(&self) -> bool {
self.enabled("show_deleted")
}
#[inline]
pub fn validate_only(&self) -> bool {
self.enabled("validate_only")
}
#[inline]
pub fn no_check(&self) -> bool {
self.enabled("no_check")
}
}
impl Default for Query {
#[inline]
fn default() -> Self {
Self {
fields: Vec::new(),
filters: Map::new(),
sort_order: Vec::new(),
offset: 0,
limit: 10,
extra: Map::new(),
}
}
}