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