author | Jordan Doyle <jordan@doyle.la> | 2023-09-17 1:45:01.0 +01:00:00 |
---|---|---|
committer | Jordan Doyle <jordan@doyle.la> | 2023-09-17 1:45:01.0 +01:00:00 |
commit | 7504ef7a6959829acceba7d069b4de635e9a724d [patch] |
|
tree | d200be75a0da15f2a9ef2a7e761194f632cbe7ce |
|
parent | 12249d2c581cdedf2e6b946ce947e4931ca5736c |
|
download | 7504ef7a6959829acceba7d069b4de635e9a724d.tar.gz |
Add JsContact types from draft-ietf-jmap-jscontact-07
Diff
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>(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, } 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;