Skip to main content

aster_forge_db/
sort.rs

1//! Shared repository helpers for applying whitelisted sort options.
2//!
3//! The helpers accept a caller-provided whitelist of field names and SeaORM columns, then apply
4//! ascending or descending ordering to a query. Keeping this logic shared avoids repeating unsafe
5//! ad-hoc string-to-column mapping in each repository.
6
7use sea_orm::{ColumnTrait, QueryOrder};
8
9pub use aster_forge_api::SortOrder;
10
11/// Orders a query by one column.
12pub fn order_by_column<Q, C>(query: Q, column: C, order: SortOrder) -> Q
13where
14    Q: QueryOrder,
15    C: ColumnTrait,
16{
17    match order {
18        SortOrder::Asc => query.order_by_asc(column),
19        SortOrder::Desc => query.order_by_desc(column),
20    }
21}
22
23/// Orders a query by one column and then by an id column for stable ordering.
24pub fn order_by_column_with_id<E, C, I>(query: E, column: C, order: SortOrder, id_column: I) -> E
25where
26    E: QueryOrder,
27    C: ColumnTrait,
28    I: ColumnTrait,
29{
30    order_by_id(order_by_column(query, column, order), id_column, order)
31}
32
33/// Orders a query by an id column.
34pub fn order_by_id<Q, I>(query: Q, id_column: I, order: SortOrder) -> Q
35where
36    Q: QueryOrder,
37    I: ColumnTrait,
38{
39    match order {
40        SortOrder::Asc => query.order_by_asc(id_column),
41        SortOrder::Desc => query.order_by_desc(id_column),
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::{SortOrder, order_by_column, order_by_column_with_id, order_by_id};
48    use sea_orm::{
49        ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
50        EnumIter, PrimaryKeyTrait, QueryTrait,
51    };
52
53    #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
54    #[sea_orm(table_name = "sortable_items")]
55    pub struct Model {
56        #[sea_orm(primary_key)]
57        pub id: i32,
58        pub score: i32,
59    }
60
61    #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
62    pub enum Relation {}
63
64    impl ActiveModelBehavior for ActiveModel {}
65
66    #[test]
67    fn order_by_column_applies_requested_direction() {
68        let asc_sql = order_by_column(Entity::find(), Column::Score, SortOrder::Asc)
69            .build(sea_orm::DbBackend::Sqlite)
70            .to_string();
71        let desc_sql = order_by_column(Entity::find(), Column::Score, SortOrder::Desc)
72            .build(sea_orm::DbBackend::Sqlite)
73            .to_string();
74
75        assert!(asc_sql.contains(r#"ORDER BY "sortable_items"."score" ASC"#));
76        assert!(desc_sql.contains(r#"ORDER BY "sortable_items"."score" DESC"#));
77    }
78
79    #[test]
80    fn order_by_column_with_id_adds_stable_tiebreaker() {
81        let sql =
82            order_by_column_with_id(Entity::find(), Column::Score, SortOrder::Desc, Column::Id)
83                .build(sea_orm::DbBackend::Sqlite)
84                .to_string();
85
86        assert!(sql.contains(r#""sortable_items"."score" DESC"#));
87        assert!(sql.contains(r#""sortable_items"."id" DESC"#));
88    }
89
90    #[test]
91    fn order_by_id_orders_by_primary_key() {
92        let sql = order_by_id(Entity::find(), Column::Id, SortOrder::Asc)
93            .build(sea_orm::DbBackend::Sqlite)
94            .to_string();
95
96        assert!(sql.contains(r#"ORDER BY "sortable_items"."id" ASC"#));
97    }
98}