autofill/sync/address/
mod.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*/
5
6pub mod incoming;
7pub mod name_utils;
8pub mod outgoing;
9
10use super::engine::{ConfigSyncEngine, EngineConfig, SyncEngineStorageImpl};
11use super::{
12    MergeResult, Metadata, ProcessIncomingRecordImpl, ProcessOutgoingRecordImpl, SyncRecord,
13    UnknownFields,
14};
15use crate::db::models::address::InternalAddress;
16use crate::error::*;
17use crate::sync_merge_field_check;
18use incoming::IncomingAddressesImpl;
19use name_utils::{split_name, NameParts};
20use outgoing::OutgoingAddressesImpl;
21use rusqlite::Transaction;
22use serde::{Deserialize, Serialize};
23use std::sync::Arc;
24use sync_guid::Guid;
25use types::Timestamp;
26
27// The engine.
28pub(crate) fn create_engine(store: Arc<crate::Store>) -> ConfigSyncEngine<InternalAddress> {
29    ConfigSyncEngine::new(
30        EngineConfig {
31            namespace: "addresses".to_string(),
32            collection: "addresses".into(),
33        },
34        store,
35        Box::new(AddressesEngineStorageImpl {}),
36    )
37}
38
39pub(super) struct AddressesEngineStorageImpl {}
40
41impl SyncEngineStorageImpl<InternalAddress> for AddressesEngineStorageImpl {
42    fn get_incoming_impl(
43        &self,
44        enc_key: &Option<String>,
45    ) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = InternalAddress>>> {
46        assert!(enc_key.is_none());
47        Ok(Box::new(IncomingAddressesImpl {}))
48    }
49
50    fn reset_storage(&self, tx: &Transaction<'_>) -> Result<()> {
51        tx.execute_batch(
52            "DELETE FROM addresses_mirror;
53            DELETE FROM addresses_tombstones;",
54        )?;
55        Ok(())
56    }
57
58    fn get_outgoing_impl(
59        &self,
60        enc_key: &Option<String>,
61    ) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = InternalAddress>>> {
62        assert!(enc_key.is_none());
63        Ok(Box::new(OutgoingAddressesImpl {}))
64    }
65}
66
67// These structs are a representation of what's stored on the sync server for non-tombstone records.
68// (The actual server doesn't have `id` in the payload but instead in the envelope)
69#[derive(Default, Deserialize, Serialize)]
70pub struct AddressPayload {
71    id: Guid,
72    // For some historical reason and unlike most other sync records, addresses
73    // are serialized with this explicit 'entry' object.
74    entry: PayloadEntry,
75}
76
77#[derive(Default, Deserialize, Serialize)]
78#[serde(default, rename_all = "kebab-case")]
79struct PayloadEntry {
80    pub name: String,
81    // given_name, additional_name and family_name
82    // should exist in PayloadEntry but not in InternalAddress
83    // This is needed to support old devices that still need these fields
84    pub given_name: String,
85    pub additional_name: String,
86    pub family_name: String,
87    pub organization: String,
88    pub street_address: String,
89    pub address_level3: String,
90    pub address_level2: String,
91    pub address_level1: String,
92    pub postal_code: String,
93    pub country: String,
94    pub tel: String,
95    pub email: String,
96    // metadata (which isn't kebab-case for some historical reason...)
97    #[serde(rename = "timeCreated")]
98    pub time_created: Timestamp,
99    #[serde(rename = "timeLastUsed")]
100    pub time_last_used: Timestamp,
101    #[serde(rename = "timeLastModified")]
102    pub time_last_modified: Timestamp,
103    #[serde(rename = "timesUsed")]
104    pub times_used: i64,
105    pub version: u32, // always 3 for credit-cards
106    // Fields that the current schema did not expect, we store them only internally
107    // to round-trip them back to sync without processing them in any way
108    #[serde(flatten)]
109    unknown_fields: UnknownFields,
110}
111
112impl InternalAddress {
113    fn from_payload(p: AddressPayload) -> Result<Self> {
114        if p.entry.version != 1 {
115            // Always been version 1
116            return Err(Error::InvalidSyncPayload(format!(
117                "invalid version - {}",
118                p.entry.version
119            )));
120        }
121
122        Ok(InternalAddress {
123            guid: p.id,
124            name: p.entry.name,
125            organization: p.entry.organization,
126            street_address: p.entry.street_address,
127            address_level3: p.entry.address_level3,
128            address_level2: p.entry.address_level2,
129            address_level1: p.entry.address_level1,
130            postal_code: p.entry.postal_code,
131            country: p.entry.country,
132            tel: p.entry.tel,
133            email: p.entry.email,
134            metadata: Metadata {
135                time_created: p.entry.time_created,
136                time_last_used: p.entry.time_last_used,
137                time_last_modified: p.entry.time_last_modified,
138                times_used: p.entry.times_used,
139                sync_change_counter: 0,
140            },
141        })
142    }
143
144    fn into_payload(self) -> Result<AddressPayload> {
145        let NameParts {
146            given,
147            middle,
148            family,
149        } = split_name(&self.name);
150        Ok(AddressPayload {
151            id: self.guid,
152            entry: PayloadEntry {
153                name: self.name,
154                given_name: given,
155                additional_name: middle,
156                family_name: family,
157                organization: self.organization,
158                street_address: self.street_address,
159                address_level3: self.address_level3,
160                address_level2: self.address_level2,
161                address_level1: self.address_level1,
162                postal_code: self.postal_code,
163                country: self.country,
164                tel: self.tel,
165                email: self.email,
166                time_created: self.metadata.time_created,
167                time_last_used: self.metadata.time_last_used,
168                time_last_modified: self.metadata.time_last_modified,
169                times_used: self.metadata.times_used,
170                unknown_fields: Default::default(),
171                version: 1,
172            },
173        })
174    }
175}
176
177impl SyncRecord for InternalAddress {
178    fn record_name() -> &'static str {
179        "Address"
180    }
181
182    fn id(&self) -> &Guid {
183        &self.guid
184    }
185
186    fn metadata(&self) -> &Metadata {
187        &self.metadata
188    }
189
190    fn metadata_mut(&mut self) -> &mut Metadata {
191        &mut self.metadata
192    }
193
194    /// Performs a three-way merge between an incoming, local, and mirror record.
195    /// If a merge cannot be successfully completed (ie, if we find the same
196    /// field has changed both locally and remotely since the last sync), the
197    /// local record data is returned with a new guid and updated sync metadata.
198    /// Note that mirror being None is an edge-case and typically means first
199    /// sync since a "reset" (eg, disconnecting and reconnecting.
200    #[allow(clippy::cognitive_complexity)] // Looks like clippy considers this after macro-expansion...
201    fn merge(incoming: &Self, local: &Self, mirror: &Option<Self>) -> MergeResult<Self> {
202        let mut merged_record: Self = Default::default();
203        // guids must be identical
204        assert_eq!(incoming.guid, local.guid);
205
206        if let Some(m) = mirror {
207            assert_eq!(incoming.guid, m.guid)
208        };
209
210        merged_record.guid = incoming.guid.clone();
211
212        sync_merge_field_check!(name, incoming, local, mirror, merged_record);
213        sync_merge_field_check!(organization, incoming, local, mirror, merged_record);
214        sync_merge_field_check!(street_address, incoming, local, mirror, merged_record);
215        sync_merge_field_check!(address_level3, incoming, local, mirror, merged_record);
216        sync_merge_field_check!(address_level2, incoming, local, mirror, merged_record);
217        sync_merge_field_check!(address_level1, incoming, local, mirror, merged_record);
218        sync_merge_field_check!(postal_code, incoming, local, mirror, merged_record);
219        sync_merge_field_check!(country, incoming, local, mirror, merged_record);
220        sync_merge_field_check!(tel, incoming, local, mirror, merged_record);
221        sync_merge_field_check!(email, incoming, local, mirror, merged_record);
222
223        merged_record.metadata = incoming.metadata;
224        merged_record
225            .metadata
226            .merge(&local.metadata, mirror.as_ref().map(|m| m.metadata()));
227
228        MergeResult::Merged {
229            merged: merged_record,
230        }
231    }
232}
233
234/// Returns a with the given local record's data but with a new guid and
235/// fresh sync metadata.
236fn get_forked_record(local_record: InternalAddress) -> InternalAddress {
237    let mut local_record_data = local_record;
238    local_record_data.guid = Guid::random();
239    local_record_data.metadata.time_created = Timestamp::now();
240    local_record_data.metadata.time_last_used = Timestamp::now();
241    local_record_data.metadata.time_last_modified = Timestamp::now();
242    local_record_data.metadata.times_used = 0;
243    local_record_data.metadata.sync_change_counter = 1;
244
245    local_record_data
246}