linkding/
users.rs

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}