Skip to main content

Crate jmap_jscontact_types

Crate jmap_jscontact_types 

Source
Expand description

JSContact (RFC 9553) typed sub-types for the jmap-* crate family.

Normative reference: RFC 9553 (JSContact).

These are sub-object types that have no JMAP identity of their own. They are embedded within ContactCard (from jmap-contacts-types).

§Crate family position

(no JMAP dep)
    └── jmap-jscontact-types  ← this crate (RFC 9553 typed sub-types)
            └── jmap-contacts-types (consumes via path-dep + re-export)

§Design: optional fields and Option<...>

RFC 9553 marks most fields optional, and JMAP properties arguments permit partial responses. Every optional field is Option<...> with #[serde(skip_serializing_if = "Option::is_none")] so partial inputs round-trip unchanged.

Mandatory fields per the RFC are normally kept as bare types (not Option) to express the requirement at the type level — callers building a fresh sub-object must populate them. The exception is the Resource-derived types (Calendar, CryptoKey, Directory, Link, Media), whose RFC-mandatory kind and uri fields are modelled as Option to permit partial-response deserialization (a JMAP client requesting properties: ["kind"] on a Calendar legitimately receives a JSON object with no uri and must round-trip it unchanged).

The trade-off: callers can construct, e.g., Calendar { kind: None, uri: None, ... } and serialize a wire object that no spec-conformant peer can validate. The type system does not catch this; the kit’s posture is “types model the wire shape; semantic validity is the consumer’s job” (see the kit-vs-jig section in the workspace AGENTS.md). Callers building a fresh value for emission MUST populate the mandatory-on-wire fields themselves before serializing.

Mandatory-on-wire fields modelled as Option (8 sites across 5 types), gathered into one place so each consumer does not have to re-derive them from the per-field rustdoc:

StructMandatory fieldRFC section
CalendarkindRFC 9553 §2.4.1
CalendaruriRFC 9553 §1.4.4
CryptoKeyuriRFC 9553 §1.4.4
DirectorykindRFC 9553 §2.6.2
DirectoryuriRFC 9553 §1.4.4
LinkuriRFC 9553 §1.4.4
MediakindRFC 9553 §2.6.4
MediauriRFC 9553 §1.4.4

§Design: cross-field invariants are not type-enforced

Six structs carry a cross-field “at least one of X, Y must be set” constraint at the rustdoc level that the Rust type system does not enforce. The kit’s posture is “types model the wire shape; semantic validity is the consumer’s job” (see the kit-vs-jig section in the workspace AGENTS.md). Encoding these constraints in the type system would diverge from that posture and force the partial-response Option modelling into a corner.

Callers building a fresh value for emission MUST validate the constraint themselves before serializing. The constraints, gathered into one place so each consumer does not have to re-derive them from the per-struct rustdoc:

StructConstraint
Nameat least one of components or full
Organizationat least one of name or units
SpeakToAsat least one of grammatical_gender or pronouns
OnlineServiceat least one of uri or user
Addressat least one of components, coordinates, country_code, full, or time_zone
Authorat least one property other than @type

Deserialize does not reject inputs that violate these constraints — the kit accepts partial-response inputs that legitimately omit fields. The constraint applies only to emitting fresh values. See bd:JMAP-sgrr.30.

§Design: numeric-range bounds are not type-enforced

RFC 9553 specifies bounded ranges for several numeric fields, but the kit models them as bare u32 (or Option<u32>) without type-level guarantees. Deserialize accepts out-of-range values without complaint; callers building a fresh value MUST verify the bound themselves before serializing. The kit’s posture — types model the wire shape, semantic validity is the consumer’s job — takes precedence over the NonZeroU32 / BoundedU8 patterns that would push validation into the type system but require breaking API changes to introduce later.

Affected fields, gathered into one place:

