From 15ed1b3b13884128b52d10b4494d2572fcc4ff97 Mon Sep 17 00:00:00 2001 From: Alexander Shishenko Date: Fri, 3 Jan 2025 20:38:38 +0300 Subject: [PATCH] Split query parsing logic from query applying --- notifico-core/src/http/admin.rs | 142 +++++++++++++----- notifico-dbpipeline/src/controllers/event.rs | 1 + .../src/controllers/pipeline.rs | 1 + notifico-project/src/lib.rs | 1 + .../src/controllers/contact.rs | 1 + .../src/controllers/group.rs | 1 + .../src/controllers/recipient.rs | 1 + .../src/controllers/subscription.rs | 1 + notifico-template/src/source/db.rs | 1 + 9 files changed, 114 insertions(+), 36 deletions(-) diff --git a/notifico-core/src/http/admin.rs b/notifico-core/src/http/admin.rs index 081d0d0..fe89056 100644 --- a/notifico-core/src/http/admin.rs +++ b/notifico-core/src/http/admin.rs @@ -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, } @@ -30,8 +31,8 @@ impl From for sea_orm::Order { } pub trait ListableTrait: QuerySelect { - fn apply_filter(self, params: &ListQueryParams) -> anyhow::Result; - fn apply_params(self, params: &ListQueryParams) -> anyhow::Result; + fn apply_filter(self, params: &ParsedListQueryParams) -> anyhow::Result; + fn apply_params(self, params: &ParsedListQueryParams) -> anyhow::Result; } impl ListableTrait for Select @@ -39,59 +40,128 @@ where ET: EntityTrait, ::Err: Error + Send + Sync, { - fn apply_filter(mut self, params: &ListQueryParams) -> anyhow::Result { - if let Some(filter) = ¶ms.filter { - let filter: BTreeMap = 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 = 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 { + let Some(filter) = ¶ms.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 = 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 { + fn apply_params(mut self, params: &ParsedListQueryParams) -> anyhow::Result { if let Some(order) = ¶ms.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) = ¶ms.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, range: Option, filter: Option, } +#[derive(Deserialize, Clone)] +enum FilterOp { + IsIn(Vec), +} + +pub struct ParsedListQueryParams { + range: Option<(u64, u64)>, + filter: Option>, + sort: Option<(String, SortOrder)>, +} + +impl ParsedListQueryParams { + fn limit(&self) -> Option { + self.range.map(|(start, end)| end - start) + } + + fn offset(&self) -> Option { + self.range.map(|(start, _)| start) + } +} + +impl TryFrom for ParsedListQueryParams { + type Error = anyhow::Error; + + fn try_from(value: ListQueryParams) -> Result { + 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 = 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 = 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 { pub items: Vec, pub total: u64, diff --git a/notifico-dbpipeline/src/controllers/event.rs b/notifico-dbpipeline/src/controllers/event.rs index 18991b8..958c598 100644 --- a/notifico-dbpipeline/src/controllers/event.rs +++ b/notifico-dbpipeline/src/controllers/event.rs @@ -33,6 +33,7 @@ impl AdminCrudTable for EventDbController { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; Ok(PaginatedResult { items: entity::event::Entity::find() .apply_params(¶ms)? diff --git a/notifico-dbpipeline/src/controllers/pipeline.rs b/notifico-dbpipeline/src/controllers/pipeline.rs index 773b9d3..c9b13e2 100644 --- a/notifico-dbpipeline/src/controllers/pipeline.rs +++ b/notifico-dbpipeline/src/controllers/pipeline.rs @@ -94,6 +94,7 @@ impl AdminCrudTable for PipelineDbController { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; let pipelines = entity::pipeline::Entity::find() .apply_params(¶ms) .unwrap() diff --git a/notifico-project/src/lib.rs b/notifico-project/src/lib.rs index 0124c45..6fb5537 100644 --- a/notifico-project/src/lib.rs +++ b/notifico-project/src/lib.rs @@ -52,6 +52,7 @@ impl AdminCrudTable for ProjectController { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; let query = entity::project::Entity::find() .apply_params(¶ms) .unwrap() diff --git a/notifico-subscription/src/controllers/contact.rs b/notifico-subscription/src/controllers/contact.rs index aeb6087..e1d86c9 100644 --- a/notifico-subscription/src/controllers/contact.rs +++ b/notifico-subscription/src/controllers/contact.rs @@ -57,6 +57,7 @@ impl AdminCrudTable for ContactDbController { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; let items = crate::entity::contact::Entity::find() .apply_params(¶ms)? .all(&self.db) diff --git a/notifico-subscription/src/controllers/group.rs b/notifico-subscription/src/controllers/group.rs index 1cf6186..652b4c4 100644 --- a/notifico-subscription/src/controllers/group.rs +++ b/notifico-subscription/src/controllers/group.rs @@ -57,6 +57,7 @@ impl AdminCrudTable for GroupDbController { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; let items = crate::entity::group::Entity::find() .apply_params(¶ms)? .all(&self.db) diff --git a/notifico-subscription/src/controllers/recipient.rs b/notifico-subscription/src/controllers/recipient.rs index 9c19823..ed0176b 100644 --- a/notifico-subscription/src/controllers/recipient.rs +++ b/notifico-subscription/src/controllers/recipient.rs @@ -60,6 +60,7 @@ impl AdminCrudTable for RecipientDbController { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; let total = crate::entity::recipient::Entity::find() .apply_filter(¶ms)? .count(&self.db) diff --git a/notifico-subscription/src/controllers/subscription.rs b/notifico-subscription/src/controllers/subscription.rs index 705a83d..5782092 100644 --- a/notifico-subscription/src/controllers/subscription.rs +++ b/notifico-subscription/src/controllers/subscription.rs @@ -118,6 +118,7 @@ impl AdminCrudTable for SubscriptionDbController { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; let total = Subscription::find() .apply_filter(¶ms)? .count(&self.db) diff --git a/notifico-template/src/source/db.rs b/notifico-template/src/source/db.rs index 5d88785..039dd89 100644 --- a/notifico-template/src/source/db.rs +++ b/notifico-template/src/source/db.rs @@ -69,6 +69,7 @@ impl AdminCrudTable for DbTemplateSource { &self, params: ListQueryParams, ) -> Result>, EngineError> { + let params = params.try_into()?; let items = entity::template::Entity::find(); Ok(PaginatedResult { items: items