1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3#[derive(Debug, Serialize, Deserialize)]
4#[cfg_attr(feature = "ffi", derive(uniffi::Record))]
5pub struct UserProfile {
6 theme: SelectedTheme,
7 bookmark_date_display: DateDisplay,
8 bookmark_link_target: LinkTarget,
9 #[serde(serialize_with = "serialize_webarchive_integration")]
10 #[serde(deserialize_with = "deserialize_webarchive_integration")]
11 web_archive_integration: bool,
12 tag_search: TagSearchMethod,
13 enable_sharing: bool,
14 enable_public_sharing: bool,
15 enable_favicons: bool,
16 display_url: bool,
17 permanent_notes: bool,
18 search_preferences: UserSearchPreferences,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22#[cfg_attr(feature = "ffi", derive(uniffi::Record))]
23pub struct UserSearchPreferences {
24 #[serde(default)]
25 pub sort: SortBy,
26 #[serde(default)]
27 pub shared: bool,
28 #[serde(default)]
29 pub unread: bool,
30}
31
32#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
33#[serde(rename_all = "snake_case")]
34#[cfg_attr(feature = "ffi", derive(uniffi::Enum))]
35pub enum SortBy {
36 #[default]
37 TitleAsc,
38 TitleDesc,
39 AddedAsc,
40 AddedDesc,
41}
42
43#[derive(Debug, Serialize, Deserialize, PartialEq)]
44#[serde(rename_all = "snake_case")]
45#[cfg_attr(feature = "ffi", derive(uniffi::Enum))]
46pub enum SelectedTheme {
47 Light,
48 Dark,
49 Auto,
50}
51
52#[derive(Debug, Serialize, Deserialize, PartialEq)]
53#[serde(rename_all = "snake_case")]
54#[cfg_attr(feature = "ffi", derive(uniffi::Enum))]
55pub enum DateDisplay {
56 Relative,
57 Absolute,
58 Hidden,
59}
60
61#[derive(Debug, PartialEq)]
62#[cfg_attr(feature = "ffi", derive(uniffi::Enum))]
63pub enum LinkTarget {
64 SameWindow,
65 NewWindow,
66}
67
68impl Serialize for LinkTarget {
69 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70 where
71 S: Serializer,
72 {
73 let s = match *self {
74 LinkTarget::SameWindow => "_self",
75 LinkTarget::NewWindow => "_blank",
76 };
77 serializer.serialize_str(s)
78 }
79}
80
81impl<'de> Deserialize<'de> for LinkTarget {
82 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
83 where
84 D: Deserializer<'de>,
85 {
86 let s: &str = Deserialize::deserialize(deserializer)?;
87 match s {
88 "_self" => Ok(LinkTarget::SameWindow),
89 "_blank" => Ok(LinkTarget::NewWindow),
90 _ => Err(serde::de::Error::custom("invalid variant")),
91 }
92 }
93}
94
95#[derive(Debug, Serialize, Deserialize, PartialEq)]
96#[serde(rename_all = "snake_case")]
97#[cfg_attr(feature = "ffi", derive(uniffi::Enum))]
98pub enum TagSearchMethod {
99 Strict,
100 Lax,
101}
102
103fn serialize_webarchive_integration<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
104where
105 S: Serializer,
106{
107 let string_value = match value {
108 true => "enabled",
109 false => "disabled",
110 };
111 serializer.serialize_str(string_value)
112}
113
114fn deserialize_webarchive_integration<'de, D>(deserializer: D) -> Result<bool, D::Error>
115where
116 D: Deserializer<'de>,
117{
118 let string_value = String::deserialize(deserializer)?;
119 match string_value.as_str() {
120 "enabled" => Ok(true),
121 "disabled" => Ok(false),
122 _ => Err(serde::de::Error::custom("invalid variant")),
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn deserialize_user_profile() {
132 let json = r#"{
133 "theme": "auto",
134 "bookmark_date_display": "relative",
135 "bookmark_link_target": "_blank",
136 "web_archive_integration": "disabled",
137 "tag_search": "strict",
138 "enable_sharing": false,
139 "enable_public_sharing": false,
140 "enable_favicons": false,
141 "display_url": false,
142 "permanent_notes": false,
143 "search_preferences": {
144 "sort": "title_asc",
145 "shared": true,
146 "unread": true
147 }
148 }"#;
149 let user_profile: UserProfile = serde_json::from_str(json).unwrap();
150 assert_eq!(user_profile.theme, SelectedTheme::Auto);
151 assert_eq!(user_profile.bookmark_date_display, DateDisplay::Relative);
152 assert_eq!(user_profile.bookmark_link_target, LinkTarget::NewWindow);
153 assert_eq!(user_profile.web_archive_integration, false);
154 assert_eq!(user_profile.tag_search, TagSearchMethod::Strict);
155 assert_eq!(user_profile.enable_sharing, false);
156 assert_eq!(user_profile.enable_public_sharing, false);
157 assert_eq!(user_profile.enable_favicons, false);
158 assert_eq!(user_profile.display_url, false);
159 assert_eq!(user_profile.permanent_notes, false);
160 assert_eq!(user_profile.search_preferences.sort, SortBy::TitleAsc);
161 assert_eq!(user_profile.search_preferences.shared, true);
162 assert_eq!(user_profile.search_preferences.unread, true);
163 }
164
165 #[test]
166 fn deserialize_user_profile_without_search_preferences() {
167 let json = r#"{
168 "theme": "auto",
169 "bookmark_date_display": "relative",
170 "bookmark_link_target": "_blank",
171 "web_archive_integration": "disabled",
172 "tag_search": "strict",
173 "enable_sharing": false,
174 "enable_public_sharing": false,
175 "enable_favicons": false,
176 "display_url": false,
177 "permanent_notes": false,
178 "search_preferences": {
179 }
180 }"#;
181 let user_profile: UserProfile = serde_json::from_str(json).unwrap();
182 assert_eq!(user_profile.theme, SelectedTheme::Auto);
183 assert_eq!(user_profile.bookmark_date_display, DateDisplay::Relative);
184 assert_eq!(user_profile.bookmark_link_target, LinkTarget::NewWindow);
185 assert_eq!(user_profile.web_archive_integration, false);
186 assert_eq!(user_profile.tag_search, TagSearchMethod::Strict);
187 assert_eq!(user_profile.enable_sharing, false);
188 assert_eq!(user_profile.enable_public_sharing, false);
189 assert_eq!(user_profile.enable_favicons, false);
190 assert_eq!(user_profile.display_url, false);
191 assert_eq!(user_profile.permanent_notes, false);
192 assert_eq!(user_profile.search_preferences.sort, SortBy::TitleAsc);
193 assert_eq!(user_profile.search_preferences.shared, false);
194 assert_eq!(user_profile.search_preferences.unread, false);
195 }
196}