Field(s)RFC boundRFC section
.pref on Nickname, Pronouns, EmailAddress, OnlineService, Phone, LanguagePref, Calendar, SchedulingAddress, Address, CryptoKey, Directory, Link, Media (13 sites)1..=100, lower = more preferredRFC 9553 §1.5.3
Directory::list_as> 0RFC 9553 §2.6.2
PersonalInfo::list_as> 0RFC 9553 §2.8.4
PartialDate::month1..=12RFC 9553 §2.8.1
PartialDate::day1..=31 (with month-specific cap)RFC 9553 §2.8.1

Newtypes like Pref(NonZeroU8) capped at 100 or Option<NonZeroU32> for list_as would express the contract at the type level, but adopting them is a public-API break the kit is not taking; see bd:JMAP-sgrr.14, bd:JMAP-sgrr.15, and bd:JMAP-sgrr.22.

§Design: @type discriminator

Every RFC 9553 sub-object has an @type discriminator on the wire. The Rust field is named at_type: Option<String> and renamed to "@type" via serde attributes, with default and skip_serializing_if = "Option::is_none". The field is modelled as Option<String> (not bare String) because RFC 9553 §1.3.4 permits omitting @type whenever the type is implied by context — most notably when the value is in a defaultType position (see Anniversary::date / AnniversaryDate for the worked example). The value type is String (not an enum) to preserve forward- compatibility with new sub-object types.

§Design: Resource-derived types

RFC 9553 §1.4.4 defines the abstract Resource common fields (@type, kind, uri, mediaType, contexts, pref, label). Five concrete types extend Resource: Calendar, CryptoKey, Directory, Link, Media. Each embeds the common fields directly because the RFC defines the inheritance for documentation only — the wire format is a flat object per concrete type.

Structs§

Address
A postal or geographic address (RFC 9553 §2.5.1).
AddressComponent
A single component of an Address (RFC 9553 §2.5.1.2).
Anniversary
A memorable date or event (RFC 9553 §2.8.1).
Author
The author of a Note (RFC 9553 §2.8.3).
Calendar
A calendaring resource (RFC 9553 §2.4.1).
CryptoKey
A cryptographic key or certificate associated with a Card (RFC 9553 §2.6.1). Extends the abstract Resource type.
Directory
A directory service associated with a Card (RFC 9553 §2.6.2).
EmailAddress
An email address (RFC 9553 §2.3.1).
LanguagePref
A preferred language (RFC 9553 §2.3.4).
Link
A generic resource link associated with a Card (RFC 9553 §2.6.3).
Media
A media resource associated with a Card (RFC 9553 §2.6.4).
Name
The name of the entity represented by a Card (RFC 9553 §2.2.1).
NameComponent
A single component of a Name (RFC 9553 §2.2.1.2).
Nickname
A nickname for the entity represented by a Card (RFC 9553 §2.2.2).
Note
A free-text note associated with a Card (RFC 9553 §2.8.3).
OnlineService
An online service (messaging service, social media, etc.) (RFC 9553 §2.3.2).
OrgUnit
An organizational unit within an Organization (RFC 9553 §2.2.3).
Organization
A company or organization name associated with a Card (RFC 9553 §2.2.3).
PartialDate
A complete or partial Gregorian calendar date (RFC 9553 §2.8.1).
PersonalInfo
Personal information such as an expertise, hobby, or interest (RFC 9553 §2.8.4).
Phone
A phone number (RFC 9553 §2.3.3).
Pronouns
A pronouns entry (RFC 9553 §2.2.4).
Relation
A relationship to another Card (RFC 9553 §2.1.8).
SchedulingAddress
An iTIP scheduling address (RFC 9553 §2.4.2).
SpeakToAs
How to address or refer to the entity represented by a Card (RFC 9553 §2.2.4).
Timestamp
A UTC point in time (RFC 9553 §2.8.1).
Title
A job title or functional position (RFC 9553 §2.2.5).

Enums§

AnniversaryDate
The date value of an Anniversary — either a PartialDate or a Timestamp (RFC 9553 §2.8.1).