Skip to main content

queryweave_api/
lib.rs

1//! The `queryweave` runtime for the API layer: the types the code generated
2//! for API types (a `Filters` mirror with `serde`, `utoipa` or `schemars`,
3//! `ValidateNonNullable`) names, on top of the core runtime (`queryweave`),
4//! which it re-exports so that code names only this crate.
5//!
6//! Every integration is a feature, none enabled by default: `serde`
7//! (`Deserialize` / `Serialize`), `utoipa` (`ToSchema`) and `schemars`
8//! (`JsonSchema`) add their derives to [`SortDirectionDto`] and [`PageDto`].
9//! A mirror's flag needs the feature of the same name, and reports it
10//! disabled at the flag (see [`features`]). Each integration states the wire
11//! format itself (snake case directions), so they agree whichever are
12//! enabled.
13
14pub use queryweave::*;
15
16pub mod features;
17mod page;
18
19pub use page::PageDto;
20
21/// The sort direction applied to the result retrieved by filters.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23#[cfg_attr(
24    feature = "serde",
25    derive(serde::Deserialize, serde::Serialize),
26    serde(rename_all = "snake_case")
27)]
28#[cfg_attr(
29    feature = "utoipa",
30    derive(utoipa::ToSchema),
31    schema(rename_all = "snake_case")
32)]
33#[cfg_attr(
34    feature = "schemars",
35    derive(schemars::JsonSchema),
36    schemars(rename_all = "snake_case")
37)]
38pub enum SortDirectionDto {
39    /// Sort in ascending order.
40    #[default]
41    Ascending,
42    /// Sort in descending order.
43    Descending,
44}
45
46impl From<SortDirectionDto> for queryweave::SortDirectionModel {
47    fn from(value: SortDirectionDto) -> Self {
48        match value {
49            SortDirectionDto::Ascending => Self::Ascending,
50            SortDirectionDto::Descending => Self::Descending,
51        }
52    }
53}
54
55impl From<queryweave::SortDirectionModel> for SortDirectionDto {
56    fn from(value: queryweave::SortDirectionModel) -> Self {
57        match value {
58            queryweave::SortDirectionModel::Ascending => Self::Ascending,
59            queryweave::SortDirectionModel::Descending => Self::Descending,
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::PageDto;
67
68    fn total_pages(page_size: Option<u32>, total_items: u32) -> Option<u32> {
69        PageDto::<u32>::new(Some(0), page_size, total_items, Vec::new()).total_pages
70    }
71
72    // A partial last page still counts as a page.
73    #[test]
74    fn total_pages_rounds_up() {
75        assert_eq!(total_pages(Some(10), 0), Some(0));
76        assert_eq!(total_pages(Some(10), 10), Some(1));
77        assert_eq!(total_pages(Some(10), 11), Some(2));
78    }
79
80    // Without a page size there is nothing to count pages by.
81    #[test]
82    fn total_pages_is_none_without_a_page_size() {
83        assert_eq!(total_pages(None, 11), None);
84    }
85
86    // A zero page size used to divide by zero and panic.
87    #[test]
88    fn total_pages_is_none_for_a_zero_page_size() {
89        assert_eq!(total_pages(Some(0), 11), None);
90    }
91
92    // serde (de)serializes a direction in snake case.
93    #[cfg(feature = "serde")]
94    #[test]
95    fn serde_writes_directions_in_snake_case() {
96        use super::SortDirectionDto;
97
98        assert_eq!(
99            serde_json::to_value(SortDirectionDto::Descending).unwrap(),
100            serde_json::json!("descending")
101        );
102        assert_eq!(
103            serde_json::from_value::<SortDirectionDto>(serde_json::json!("ascending")).unwrap(),
104            SortDirectionDto::Ascending
105        );
106    }
107
108    // schemars describes the same snake case values, whether or not serde is
109    // enabled.
110    #[cfg(feature = "schemars")]
111    #[test]
112    fn schemars_describes_directions_in_snake_case() {
113        let schema = serde_json::to_value(schemars::schema_for!(super::SortDirectionDto)).unwrap();
114        // A documented variant is a `oneOf` constant.
115        let values = schema["oneOf"]
116            .as_array()
117            .unwrap()
118            .iter()
119            .map(|variant| variant["const"].clone())
120            .collect::<Vec<_>>();
121
122        assert_eq!(values, ["ascending", "descending"]);
123    }
124
125    // utoipa documents the same snake case values, whether or not serde is
126    // enabled (it states them itself rather than reading serde's attributes).
127    #[cfg(feature = "utoipa")]
128    #[test]
129    fn utoipa_documents_directions_in_snake_case() {
130        let schema =
131            serde_json::to_value(<super::SortDirectionDto as utoipa::PartialSchema>::schema())
132                .unwrap();
133
134        assert_eq!(
135            schema["enum"],
136            serde_json::json!(["ascending", "descending"])
137        );
138    }
139}