use std::{borrow::Cow, collections::HashMap};
use chrono::NaiveDate;
use serde::{
ser::SerializeMap, Deserialize, Serialize, Serializer, __private::ser::FlatMapSerializer,
};
use serde_json::Value;
use crate::common::{Id, UnsignedInt, UtcDate};
#[derive(Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct TypeWrapper<T>(T);
impl<T: Serialize + TypedStruct> Serialize for TypeWrapper<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("@type", T::KIND)?;
self.0.serialize(FlatMapSerializer(&mut map))?;
map.end()
}
}
pub trait TypedStruct {
const KIND: &'static str;
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "PascalCase", tag = "@type")]
pub enum Data<'a> {
Card(#[serde(borrow)] Card<'a>),
CardGroup(CardGroup<'a>),
}
/// A CardGroup object represents a group of cards. Its members may be Cards or CardGroups.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CardGroup<'a> {
/// An identifier, used to associate the object as the same across different
/// systems, addressbooks and views.
#[serde(borrow)]
uid: Id<'a>,
/// The set is represented as an object, with each key being the uid of another Card or
/// CardGroup. The value for each key in the object MUST be true.
members: HashMap<Id<'a>, bool>,
/// The user-visible name for the group, e.g. "Friends". This may be any UTF-8 string of at
/// least 1 character in length and maximum 255 octets in size. The same name may be used by
/// two different groups.
#[serde(default)]
name: Cow<'a, str>,
/// The card that represents this group.
card: Option<Card<'a>>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Card<'a> {
/// An identifier, used to associate the object as the same across different
/// systems, addressbooks and views.
#[serde(borrow)]
uid: Id<'a>,
/// The identifier for the product that created the Card object.
prod_id: Option<Cow<'a, str>>,
/// The date and time when this Card object was created.
created: Option<UtcDate>,
/// The date and time when the data in this Card object was last modified.
updated: Option<UtcDate>,
/// The kind of the entity the Card represents.
kind: Option<CardKind>,
/// Relates the object to other Card and CardGroup objects. This is
/// represented as a map, where each key is the
#[serde(default)]
related_to: HashMap<Id<'a>, TypeWrapper<Relation>>,
/// Language used for free-form text on this card.
language: Option<Cow<'a, str>>,
/// The name components of the name of the entity represented by this Card.
#[serde(default)]
name: Vec<TypeWrapper<NameComponent<'a>>>,
/// The full name (e.g. the personal name and surname of an individual, the
/// name of an organization) of the entity represented by this card. The
/// purpose of this property is to define a name, even if the individual
/// name components are not known. In addition, it is meant to provide
/// alternative versions of the name for internationalisation.
///
/// Implementations SHOULD prefer using the name property over this one
/// and SHOULD NOT store the concatenated name component values in this
/// property.
#[serde(default)]
full_name: Cow<'a, str>,
/// The nick names of the entity represented by this card.
#[serde(default)]
nick_names: Vec<Cow<'a, str>>,
/// The companies or organization names and units associated with this
/// card.
#[serde(default)]
organizations: HashMap<Id<'a>, TypeWrapper<Organization<'a>>>,
/// The job titles or functional positions of the entity represented by
/// this card.
#[serde(default)]
titles: HashMap<Id<'a>, TypeWrapper<Title<'a>>>,
/// The email addresses to contact the entity represented by this card.
#[serde(default)]
emails: HashMap<Id<'a>, TypeWrapper<EmailAddress<'a>>>,
/// The phone numbers to contact the entity represented by this card.
#[serde(default)]
phones: HashMap<Id<'a>, TypeWrapper<Phone<'a>>>,
/// The online resources and services that are associated with the entity
/// represented by this card.
#[serde(default)]
online: HashMap<Id<'a>, TypeWrapper<Resource<'a>>>,
/// A map of photo ids to File objects that contain photographs or images
/// associated with this card. A typical use case is to include an avatar for display along the
/// contact name.
#[serde(default)]
photos: HashMap<Id<'a>, TypeWrapper<File<'a>>>,
/// Defines the preferred method to contact the holder of this card.
preferred_contact_method: Option<PreferredContactMethod>,
/// Defines the preferred languages for contacting the entity associated with this card. The
/// keys in the object MUST be [RFC5646] language tags. The values are a (possibly empty) list
/// of contact language preferences for this language. A valid ContactLanguage object MUST have
/// at least one of its properties set.
#[serde(default)]
preferred_contact_languages: HashMap<String, TypeWrapper<ContactLanguage>>,
/// A map of address ids to Address objects, containing physical locations.
#[serde(default)]
address: HashMap<Id<'a>, TypeWrapper<Address<'a>>>,
/// A map of language tags [RFC5646] to patches, which localize a property value into the
/// locale of the respective language tag.
///
/// A patch MUST NOT target the localizations property.
#[serde(default)]
localizations: HashMap<Cow<'a, str>, Value>,
/// These are memorable dates and events for the entity represented by this card.
#[serde(default)]
anniversaries: HashMap<Id<'a>, TypeWrapper<Anniversary<'a>>>,
/// Defines personal information about the entity represented by this card.
#[serde(default)]
personal_info: HashMap<Id<'a>, TypeWrapper<PersonalInfo<'a>>>,
/// Arbitrary notes about the entity represented by this card.
#[serde(default)]
notes: Cow<'a, str>,
/// The set of free-text or URI categories that relate to the card. The set is represented as
/// an object, with each key being a category. The value for each key in the object MUST be
/// true.
#[serde(default)]
categories: HashMap<Cow<'a, str>, bool>,
/// Maps identifiers of custom time zones to their time zone definitions. For a description of
/// this property see the timeZones property definition in [RFC8984].
#[serde(default)]
time_zones: HashMap<Cow<'a, str>, Value>,
}
/// Defines personal information about the entity represented by this card.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PersonalInfo<'a> {
/// Specifies the type for this personal information.
#[serde(rename = "type")]
type_: PersonalInfoType,
/// The actual information. This generally is free-text, but future
/// specifications MAY restrict allowed values depending on the type of
/// this PersonalInformation.
value: Cow<'a, str>,
/// Indicates the level of expertise, or engagement in hobby or interest.
level: Option<PersonalInfoLevel>,
}
impl TypedStruct for PersonalInfo<'_> {
const KIND: &'static str = "PersonalInfo";
}
/// Specifies the type for this personal information.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum PersonalInfoType {
/// A field of expertise or credential
Expertise,
/// A hobby
Hobby,
/// An interest
Interest,
/// An information not covered by the above categories
Other,
}
/// Indicates the level of expertise, or engagement in hobby or interest.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum PersonalInfoLevel {
High,
Medium,
Low,
}
/// These are memorable dates and events for the entity represented by this card.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Anniversary<'a> {
/// Specifies the type of the anniversary.
#[serde(rename = "type")]
type_: AnniversaryType,
/// A label describing the value in more detail, especially if the type
/// property has value other (but MAY be included with any type).
#[serde(default)]
label: Cow<'a, str>,
/// The date of this anniversary, in the form "YYYY-MM-DD"
/// (any part may be all 0s for unknown) or a [RFC3339] timestamp.
date: NaiveDate,
/// An address associated with this anniversary, e.g. the place of birth or
/// death.
place: Option<Address<'a>>,
}
impl TypedStruct for Anniversary<'_> {
const KIND: &'static str = "Anniversary";
}
/// Specifies the type of the anniversary.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum AnniversaryType {
/// A birth day anniversary
Birth,
/// A death day anniversary
Death,
/// An anniversary not covered by any of the known types.
Other,
}
/// A physical location.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Address<'a> {
/// The complete address, excluding type and label. This property is mainly useful to
/// represent addresses of which the individual address components are unknown, or to provide
/// localized representations.
#[serde(default)]
full_address: Cow<'a, str>,
/// The street address. The concatenation of the component values, separated by whitespace,
/// SHOULD result in a valid street address for the address locale. Doing so, implementations
/// MAY ignore any separator components. The StreetComponent object type is defined in the
/// paragraph below.
#[serde(default)]
street: Vec<TypeWrapper<StreetComponent<'a>>>,
/// The city, town, village, post town, or other locality within which the street address may
/// be found.
#[serde(default)]
locality: Cow<'a, str>,
/// The province, such as a state, county, or canton within which the locality may be found.
#[serde(default)]
region: Cow<'a, str>,
/// The country name.
#[serde(default)]
country: Cow<'a, str>,
/// The postal code, post code, ZIP code or other short code associated with the address by the
/// relevant country's postal system.
#[serde(default)]
postcode: Cow<'a, str>,
/// The ISO-3166-1 country code.
#[serde(default)]
country_code: Cow<'a, str>,
/// A [RFC5870] "geo:" URI for the address.
#[serde(default)]
coordinates: Cow<'a, str>,
/// Identifies the time zone this address is located in. This either MUST be a time zone name
/// registered in the IANA Time Zone Database, or it MUST be a valid TimeZoneId as defined in
/// [RFC8984]. For the latter, a corresponding time zone MUST be defined in the timeZones
/// property.
#[serde(default)]
time_zone: Cow<'a, str>,
/// The contexts of the address information.
#[serde(default)]
context: HashMap<AddressContext, bool>,
/// A label describing the value in more detail.
#[serde(default)]
label: Cow<'a, str>,
/// The preference of this address in relation to other addresses.
pref: Option<Preference>,
}
impl TypedStruct for Address<'_> {
const KIND: &'static str = "Address";
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase", untagged)]
pub enum AddressContext {
/// An address to be used for billing.
Billing,
/// An address to be used for delivering physical items
Postal,
/// A normal context
Other(Context),
}
/// The street address. The concatenation of the component values, separated by whitespace, SHOULD
/// result in a valid street address for the address locale. Doing so, implementations MAY ignore
/// any separator components. The StreetComponent object type is defined in the paragraph below.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct StreetComponent<'a> {
/// The type of this street component.
#[serde(rename = "type")]
type_: StreetComponentKind,
/// The value of this street component.
value: Cow<'a, str>,
}
impl TypedStruct for StreetComponent<'_> {
const KIND: &'static str = "StreetComponent";
}
/// The type of this street component.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum StreetComponentKind {
Name,
Number,
Apartment,
Room,
Extension,
Direction,
Building,
Floor,
PostOfficeBox,
Separator,
Unknown,
}
/// Defines the preferred method to contact the holder of this card.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ContactLanguage {
/// Defines the context in which to use this language.
context: Option<Context>,
/// Defines the preference of this language in relation to other
/// languages of the same context.
pref: Option<Preference>,
}
impl TypedStruct for ContactLanguage {
const KIND: &'static str = "ContactLanguage";
}
/// Defines the preferred method to contact the holder of this card.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum PreferredContactMethod {
Emails,
Phones,
Online,
}
/// A map of photo ids to File objects that contain photographs or images
/// associated with this card. A typical use case is to include an avatar for display along the
/// contact name.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct File<'a> {
/// A URI where to fetch the data of this file.
href: Cow<'a, str>,
/// The content-type of the file, if known.
media_type: Cow<'a, str>,
/// The size, in octets, of the file when fully decoded (i.e., the number
/// of octets in the file the user would download), if known.
size: Option<UnsignedInt>,
/// The preference of this photo in relation to other photos.
pref: Option<Preference>,
}
impl TypedStruct for File<'_> {
const KIND: &'static str = "File";
}
/// The online resources and services that are associated with the entity
/// represented by this card.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Resource<'a> {
/// resource value, where the allowed value form is defined by the the type
/// property. In any case the value MUST NOT be empty.
resource: Cow<'a, str>,
/// The type of the resource value.
#[serde(rename = "type")]
type_: ResourceType,
/// Used for URI resource values. Provides the media type [RFC2046] of the
/// resource identified by the URI.
#[serde(default)]
media_type: Cow<'a, str>,
/// The contexts in which to use this resource.
#[serde(default)]
context: HashMap<Context, bool>,
/// A label describing the value in more detail, especially if the type
/// property has value other (but MAY be included with any type).
#[serde(default)]
label: Cow<'a, str>,
/// The preference of this resource in relation to other resources.
pref: Option<Preference>,
}
impl TypedStruct for Resource<'_> {
const KIND: &'static str = "Resource";
}
/// The type of the resource value.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum ResourceType {
/// The resource value is a URI, e.g. a website link. This MUST be a valid URI as defined in
/// Section 3 of [RFC3986] and updates.
Uri,
/// The resource value is a username associated with the entity represented by this card (e.g.
/// for social media, or an IM client). The label property SHOULD be included to identify what
/// service this is for. For compatibility between clients, this label SHOULD be the canonical
/// service name, including capitalisation. e.g. Twitter, Facebook, Skype, GitHub, XMPP. The
/// resource value may be any non-empty free text.
Username,
/// The resource value is something else not covered by the above categories. A label property
/// MAY be included to display next to the number to help the user identify its purpose. The
/// resource value may be any non-empty free text.
Other,
}
/// The phone numbers to contact the entity represented by this card.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Phone<'a> {
/// The phone value, as either a URI or a free-text phone number. Typical
/// URI schemes are the [RFC3966] tel or [RFC3261] sip schemes, but any
/// URI scheme is allowed.
phone: Cow<'a, str>,
/// The set of contact features that this phone number may be used for. The
/// set is represented as an object, with each key being a method type. The
/// value for each key in the object MUST be true.
#[serde(default)]
features: HashMap<PhoneFeature, bool>,
// The contexts in which to use this number. The value for each
/// key in the object MUST be true.
#[serde(default)]
contexts: HashMap<Context, bool>,
/// A label describing the value in more detail, especially if the type
/// property has value other (but MAY be included with any type).
#[serde(default)]
label: Cow<'a, str>,
/// The preference of this email address in relation to other email addresses.
pref: Option<Preference>,
}
impl TypedStruct for Phone<'_> {
const KIND: &'static str = "Phone";
}
/// The email addresses to contact the entity represented by this card.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum PhoneFeature {
/// The number is for calling by voice.
Voice,
/// The number is for sending faxes.
Fax,
/// The number is for a pager or beeper.
Pager,
/// The number supports text messages (SMS).
Text,
/// The number is for a cell phone.
Cell,
/// The number is for a device for people with hearing or speech difficulties.
Textphone,
/// The number supports video conferencing.
Video,
/// The number is for some other purpose. The label property MAY be included
/// to display next to the number to help the user identify its purpose.
Other,
}
/// The email addresses to contact the entity represented by this card.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct EmailAddress<'a> {
/// The email address. This MUST be an addr-spec value as defined in
/// Section 3.4.1 of [RFC5322].
email: Cow<'a, str>,
/// The contexts in which to use this email address. The value for each
/// key in the object MUST be true.
#[serde(default)]
contexts: HashMap<Context, bool>,
/// The preference of this email address in relation to other email addresses.
pref: Option<Preference>,
}
impl TypedStruct for EmailAddress<'_> {
const KIND: &'static str = "EmailAddress";
}
/// This data type allows to define a preference order on same-typed contact
/// information. For example, a card holder may have two email addresses and
/// prefer to be contacted with one of them.
///
/// A preference value MUST be an integer number in the range 1 and 100. Lower
/// values correspond to a higher level of preference, with 1 being most
/// preferred. If no preference is set, then the contact information MUST be
/// interpreted as being least preferred.
///
/// Note that the preference only is defined in relation to contact information
/// of the same type. For example, the preference orders within emails and
/// phone numbers are indendepent of each other. Also note that the
/// preferredContactMethod property allows to define a preferred contact method
/// across method types.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub struct Preference(u8);
/// The companies or organization names and units associated with this card.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Title<'a> {
/// The name of this organization.
#[serde(borrow)]
name: Cow<'a, str>,
/// The id of the organization in which this title is held.
#[serde(default)]
organization: Vec<Id<'a>>,
}
impl TypedStruct for Title<'_> {
const KIND: &'static str = "Title";
}
/// The companies or organization names and units associated with this card.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Organization<'a> {
/// The name of this organization.
name: Cow<'a, str>,
/// Additional levels of organizational unit names.
#[serde(default)]
units: Vec<Cow<'a, str>>,
}
impl TypedStruct for Organization<'_> {
const KIND: &'static str = "Organization";
}
/// The name components of the name of the entity represented by this Card. Name
/// components SHOULD be ordered such that their values joined by whitespace
/// produce a valid full name of this entity. Doing so, implementations MAY
/// ignore any separator components.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct NameComponent<'a> {
value: Cow<'a, str>,
#[serde(rename = "type")]
type_: NameComponentKind,
}
impl TypedStruct for NameComponent<'_> {
const KIND: &'static str = "NameComponent";
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum NameComponentKind {
/// The value is a honorific title(s), e.g. "Mr", "Ms", "Dr".
Prefix,
/// The value is a personal name(s), also known as "first name", "given name".
Personal,
/// The value is a surname, also known as "last name", "family name".
Surname,
/// The value is an additional name, also known as "middle name".
Additional,
/// The value is a honorific suffix, e.g. "B.A.", "Esq.".
Suffix,
/// A separator for two name components. The value property of the component
/// includes the verbatim separator, for example a newline character.
Separator,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Relation {
relation: HashMap<RelationKind, bool>,
}
impl TypedStruct for Relation {
const KIND: &'static str = "Relation";
}
/// Contact information typically is associated with a context in which it
/// should be used. For example, someone might have distinct phone numbers
/// for work and private contexts. The Context data type enumerates common
/// contexts.
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum Context {
/// The contact information may be used to contact the card holder in a
/// private context.
Private,
/// The contact information may be used to contact the card holder in a
/// professional context.
Work,
/// The contact information may be used to contact the card holder in some
/// other context.
Other,
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum RelationKind {
Contact,
Acquaintance,
Friend,
Met,
CoWorker,
Colleague,
CoResident,
Neighbor,
Child,
Parent,
Sibling,
Spouse,
Kin,
Muse,
Crush,
Date,
Sweetheart,
Me,
Agent,
Emergency,
}
/// The kind of the entity the Card represents.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum CardKind {
/// A single person
Individual,
/// An organization
Org,
/// A named location
Location,
/// A device, such as appliances, computers, or network elements
Device,
/// A software application
Application,
}