From 7504ef7a6959829acceba7d069b4de635e9a724d Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sun, 17 Sep 2023 01:45:01 +0100 Subject: [PATCH] Add JsContact types from draft-ietf-jmap-jscontact-07 --- jmap-proto/src/lib.rs | 1 + jmap-proto/src/extensions/js_contact.rs | 640 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ jmap-proto/src/extensions/mod.rs | 1 + 3 files changed, 642 insertions(+) diff --git a/jmap-proto/src/lib.rs b/jmap-proto/src/lib.rs index e5c772a..f89ca07 100644 --- a/jmap-proto/src/lib.rs +++ a/jmap-proto/src/lib.rs @@ -1,5 +1,6 @@ pub mod common; pub mod endpoints; pub mod errors; pub mod events; +pub mod extensions; pub(crate) mod util; diff --git a/jmap-proto/src/extensions/js_contact.rs b/jmap-proto/src/extensions/js_contact.rs new file mode 100644 index 0000000..56646be 100644 --- /dev/null +++ a/jmap-proto/src/extensions/js_contact.rs @@ -1,0 +1,640 @@ +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); + +impl Serialize for TypeWrapper { + fn serialize(&self, serializer: S) -> Result + 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, 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>, +} + +#[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>, + /// The date and time when this Card object was created. + created: Option, + /// The date and time when the data in this Card object was last modified. + updated: Option, + /// The kind of the entity the Card represents. + kind: Option, + /// 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, TypeWrapper>, + /// Language used for free-form text on this card. + language: Option>, + /// The name components of the name of the entity represented by this Card. + #[serde(default)] + name: Vec>>, + /// 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>, + /// The companies or organization names and units associated with this + /// card. + #[serde(default)] + organizations: HashMap, TypeWrapper>>, + /// The job titles or functional positions of the entity represented by + /// this card. + #[serde(default)] + titles: HashMap, TypeWrapper>>, + /// The email addresses to contact the entity represented by this card. + #[serde(default)] + emails: HashMap, TypeWrapper>>, + /// The phone numbers to contact the entity represented by this card. + #[serde(default)] + phones: HashMap, TypeWrapper>>, + /// The online resources and services that are associated with the entity + /// represented by this card. + #[serde(default)] + online: HashMap, TypeWrapper>>, + /// 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, TypeWrapper>>, + /// Defines the preferred method to contact the holder of this card. + preferred_contact_method: Option, + /// 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>, + /// A map of address ids to Address objects, containing physical locations. + #[serde(default)] + address: HashMap, TypeWrapper>>, + /// 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, Value>, + /// These are memorable dates and events for the entity represented by this card. + #[serde(default)] + anniversaries: HashMap, TypeWrapper>>, + /// Defines personal information about the entity represented by this card. + #[serde(default)] + personal_info: HashMap, TypeWrapper>>, + /// 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, 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, 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, +} + +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>, +} + +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>>, + /// 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, + /// 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, +} + +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, + /// Defines the preference of this language in relation to other + /// languages of the same context. + pref: Option, +} + +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, + /// The preference of this photo in relation to other photos. + pref: Option, +} + +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, + /// 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, +} + +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, + // The contexts in which to use this number. The value for each + /// key in the object MUST be true. + #[serde(default)] + contexts: HashMap, + /// 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, +} + +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, + /// The preference of this email address in relation to other email addresses. + pref: Option, +} + +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>, +} + +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>, +} + +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, +} + +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, +} diff --git a/jmap-proto/src/extensions/mod.rs b/jmap-proto/src/extensions/mod.rs new file mode 100644 index 0000000..034ca9c 100644 --- /dev/null +++ a/jmap-proto/src/extensions/mod.rs @@ -1,0 +1,1 @@ +pub mod js_contact; -- rgit 0.1.3