Skip to content

Repository files navigation

I/O Addressbook Documentation Matrix Mastodon

Addressbook client library, written in Rust.

This library is composed of 2 feature-gated layers:

  • Low-level I/O-free coroutines: these no_std-compatible state machines wrap the underlying io-vdir and io-webdav coroutines and surface a shared least-common-denominator type on completion
  • Mid-level std client: a standard, blocking client; an addressbook account speaks one protocol at a time, so the client is an enum holding the single active backend

Table of contents

Features

  • Shared LCD types: Addressbook and Card that fit both local Vdir and CardDAV, with byte-oriented card contents.
  • I/O-free coroutines: no_std state machines per (backend, operation), wrapping the underlying io-* coroutine and producing a shared type on completion.
  • Std client (client feature): blocking client built as an enum over the active backend; construct it from a backend client via From.
  • TLS for the CardDAV backend (gated by the same rustls-ring / rustls-aws / native-tls features as io-webdav).
  • Optional vCard parsing (parser feature, calcard-backed) and serde round-trip on every shared type (serde feature).

Tip

I/O Addressbook is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml or on docs.rs.

Backend coverage

Operation Vdir WebDAV
list_addressbooks yes yes
create_addressbook yes yes
update_addressbook yes yes
delete_addressbook yes yes
list_cards yes yes
get_card yes yes
create_card yes yes
update_card yes yes
delete_card yes yes

Usage

I/O Addressbook can be consumed two ways, depending on how much of the I/O stack you want to own. Each mode is gated by cargo features.

Every shared-API coroutine implements the backend trait of the protocol it targets (io_vdir::coroutine::VdirCoroutine for Vdir, io_webdav::coroutine::WebdavCoroutine for CardDAV). The resume(...) method returns the matching <Backend>CoroutineState<Yield, Return> with two variants:

  • Yielded(Y): intermediate. Y is the backend's standard yield (WantsDirCreate / WantsFileRead / WantsRename etc. for Vdir, WantsRead / WantsWrite for CardDAV).
  • Complete(R): terminal. By convention R = Result<Output, Error> carrying the operation's final value typed against the shared Addressbook / Card.

The std client owns the resume loop for you; the I/O-free mode hands it back so you can drive the same coroutine under any blocking, async, or fuzz harness.

Coroutines

No client feature required: every wrapper lives under <domain>::<protocol>::<op> (for example addressbook::vdir::create::VdirAddressbookCreate, card::webdav::get::WebdavCardGet) and is built straight from the shared inputs. You own the loop and the syscalls; the library only produces operations and consumes their results.

Create a Vdir addressbook against a blocking caller (the same shape works under async or in-memory replay):

use std::fs;

use io_addressbook::addressbook::vdir::create::VdirAddressbookCreate;
use io_vdir::{coroutine::*, path::VdirPath};

let root = VdirPath::new("/home/alice/contacts");

let mut coroutine = VdirAddressbookCreate::new(root, "personal", None, None).unwrap();
let mut arg: Option<VdirReply> = None;

let id = loop {
    match coroutine.resume(arg.take()) {
        VdirCoroutineState::Complete(Ok(id)) => break id,
        VdirCoroutineState::Complete(Err(err)) => panic!("{err}"),
        VdirCoroutineState::Yielded(VdirYield::WantsDirCreate(paths)) => {
            for path in paths {
                fs::create_dir_all(path.as_str()).unwrap();
            }
            arg = Some(VdirReply::DirCreate);
        }
        VdirCoroutineState::Yielded(VdirYield::WantsFileCreate(files)) => {
            for (path, bytes) in files {
                fs::write(path.as_str(), &bytes).unwrap();
            }
            arg = Some(VdirReply::FileCreate);
        }
        VdirCoroutineState::Yielded(other) => unreachable!("unexpected {other:?}"),
    }
};

println!("created addressbook {id}");

The CardDAV backend follows the same pattern but yields WantsRead / WantsWrite(Vec<u8>) instead; see io-webdav for the full TCP / TLS / discovery setup that connects the stream before the wrapper coroutine runs.

Std client

Enable the client feature (pulled in by every backend feature) and at least one backend. Build a per-backend client (VdirClient, WebdavClient) around its inner io-* client, then wrap it into the unified AddressbookClientStd via From.

[dependencies]
io-addressbook = "0.0.2"
use io_addressbook::{client::AddressbookClientStd, vdir::client::VdirClient};
use io_vdir::{client::VdirClient as InnerVdirClient, path::VdirPath};

let root = VdirPath::new("/home/alice/contacts");
let mut client = AddressbookClientStd::from(VdirClient::new(InnerVdirClient::new(root)));

for book in client.list_addressbooks().unwrap() {
    println!("{}: {}", book.id, book.name);
}

Examples

Have a look at real-world projects built on top of this library:

AI disclosure

This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.

  • Tools: Claude Code (Anthropic), Opus 4.8, invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.

  • Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.

  • Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.

  • Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit (nix develop --command cargo check / cargo test / cargo fmt). Behavioural correctness is verified against the relevant RFC or upstream spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour.

  • Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale RFC references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.

  • Last reviewed: 11/06/2026

License

This project is licensed under either of:

at your option.

Social

Sponsoring

nlnet

Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:

If you appreciate the project, feel free to donate using one of the following providers:

GitHub Ko-fi Buy Me a Coffee Liberapay PayPal

About

Set of I/O-free coroutines to manage contacts

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages