places/history_sync/
record.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::{history_sync::ServerVisitTimestamp, types::UnknownFields};
6use serde::Deserialize;
7use serde_derive::*;
8use sync_guid::Guid as SyncGuid;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
11#[serde(rename_all = "camelCase")]
12pub struct HistoryRecordVisit {
13    pub date: ServerVisitTimestamp,
14    #[serde(rename = "type")]
15    pub transition: u8,
16
17    #[serde(flatten)]
18    pub unknown_fields: UnknownFields,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct HistoryRecord {
24    // TODO: consider `#[serde(rename = "id")] pub guid: String` to avoid confusion
25    pub id: SyncGuid,
26
27    #[serde(default)]
28    #[serde(deserialize_with = "deserialize_nonull_string")]
29    #[serde(skip_serializing_if = "String::is_empty")]
30    pub title: String,
31
32    pub hist_uri: String,
33
34    pub visits: Vec<HistoryRecordVisit>,
35
36    #[serde(flatten)]
37    pub unknown_fields: UnknownFields,
38}
39
40fn deserialize_nonull_string<'de, D>(deserializer: D) -> Result<String, D::Error>
41where
42    D: serde::Deserializer<'de>,
43{
44    Ok(match <Option<String>>::deserialize(deserializer)? {
45        Some(s) => s,
46        None => "".to_string(),
47    })
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_null_title() {
56        // #5544 tells us we are seeing an explicit null for an incoming tab title.
57        // Really not clear where these are coming from - possibly very old versions of
58        // apps, but seems easy to handle, so here we are!
59        let json = serde_json::json!({
60            "id": "foo",
61            "title": null,
62            "histUri": "https://example.com",
63            "visits": [],
64        });
65
66        let rec = serde_json::from_value::<HistoryRecord>(json).expect("should deser");
67        assert!(rec.title.is_empty());
68    }
69
70    #[test]
71    fn test_missing_title() {
72        let json = serde_json::json!({
73            "id": "foo",
74            "histUri": "https://example.com",
75            "visits": [],
76        });
77
78        let rec = serde_json::from_value::<HistoryRecord>(json).expect("should deser");
79        assert!(rec.title.is_empty());
80    }
81}