Skip to content

Commit

Permalink
Split query parsing logic from query applying
Browse files Browse the repository at this point in the history
  • Loading branch information
GamePad64 committed Jan 3, 2025
1 parent a6b0dd8 commit 15ed1b3
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 36 deletions.
142 changes: 106 additions & 36 deletions notifico-core/src/http/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ use axum::Json;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Select};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::error::Error;
use std::str::FromStr;
use uuid::Uuid;

#[derive(Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[derive(Deserialize, Copy, Clone)]
pub enum SortOrder {
#[serde(alias = "ASC", alias = "asc")]
Asc,
#[serde(alias = "DESC", alias = "desc")]
Desc,
}

Expand All @@ -30,68 +31,137 @@ impl From<SortOrder> for sea_orm::Order {
}

pub trait ListableTrait: QuerySelect {
fn apply_filter(self, params: &ListQueryParams) -> anyhow::Result<Self>;
fn apply_params(self, params: &ListQueryParams) -> anyhow::Result<Self>;
fn apply_filter(self, params: &ParsedListQueryParams) -> anyhow::Result<Self>;
fn apply_params(self, params: &ParsedListQueryParams) -> anyhow::Result<Self>;
}

impl<ET> ListableTrait for Select<ET>
where
ET: EntityTrait,
<ET::Column as FromStr>::Err: Error + Send + Sync,
{
fn apply_filter(mut self, params: &ListQueryParams) -> anyhow::Result<Self> {
if let Some(filter) = &params.filter {
let filter: BTreeMap<String, Value> = serde_json::from_str(filter)?;

for (col, val) in filter.into_iter() {
let column = ET::Column::from_str(&col)?;
let filters = match val {
Value::String(v) => vec![Value::String(v)],
Value::Array(v) => v,
_ => {
bail!("Invalid filter value type: {col}. Expected string or array of strings.")
}
};

let mut values: Vec<sea_orm::Value> = vec![];
for filter in filters {
if let Ok(uuid) = Uuid::deserialize(filter.clone()) {
values.push(uuid.into());
} else if let Value::String(s) = filter {
values.push(s.into());
} else {
values.push(filter.into())
fn apply_filter(mut self, params: &ParsedListQueryParams) -> anyhow::Result<Self> {
let Some(filter) = &params.filter else {
return Ok(self);
};

for (col, val) in filter.iter() {
let column = ET::Column::from_str(col)?;
match val {
FilterOp::IsIn(filter) => {
let mut values: Vec<sea_orm::Value> = vec![];
for filter in filter {
if let Ok(uuid) = Uuid::from_str(filter) {
values.push(uuid.into());
} else {
values.push(filter.into())
}
}
self = self.filter(column.is_in(values));
}
self = self.filter(column.is_in(values));
}
}

Ok(self)
}

fn apply_params(mut self, params: &ListQueryParams) -> anyhow::Result<Self> {
fn apply_params(mut self, params: &ParsedListQueryParams) -> anyhow::Result<Self> {
if let Some(order) = &params.sort {
let order: (String, SortOrder) = serde_json::from_str(order)?;

self = self.order_by(ET::Column::from_str(&order.0)?, order.1.into())
}
if let Some(range) = &params.range {
let range: (u64, u64) = serde_json::from_str(range)?;

self = self.offset(range.0).limit(range.1 - range.0);
if let Some(limit) = params.limit() {
self = self.limit(limit)
}
if let Some(offset) = params.offset() {
self = self.offset(offset)
}

self = self.apply_filter(params)?;
Ok(self)
}
}

#[derive(Deserialize, Clone, Default)]
#[derive(Deserialize, Clone)]
pub struct ListQueryParams {
sort: Option<String>,
range: Option<String>,
filter: Option<String>,
}

#[derive(Deserialize, Clone)]
enum FilterOp {
IsIn(Vec<String>),
}

pub struct ParsedListQueryParams {
range: Option<(u64, u64)>,
filter: Option<HashMap<String, FilterOp>>,
sort: Option<(String, SortOrder)>,
}

impl ParsedListQueryParams {
fn limit(&self) -> Option<u64> {
self.range.map(|(start, end)| end - start)
}

fn offset(&self) -> Option<u64> {
self.range.map(|(start, _)| start)
}
}

impl TryFrom<ListQueryParams> for ParsedListQueryParams {
type Error = anyhow::Error;

fn try_from(value: ListQueryParams) -> Result<Self, Self::Error> {
let sort = match value.sort {
None => None,
Some(sort) => serde_json::from_str(&sort)?,
};

let range = match value.range {
None => None,
Some(range) => serde_json::from_str(&range)?,
};

let filter = match value.filter {
None => None,
Some(filter) => {
let mut parsed_filter = HashMap::new();

let filter: BTreeMap<String, Value> = serde_json::from_str(&filter)?;
for (col, values) in filter.into_iter() {
let values = match values {
Value::String(v) => vec![v],
Value::Array(v) => {
let mut values: Vec<String> = vec![];
for filter in v {
match filter {
Value::String(filter) => values.push(filter),
_ => {
bail!("Invalid filter value type: {col}. Expected string.")
}
}
}
values
}
_ => {
bail!("Invalid filter value type: {col}. Expected string or array of strings.")
}
};
parsed_filter.insert(col, FilterOp::IsIn(values));
}
Some(parsed_filter)
}
};

Ok(Self {
range,
filter,
sort,
})
}
}

pub struct PaginatedResult<T> {
pub items: Vec<T>,
pub total: u64,
Expand Down
1 change: 1 addition & 0 deletions notifico-dbpipeline/src/controllers/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl AdminCrudTable for EventDbController {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
Ok(PaginatedResult {
items: entity::event::Entity::find()
.apply_params(&params)?
Expand Down
1 change: 1 addition & 0 deletions notifico-dbpipeline/src/controllers/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ impl AdminCrudTable for PipelineDbController {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
let pipelines = entity::pipeline::Entity::find()
.apply_params(&params)
.unwrap()
Expand Down
1 change: 1 addition & 0 deletions notifico-project/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl AdminCrudTable for ProjectController {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
let query = entity::project::Entity::find()
.apply_params(&params)
.unwrap()
Expand Down
1 change: 1 addition & 0 deletions notifico-subscription/src/controllers/contact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ impl AdminCrudTable for ContactDbController {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
let items = crate::entity::contact::Entity::find()
.apply_params(&params)?
.all(&self.db)
Expand Down
1 change: 1 addition & 0 deletions notifico-subscription/src/controllers/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ impl AdminCrudTable for GroupDbController {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
let items = crate::entity::group::Entity::find()
.apply_params(&params)?
.all(&self.db)
Expand Down
1 change: 1 addition & 0 deletions notifico-subscription/src/controllers/recipient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl AdminCrudTable for RecipientDbController {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
let total = crate::entity::recipient::Entity::find()
.apply_filter(&params)?
.count(&self.db)
Expand Down
1 change: 1 addition & 0 deletions notifico-subscription/src/controllers/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ impl AdminCrudTable for SubscriptionDbController {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
let total = Subscription::find()
.apply_filter(&params)?
.count(&self.db)
Expand Down
1 change: 1 addition & 0 deletions notifico-template/src/source/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl AdminCrudTable for DbTemplateSource {
&self,
params: ListQueryParams,
) -> Result<PaginatedResult<ItemWithId<Self::Item>>, EngineError> {
let params = params.try_into()?;
let items = entity::template::Entity::find();
Ok(PaginatedResult {
items: items
Expand Down

0 comments on commit 15ed1b3

Please sign in to comment.