Skip to main content

paperless_api/
saved_view.rs

1//! Types related to saved views in the paperless UI.
2
3use derive_more::Display;
4use paperless_api_macros::{CreateDto, Item, UpdateDto};
5use serde::{Deserialize, Serialize};
6
7/// A saved view in the paperless UI.
8#[derive(Debug, Default, Clone, Deserialize, Serialize, CreateDto, UpdateDto, Item)]
9#[api_info(endpoint = "saved_views")]
10pub struct SavedView {
11    /// The ID of the saved view.
12    #[dto(skip)]
13    pub id: crate::id::SavedViewId,
14
15    /// The name of the saved view.
16    pub name: String,
17
18    /// Whether the saved view should be shown on the dashboard.
19    pub show_on_dashboard: bool,
20
21    /// Whether the saved view should be shown in the sidebar.
22    pub show_in_sidebar: bool,
23
24    /// The field to sort the view by.
25    pub sort_field: Option<String>,
26
27    /// Whether to sort the view in reverse order.
28    pub sort_reverse: Option<bool>,
29
30    /// The filter rules determining which documents are shown in the view.
31    pub filter_rules: Option<Vec<FilterRule>>,
32
33    /// The display mode of the view.
34    pub display_mode: Option<DisplayMode>,
35
36    /// The fields to display in the view.
37    pub display_fields: Option<Vec<String>>,
38
39    /// The number of documents to show per page.
40    pub page_size: Option<u32>,
41
42    /// The user who owns the saved view.
43    #[dto(skip)]
44    pub owner: Option<crate::id::UserId>,
45
46    /// Whether the user can change the saved view.
47    #[dto(skip)]
48    pub user_can_change: bool,
49}
50
51#[derive(Debug, Clone, Deserialize, Serialize)]
52#[serde(rename_all = "camelCase")]
53pub enum DisplayMode {
54    Table,
55    SmallCards,
56    LargeCards,
57}
58
59#[derive(Debug, Clone, Deserialize, Serialize)]
60pub struct FilterRule {
61    #[serde(rename = "rule_type")]
62    pub rule: FilterRuleType,
63
64    pub value: Option<String>,
65}
66
67#[derive(Debug, Clone, Copy, Display)]
68#[repr(u8)]
69pub enum FilterRuleType {
70    TitleContains = 0,
71    ContentContains = 1,
72    AsnIs = 2,
73    CorrespondentIs = 3,
74    DocumentTypeIs = 4,
75    IsInInbox = 5,
76    HasTag = 6,
77    HasAnyTag = 7,
78    CreatedBefore = 8,
79    CreatedAfter = 9,
80    CreatedInYear = 10,
81    CreatedInMonth = 11,
82    CreatedDayIs = 12,
83    AddedBefore = 13,
84    AddedAfter = 14,
85    ModifiedBefore = 15,
86    ModifiedAfter = 16,
87    DoesNotHaveTag = 17,
88    DocumentHasNoAsn = 18,
89    TitleOrContentContains = 19,
90    FullTextSearch = 20,
91    SimilarDocuments = 21,
92    HasTagsIn = 22,
93    AsnGreaterThan = 23,
94    AsnLessThan = 24,
95    StoragePathIs = 25,
96    HasCorrespondentIn = 26,
97    HasNoCorrespondentIn = 27,
98    HasDocumentTypeIn = 28,
99    HasNoDocumentTypeIn = 29,
100    HasStoragePathIn = 30,
101    HasNoStoragePathIn = 31,
102    OwnerIs = 32,
103    HasOwnerIn = 33,
104    HasNoOwner = 34,
105    HasNoOwnerIn = 35,
106    HasCustomFieldValue = 36,
107    IsSharedByMe = 37,
108    HasCustomFields = 38,
109    HasTheCustomFields = 39,
110    DoesNotHaveCustomFields = 40,
111    DoesNotHaveCustomField = 41,
112    CustomFieldQuery = 42,
113    CreateDto = 43,
114    CreatedBy = 44,
115    AddedTo = 45,
116    AddedBy = 46,
117    MimeTypeIs = 47,
118
119    Unknown(u8),
120}
121
122impl Serialize for FilterRuleType {
123    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124    where
125        S: serde::Serializer,
126    {
127        let value = if let FilterRuleType::Unknown(unknown) = self {
128            *unknown
129        } else {
130            // SAFETY: FilterRuleType must be a valid 0..=47
131            unsafe { *(std::ptr::from_ref::<FilterRuleType>(self)).cast::<u8>() }
132        };
133
134        serializer.serialize_u8(value)
135    }
136}
137
138impl<'de> serde::Deserialize<'de> for FilterRuleType {
139    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
140    where
141        D: serde::Deserializer<'de>,
142    {
143        let value = u8::deserialize(deserializer)?;
144
145        let enum_value = match value {
146            0..=47 => unsafe { std::mem::transmute::<u16, FilterRuleType>(u16::from(value)) },
147            _ => FilterRuleType::Unknown(value),
148        };
149
150        Ok(enum_value)
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn filter_rule_type_boundary_values() {
160        let zero: FilterRuleType = serde_json::from_str("0").unwrap();
161        assert!(matches!(zero, FilterRuleType::TitleContains));
162
163        let forty_seven: FilterRuleType = serde_json::from_str("47").unwrap();
164        assert!(matches!(forty_seven, FilterRuleType::MimeTypeIs));
165
166        let forty_seven: FilterRuleType = serde_json::from_str("48").unwrap();
167        assert!(matches!(forty_seven, FilterRuleType::Unknown(48)));
168    }
169
170    #[test]
171    fn filter_rule_type_unknown_value_roundtrip() {
172        let unknown: FilterRuleType = serde_json::from_str("200").unwrap();
173        assert!(matches!(unknown, FilterRuleType::Unknown(200)));
174
175        let serialized = serde_json::to_string(&unknown).unwrap();
176        assert_eq!(serialized, "200");
177    }
178
179    #[test]
180    fn filter_rule_roundtrip_with_value() {
181        let rule = FilterRule {
182            rule: FilterRuleType::FullTextSearch,
183            value: Some("created:[-3 month to now]".to_string()),
184        };
185
186        let json = serde_json::to_string(&rule).unwrap();
187        let deserialized: FilterRule = serde_json::from_str(&json).unwrap();
188
189        assert!(matches!(deserialized.rule, FilterRuleType::FullTextSearch));
190        assert_eq!(
191            deserialized.value,
192            Some("created:[-3 month to now]".to_string())
193        );
194    }
195}