1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
//! # Contack
//!
//! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
//! [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
//!
//! Contack is a contact library for rust. Rather than following the RFCs
//! exactly, it gives up some compatibility for ease of use. For example,
//! instead of being able to express any number of job roles, Contack gives you
//! the option of only 1, which intern is much easier to work with.
//!
//! With the `read_write` feature you can have native support for serialisation
//! and deserialisation, to VCard. This is achieved as follows:
//!
//! ```rust
//! use contack::Contact;
//! use contack::read_write::vcard::VCard;
//! use std::fs::File;
//! use std::io::prelude::*;
//! use std::convert::TryInto;
//!
//! fn main() -> Result<(), Box<dyn Error>> {
//!     // Load a VCard file
//!     let vcard = String::new();
//!     File::open("my_card.vcard")?.read_to_string(&mut vcard)?;
//!     
//!     // Serialise it
//!     let vcard: VCard = vcard.parse()?;
//!     
//!     // Convert it to a contact
//!     let contact: Contact = vcard.try_into()?;
//!     
//!     // Make some changes
//!     contact.role = Some("Being a handy example.");
//!     
//!     // Convert it to a VCard representation.
//!     let vcard: VCard = vcard.into();
//!     
//!     // Print it to the stdout
//!     println!("{}", vcard.to_string());
//! }
//! ```
//!
//! It also supports the following external libraries:
//!
//!  * [VCard](https://crates.io/crates/vcard) `vcard`. Note this only support
//!    serialisation, and is a stricter alternative to the inbuilt `read_write`
//!  * [Diesel](https://diesel.rs) `diesel_support`. This supports both
//!    serialisation and deserialisation.
//!  * [Sqlx](https://crates.io/crates/sqlx) `sqlx_support`. This supports both
//!    serialisation and deserialisation.

#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(
    clippy::doc_markdown,
    clippy::module_name_repetitions,
    clippy::wildcard_imports,
    clippy::missing_const_for_fn,
    clippy::wrong_self_convention,
    clippy::missing_errors_doc
)]

pub mod address;
pub mod contact_information;
pub mod contact_platform;
pub mod date_time;
pub mod name;
pub mod org;
pub mod uri;
#[cfg(feature = "to_vcard")]
pub mod vcard;

#[cfg(feature = "lazy_static")]
#[macro_use]
extern crate lazy_static;

#[cfg_attr(feature = "diesel-derive-enum", macro_use)]
#[cfg(feature = "diesel-derive-enum")]
extern crate diesel_derive_enum;

#[cfg(feature = "diesel")]
#[macro_use]
extern crate diesel;

#[cfg(feature = "diesel_migrations")]
#[macro_use]
extern crate diesel_migrations;

#[cfg(feature = "diesel_support")]
pub mod schema;

#[cfg(feature = "sql")]
pub mod sql;

#[cfg(feature = "sql")]
pub(crate) use sql::SqlConversionError;

#[cfg(feature = "read_write")]
pub mod read_write;

#[cfg(feature = "read_write")]
use crate::read_write::component::Component;
#[cfg(feature = "read_write")]
use crate::read_write::error::FromComponentError;
#[cfg(feature = "read_write")]
use std::convert::{TryFrom, TryInto};

pub use {
    address::{Address, Geo},
    contact_information::{ContactInformation, Type},
    contact_platform::ContactPlatform,
    date_time::DateTime,
    name::Name,
    org::Org,
    uri::Uri,
};

/// This is the base structure to hold all contact details
#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
pub struct Contact {
    /*
     * Explanatory Properties
     */
    /// The Contact's Uid
    pub uid: String,

    /*
     * Identification Property
     */
    /// The Contacts Name
    pub name: name::Name,

    /// The Contact's Nickname
    pub nickname: Option<String>,

    /// The Contact's Anniversary
    pub anniversary: Option<date_time::DateTime>,

    /// The Contact's Birthday
    pub bday: Option<date_time::DateTime>,

    /// The Contact's Photo
    pub photo: Option<uri::Uri>,

    /*
     * Organisational Properties
     */
    /// The Contact's Job Title
    pub title: Option<String>,

    /// The Contact's Job Role
    pub role: Option<String>,

    /// The Contact's Organization
    pub org: Option<org::Org>,

    /// The Contact's Organization's Logo
    pub logo: Option<uri::Uri>,

    /*
     * Communication Properties
     */
    /// The Contact's contact information
    pub contact_information: Vec<contact_information::ContactInformation>,

    /*
     * Deliver Addressing Properties
     */
    /// The Contact's Work Address
    pub work_address: Option<address::Address>,

    /// The Contact's Home Address
    pub home_address: Option<address::Address>,
}

impl Contact {
    pub fn gen_uid(&mut self) {
        // Generates a UID from the UUID Value
        self.uid = uuid::Uuid::new_v4().to_string();
    }

    #[must_use]
    pub fn new(name: name::Name) -> Self {
        Self {
            name,
            nickname: None,
            anniversary: None,
            bday: None,
            photo: None,
            title: None,
            role: None,
            org: None,
            logo: None,
            contact_information: Vec::new(),
            work_address: None,
            home_address: None,
            uid: uuid::Uuid::new_v4().to_string(),
        }
    }
}

#[cfg(feature = "to_vcard")]
impl std::convert::TryInto<vcard::vcard::VCard> for Contact {
    type Error = Box<dyn std::error::Error>;

