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