Skip to main content

Crate vcard

Crate vcard 

Source
Expand description

§vcard-rs

One version-agnostic vCard library: a decoded model and a byte-faithful syntax tree that read and write vCard 2.1 (versitcard), 3.0 (RFC 2426) and 4.0 (RFC 6350) alike.

The version is a decoded indicator, never a type parameter: the tree ignores it, and only the codec and the per-property spec branch on it.

The crate is no_std (with alloc), its core is dependency-free, and every dependency sits behind a cargo feature.

This header is the architecture; the behaviour behind it is specified capability by capability in the repository’s cairn/spec folder.

§Postel’s law

Parsing is maximally liberal: any real card round-trips byte for byte, its folds, its blank lines and its QUOTED-PRINTABLE soft breaks included, and an Unknown arm on every open enum carries vocabulary no version defines into the model.

Strictness lives on the way out: the builder refuses to construct a property the spec forbids, and validate checks a decoded card against its version’s RFC contract.

§The two layers

The decoded model (vcard, version, prop, param, value), with its builder and validator, is pure data with no dependency on the syntax side, so it can be depended on alone.

Property and parameter names and value kinds are closed identity enums (VcardPropKind, VcardParamKind, VcardValueKind) whose wire spelling is reached through FromStr and Deref.

A card is a Vcard: a version indicator and its properties, in source order.

A property is a VcardProp of a name, its parameters and one value, the last two open payload enums (VcardParam, VcardValue) with an Unknown arm, so anything outside the model survives.

The syntax tree (tree, behind the default parser feature) is everything byte-faithful. Its hub is VcardCst, generic nodes reproducing the wire bytes exactly.

A line is logical, its folding resolved for every layer above and recorded on its VcardWire shape, so serialization puts it back where it was and an edit that moves the bytes drops it.

parse reads one card, or a bare RFC 2425 record carrying no BEGIN and END envelope, and parse_many iterates a multi-card file.

decode projects a CST onto the decoded Vcard, and encode projects the model back to a canonical CST.

Per-property lens markers (VcardPropLens) read or edit one line through the byte-preserving cursors, and the three-way VcardMerge reconciles two divergent copies on those same edits.

A property value is raw bytes, so a foreign charset (a vCard 2.1 CHARSET) survives; a name or a parameter must be UTF-8, as every grammar guarantees. to_bytes is therefore the faithful serializer, and Display a convenience that is lossy only for a non-UTF-8 value.

§The spec layer

Each property carries a VcardPropSpec on its marker, declaring per version the value kinds and parameters it allows, and one vtable dispatch bridges the open VcardPropKind back to those static specs. It says what the RFC allows rather than how bytes are laid out, so it sits in prop with the model and needs no parser.

Most of that spec describes a property’s shape. One member describes the content of its value, and only the few whose RFC closes that content override it: GENDER’s sex code, PROFILE’s single value and CLIENTPIDMAP’s identifier. Every other vocabulary in the format ends in iana-token / x-name, which is an open set and nothing to check against.

That one source of truth has three readers: the decoder picks a value kind from it, validate checks conformance against it, and the builder rejects illegal construction with it.

A card that passes earns a VcardValid proof. Validity is a runtime predicate rather than a second type, since a conformant card may still carry extensions.

§Content encodings

The core transforms no content: a QUOTED-PRINTABLE or BASE64 encoding and a CHARSET are surfaced raw with their parameters kept, so nothing is silently transcoded.

Only the value grammar (escapes and folding) is resolved, and put back from the line’s wire shape on output.

Decoding is opt-in, one small no_std crate per feature: the value cursor exposes quoted_printable and charset, and the binary value exposes decode_base64.

§The JSON representations

jcard is the RFC 7095 spelling of this model in JSON, member for member.

jscontact is the RFC 9553 data model, which is a different model: a card is a Card object of named members, an ADR line is a structured Address, and a TYPE parameter is a context.

Both take a raw serde_json::Value at the boundary rather than a serde implementation, since one model with two JSON spellings is exactly what serde cannot key. jCard normalizes rather than preserves, while JSContact is lossless through the RFC 9555 escape hatches.

§Cargo features

parser (default) brings the byte-faithful tree and its codec, via the memchr crate. Everything under tree is gated on it. The decoded model is always available, and so are the spec layer above it, the builder, the validator and both JSON representations: none of them reads bytes.

Three content decoders are default too, one small crate each: quoted-printable decodes QUOTED-PRINTABLE value octets, base64 decodes inline BASE64 binary values, and encoding transcodes a foreign CHARSET to text through encoding_rs (the WHATWG Encoding Standard).

jcard adds the RFC 7095 JSON representation, via the serde_json crate. jscontact adds the RFC 9555 conversion to the RFC 9553 Card, implies jcard, whose syntax carries the escape hatch, and pulls no crate of its own.

Modules§

builder
Builders
jcardjcard
jCard
jscontactjscontact
JSContact
param
Parameters
prop
Properties
treeparser
Syntax tree
validator
Validator
value
Property values
vcard
vCard
version
Version