    fn try_into(self) -> Result<vcard::vcard::VCard, Self::Error> {
        vcard::contack_to_vcard(&self)
    }
}

#[cfg(feature = "read_write")]
impl From<Contact> for read_write::vcard::VCard {
    fn from(c: Contact) -> Self {
        // Vec to store propertiess
        let mut vec = vec![
            // Insert the UID
            Component {
                name: "UID".to_string(),
                values: vec![vec![c.uid]],
                ..Component::default()
            },
            // And the name (FN followed by N)
            Component {
                name: "FN".to_string(),
                values: vec![vec![c.name.to_string()]],
                ..Component::default()
            },
            c.name.into(),
        ];

        // Nickname next
        if let Some(nickname) = c.nickname {
            vec.push(Component {
                name: "NICKNAME".to_string(),
                values: vec![vec![nickname]],
                ..Component::default()
            })
        }

        // Anniversary
        if let Some(anniversary) = c.anniversary {
            let mut anniversary: Component = anniversary.into();
            anniversary.name = "ANNIVERSARY".to_string();
            vec.push(anniversary)
        }

        // Bithday
        if let Some(bday) = c.bday {
            let mut bday: Component = bday.into();
            bday.name = "BDAY".to_string();
            vec.push(bday)
        }

        // Photo
        if let Some(photo) = c.photo {
            let mut photo: Component = photo.into();
            photo.name = "PHOTO".to_string();
            vec.push(photo)
        }

        // Title
        if let Some(title) = c.title {
            vec.push(Component {
                name: "TITLE".to_string(),
                values: vec![vec![title]],
                ..Component::default()
            })
        }

        // Role
        if let Some(role) = c.role {
            vec.push(Component {
                name: "ROLE".to_string(),
                values: vec![vec![role]],
                ..Component::default()
            })
        }

        // Org
        if let Some(org) = c.org {
            vec.push(org.into());
        }

        // Logo
        if let Some(logo) = c.logo {
            let mut logo: Component = logo.into();
            logo.name = "LOGO".to_string();
            vec.push(logo)
        }

        // Contact information
        for ci in c.contact_information {
            vec.push(ci.into());
        }

        // Work Address
        if let Some(adr) = c.work_address {
            let mut adr: Component = adr.into();
            adr.parameters
                .insert("TYPE".to_string(), "work".to_string());
            vec.push(adr);
        }

        // Home Address
        if let Some(adr) = c.home_address {
            let mut adr: Component = adr.into();
            adr.parameters
                .insert("TYPE".to_string(), "home".to_string());
            vec.push(adr);
        }

        Self::new(vec)
    }
}

#[cfg(feature = "read_write")]
impl TryFrom<read_write::vcard::VCard> for Contact {
    type Error = Box<dyn std::error::Error>;

    fn try_from(vcard: read_write::vcard::VCard) -> Result<Self, Self::Error> {
        // Create a contact - with no name (for now)
        let mut contact = Self::new(Name::default());

        // Loop through all the components
        for mut comp in vcard.0 {
            match comp.name.as_str() {
                "UID" => {
                    if let Some(uid) =
                        comp.values.pop().and_then(|mut x| x.pop())
                    {
                        contact.uid = uid
                    }
                }

                "FN" => {
                    if contact.name == Name::default() {
                        if let Some(name) =
                            comp.values.pop().and_then(|mut x| x.pop())
                        {
                            contact.name.given = Some(name);
                        }
                    }
                }

                "N" => contact.name = comp.into(),

                "NICKNAME" => {
                    contact.nickname = Some(
                        comp.values
                            .pop()
                            .and_then(|mut x| x.pop())
                            .ok_or(FromComponentError::NotEnoughValues)?,
                    )
                }

                "ANNIVERSARY" => contact.anniversary = Some(comp.try_into()?),

                "BDAY" => contact.bday = Some(comp.try_into()?),

                "PHOTO" => contact.photo = Some(comp.try_into()?),

                "TITLE" => {
                    contact.title = Some(
                        comp.values
                            .pop()
                            .and_then(|mut x| x.pop())
                            .ok_or(FromComponentError::NotEnoughValues)?,
                    )
                }

                "ROLE" => {
                    contact.role = Some(
                        comp.values
                            .pop()
                            .and_then(|mut x| x.pop())
                            .ok_or(FromComponentError::NotEnoughValues)?,
                    )
                }

                "ORG" => contact.org = Some(Org::from(comp)),

                "LOGO" => contact.logo = Some(comp.try_into()?),

                "EMAIL" | "TEL" | "X-DISCORD" | "X-MATRIX" | "X-SKYPE"
                | "X-AIM" | "X-JABBER" | "X-ICQ" | "X-GROUPWISE"
                | "X-GADUGADU" | "IMPP" => {
                    contact.contact_information.push(comp.try_into()?)
                }

                // Addresses.
                //
                // Note that if ambigouous it is assumed that an address
                // is home.
                "ADR" => match comp
                    .parameters
                    .get("TYPE")
                    .map_or("home", String::as_str)
                {
                    "work" => contact.work_address = Some(comp.try_into()?),
                    _ => contact.home_address = Some(comp.try_into()?),
                },
                // Values which we don't know shall be scrapped.
                _ => (),
            }
        }

        Ok(contact)
    }
}