diff --git a/src/domain/analytics/timeline.rs b/src/domain/analytics/timeline.rs index fa2cec5..c3cadab 100644 --- a/src/domain/analytics/timeline.rs +++ b/src/domain/analytics/timeline.rs @@ -1,9 +1,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::{BagId, GearId, TimelineEventId}; -use crate::domain::listing::{SortDirection, SortKey}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimelineEventDetail { @@ -54,32 +54,7 @@ pub struct NewTimelineEvent { pub brew_data: Option, } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum TimelineSortKey { - OccurredAt, -} - -impl SortKey for TimelineSortKey { - fn default() -> Self { - TimelineSortKey::OccurredAt - } - - fn from_query(value: &str) -> Option { - match value { - "occurred-at" => Some(TimelineSortKey::OccurredAt), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - TimelineSortKey::OccurredAt => "occurred-at", - } - } - - fn default_direction(self) -> SortDirection { - match self { - TimelineSortKey::OccurredAt => SortDirection::Desc, - } - } -} +define_sort_key!(pub TimelineSortKey { + #[default] + OccurredAt("occurred-at", Desc), +}); diff --git a/src/domain/coffee/bags.rs b/src/domain/coffee/bags.rs index 5801e2d..f559cad 100644 --- a/src/domain/coffee/bags.rs +++ b/src/domain/coffee/bags.rs @@ -1,9 +1,9 @@ use chrono::{DateTime, NaiveDate, Utc}; use serde::{Deserialize, Serialize}; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::{BagId, RoastId}; -use crate::domain::listing::{SortDirection, SortKey}; use crate::domain::roasters::Roaster; use crate::domain::roasts::Roast; use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail}; @@ -93,54 +93,16 @@ impl BagFilter { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum BagSortKey { - RoastDate, - CreatedAt, - UpdatedAt, - Roaster, - Roast, - Status, - FinishedAt, -} - -impl SortKey for BagSortKey { - fn default() -> Self { - BagSortKey::CreatedAt - } - - fn from_query(value: &str) -> Option { - match value { - "roast-date" => Some(BagSortKey::RoastDate), - "created-at" => Some(BagSortKey::CreatedAt), - "updated-at" => Some(BagSortKey::UpdatedAt), - "roaster" => Some(BagSortKey::Roaster), - "roast" => Some(BagSortKey::Roast), - "status" => Some(BagSortKey::Status), - "finished-at" => Some(BagSortKey::FinishedAt), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - BagSortKey::RoastDate => "roast-date", - BagSortKey::CreatedAt => "created-at", - BagSortKey::UpdatedAt => "updated-at", - BagSortKey::Roaster => "roaster", - BagSortKey::Roast => "roast", - BagSortKey::Status => "status", - BagSortKey::FinishedAt => "finished-at", - } - } - - fn default_direction(self) -> SortDirection { - match self { - BagSortKey::Roaster | BagSortKey::Roast | BagSortKey::Status => SortDirection::Asc, - _ => SortDirection::Desc, - } - } -} +define_sort_key!(pub BagSortKey { + #[default] + CreatedAt("created-at", Desc), + RoastDate("roast-date", Desc), + UpdatedAt("updated-at", Desc), + Roaster("roaster", Asc), + Roast("roast", Asc), + Status("status", Asc), + FinishedAt("finished-at", Desc), +}); pub fn bag_timeline_event( bag: &Bag, diff --git a/src/domain/coffee/brews.rs b/src/domain/coffee/brews.rs index 3227b79..da061fa 100644 --- a/src/domain/coffee/brews.rs +++ b/src/domain/coffee/brews.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::{BagId, BrewId, GearId}; -use crate::domain::listing::{SortDirection, SortKey}; use crate::domain::timeline::{NewTimelineEvent, TimelineBrewData, TimelineEventDetail}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -272,36 +272,9 @@ impl BrewFilter { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum BrewSortKey { - CreatedAt, - CoffeeWeight, - WaterVolume, -} - -impl SortKey for BrewSortKey { - fn default() -> Self { - BrewSortKey::CreatedAt - } - - fn from_query(value: &str) -> Option { - match value { - "created-at" => Some(BrewSortKey::CreatedAt), - "coffee-weight" => Some(BrewSortKey::CoffeeWeight), - "water-volume" => Some(BrewSortKey::WaterVolume), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - BrewSortKey::CreatedAt => "created-at", - BrewSortKey::CoffeeWeight => "coffee-weight", - BrewSortKey::WaterVolume => "water-volume", - } - } - - fn default_direction(self) -> SortDirection { - SortDirection::Desc - } -} +define_sort_key!(pub BrewSortKey { + #[default] + CreatedAt("created-at", Desc), + CoffeeWeight("coffee-weight", Desc), + WaterVolume("water-volume", Desc), +}); diff --git a/src/domain/coffee/cafes.rs b/src/domain/coffee/cafes.rs index e869d76..11aff88 100644 --- a/src/domain/coffee/cafes.rs +++ b/src/domain/coffee/cafes.rs @@ -2,9 +2,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use super::normalize_optional_field; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::CafeId; -use crate::domain::listing::{SortDirection, SortKey}; use crate::domain::roasters::is_valid_url_scheme; use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail}; @@ -95,42 +95,10 @@ impl UpdateCafe { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum CafeSortKey { - CreatedAt, - Name, - City, - Country, -} - -impl SortKey for CafeSortKey { - fn default() -> Self { - CafeSortKey::CreatedAt - } - - fn from_query(value: &str) -> Option { - match value { - "created-at" => Some(CafeSortKey::CreatedAt), - "name" => Some(CafeSortKey::Name), - "city" => Some(CafeSortKey::City), - "country" => Some(CafeSortKey::Country), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - CafeSortKey::CreatedAt => "created-at", - CafeSortKey::Name => "name", - CafeSortKey::City => "city", - CafeSortKey::Country => "country", - } - } - - fn default_direction(self) -> SortDirection { - match self { - CafeSortKey::CreatedAt => SortDirection::Desc, - _ => SortDirection::Asc, - } - } -} +define_sort_key!(pub CafeSortKey { + #[default] + CreatedAt("created-at", Desc), + Name("name", Asc), + City("city", Asc), + Country("country", Asc), +}); diff --git a/src/domain/coffee/cups.rs b/src/domain/coffee/cups.rs index cfb8a59..a7eb570 100644 --- a/src/domain/coffee/cups.rs +++ b/src/domain/coffee/cups.rs @@ -1,9 +1,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::{CafeId, CupId, RoastId}; -use crate::domain::listing::{SortDirection, SortKey}; use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -106,45 +106,11 @@ impl CupFilter { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum CupSortKey { - CreatedAt, - CafeName, - CafeCity, - RoastName, - RoasterName, -} - -impl SortKey for CupSortKey { - fn default() -> Self { - CupSortKey::CreatedAt - } - - fn from_query(value: &str) -> Option { - match value { - "created-at" => Some(CupSortKey::CreatedAt), - "cafe" => Some(CupSortKey::CafeName), - "city" => Some(CupSortKey::CafeCity), - "roast" => Some(CupSortKey::RoastName), - "roaster" => Some(CupSortKey::RoasterName), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - CupSortKey::CreatedAt => "created-at", - CupSortKey::CafeName => "cafe", - CupSortKey::CafeCity => "city", - CupSortKey::RoastName => "roast", - CupSortKey::RoasterName => "roaster", - } - } - - fn default_direction(self) -> SortDirection { - match self { - CupSortKey::CreatedAt => SortDirection::Desc, - _ => SortDirection::Asc, - } - } -} +define_sort_key!(pub CupSortKey { + #[default] + CreatedAt("created-at", Desc), + CafeName("cafe", Asc), + CafeCity("city", Asc), + RoastName("roast", Asc), + RoasterName("roaster", Asc), +}); diff --git a/src/domain/coffee/gear.rs b/src/domain/coffee/gear.rs index e69c559..cebf010 100644 --- a/src/domain/coffee/gear.rs +++ b/src/domain/coffee/gear.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::GearId; -use crate::domain::listing::{SortDirection, SortKey}; use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] @@ -122,42 +122,10 @@ impl GearFilter { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum GearSortKey { - Make, - Model, - Category, - CreatedAt, -} - -impl SortKey for GearSortKey { - fn default() -> Self { - GearSortKey::CreatedAt - } - - fn from_query(value: &str) -> Option { - match value { - "make" => Some(GearSortKey::Make), - "model" => Some(GearSortKey::Model), - "category" => Some(GearSortKey::Category), - "created-at" => Some(GearSortKey::CreatedAt), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - GearSortKey::Make => "make", - GearSortKey::Model => "model", - GearSortKey::Category => "category", - GearSortKey::CreatedAt => "created-at", - } - } - - fn default_direction(self) -> SortDirection { - match self { - GearSortKey::Make | GearSortKey::Model | GearSortKey::Category => SortDirection::Asc, - GearSortKey::CreatedAt => SortDirection::Desc, - } - } -} +define_sort_key!(pub GearSortKey { + #[default] + CreatedAt("created-at", Desc), + Make("make", Asc), + Model("model", Asc), + Category("category", Asc), +}); diff --git a/src/domain/coffee/roasters.rs b/src/domain/coffee/roasters.rs index 69596fc..7d0e959 100644 --- a/src/domain/coffee/roasters.rs +++ b/src/domain/coffee/roasters.rs @@ -2,9 +2,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use super::normalize_optional_field; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::RoasterId; -use crate::domain::listing::{SortDirection, SortKey}; use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -99,42 +99,10 @@ impl UpdateRoaster { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum RoasterSortKey { - CreatedAt, - Name, - Country, - City, -} - -impl SortKey for RoasterSortKey { - fn default() -> Self { - RoasterSortKey::CreatedAt - } - - fn from_query(value: &str) -> Option { - match value { - "created-at" => Some(RoasterSortKey::CreatedAt), - "name" => Some(RoasterSortKey::Name), - "country" => Some(RoasterSortKey::Country), - "city" => Some(RoasterSortKey::City), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - RoasterSortKey::CreatedAt => "created-at", - RoasterSortKey::Name => "name", - RoasterSortKey::Country => "country", - RoasterSortKey::City => "city", - } - } - - fn default_direction(self) -> SortDirection { - match self { - RoasterSortKey::CreatedAt => SortDirection::Desc, - _ => SortDirection::Asc, - } - } -} +define_sort_key!(pub RoasterSortKey { + #[default] + CreatedAt("created-at", Desc), + Name("name", Asc), + Country("country", Asc), + City("city", Asc), +}); diff --git a/src/domain/coffee/roasts.rs b/src/domain/coffee/roasts.rs index d7cf5e4..626b551 100644 --- a/src/domain/coffee/roasts.rs +++ b/src/domain/coffee/roasts.rs @@ -1,9 +1,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::define_sort_key; use crate::domain::entity_type::EntityType; use crate::domain::ids::{RoastId, RoasterId}; -use crate::domain::listing::{SortDirection, SortKey}; use crate::domain::roasters::Roaster; use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail}; @@ -61,48 +61,14 @@ pub struct UpdateRoast { pub created_at: Option>, } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum RoastSortKey { - CreatedAt, - Name, - Roaster, - Origin, - Producer, -} - -impl SortKey for RoastSortKey { - fn default() -> Self { - RoastSortKey::CreatedAt - } - - fn from_query(value: &str) -> Option { - match value { - "created-at" => Some(RoastSortKey::CreatedAt), - "name" => Some(RoastSortKey::Name), - "roaster" => Some(RoastSortKey::Roaster), - "origin" => Some(RoastSortKey::Origin), - "producer" => Some(RoastSortKey::Producer), - _ => None, - } - } - - fn query_value(self) -> &'static str { - match self { - RoastSortKey::CreatedAt => "created-at", - RoastSortKey::Name => "name", - RoastSortKey::Roaster => "roaster", - RoastSortKey::Origin => "origin", - RoastSortKey::Producer => "producer", - } - } - - fn default_direction(self) -> SortDirection { - match self { - RoastSortKey::CreatedAt => SortDirection::Desc, - _ => SortDirection::Asc, - } - } -} +define_sort_key!(pub RoastSortKey { + #[default] + CreatedAt("created-at", Desc), + Name("name", Asc), + Roaster("roaster", Asc), + Origin("origin", Asc), + Producer("producer", Asc), +}); pub fn roast_timeline_event(roast: &Roast, roaster: &Roaster) -> NewTimelineEvent { let mut details = vec![TimelineEventDetail { diff --git a/src/domain/listing.rs b/src/domain/listing.rs index 62f08e4..656515d 100644 --- a/src/domain/listing.rs +++ b/src/domain/listing.rs @@ -29,6 +29,66 @@ pub trait SortKey: Copy + Eq { fn default_direction(self) -> SortDirection; } +/// Generates a sort key enum and its `SortKey` trait implementation. +/// +/// # Example +/// +/// ```ignore +/// define_sort_key!(RoasterSortKey { +/// #[default] +/// CreatedAt("created-at", Desc), +/// Name("name", Asc), +/// Country("country", Asc), +/// City("city", Asc), +/// }); +/// ``` +#[macro_export] +macro_rules! define_sort_key { + ( + $vis:vis $name:ident { + $(#[default] $default_variant:ident($default_query:literal, $default_dir:ident),)? + $($variant:ident($query:literal, $dir:ident)),* + $(,)? + } + ) => { + #[derive(Debug, Clone, Copy, Eq, PartialEq)] + $vis enum $name { + $($default_variant,)? + $($variant),* + } + + impl $crate::domain::listing::SortKey for $name { + fn default() -> Self { + // The first arm expands for the #[default] variant + $( return $name::$default_variant; )? + // If no #[default], this won't compile — every invocation needs one + } + + fn from_query(value: &str) -> Option { + match value { + $($default_query => Some($name::$default_variant),)? + $($query => Some($name::$variant),)* + _ => None, + } + } + + fn query_value(self) -> &'static str { + match self { + $($name::$default_variant => $default_query,)? + $($name::$variant => $query,)* + } + } + + fn default_direction(self) -> $crate::domain::listing::SortDirection { + match self { + $($name::$default_variant => $crate::domain::listing::SortDirection::$default_dir,)? + $($name::$variant => $crate::domain::listing::SortDirection::$dir,)* + } + } + } + }; +} + #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum PageSize { Limited(u32),