refactor: replace hand-written SortKey impls with define_sort_key! macro
Add define_sort_key! macro to listing.rs and convert all 8 SortKey enum + impl blocks to use it, eliminating ~270 lines of repetitive boilerplate across roasters, roasts, bags, brews, cups, cafes, gear, and timeline.
This commit is contained in:
parent
de49f7c3b1
commit
bd4cb023fb
9 changed files with 125 additions and 319 deletions
|
|
@ -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<TimelineBrewData>,
|
||||
}
|
||||
|
||||
#[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<Self> {
|
||||
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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[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<Self> {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue