Expand description
§io-msgraph
I/O-free coroutines for the Microsoft Graph API, built on io-http (HTTP/1.1) and pumped by any stream the caller owns.
io-msgraph is the Microsoft Graph sibling of io-gmail: same shape (JSON over HTTP), different vendor. Unlike io-gmail, which is scoped to Gmail’s mail API, io-msgraph represents the whole Microsoft Graph API: the mail and contacts surfaces are covered today, and sibling resources (calendars among others) will be added under the same tree over time.
§Layers and features
The crate has two of the three standard Pimalaya layers; there is no
CLI. The always-present no_std core holds the I/O-free coroutines,
the whole Microsoft Graph REST logic. The client feature adds
v1::client::MsgraphClientStd, a std blocking client over any
stream, whose connect constructor opens the TCP/TLS connection
itself behind a TLS feature (rustls-ring by default, rustls-aws,
native-tls).
§Everything lives under v1
The Microsoft Graph API is versioned (/v1.0/), so the crate is
too: the version-agnostic coroutine contract stays at the crate
root, everything else lives under v1. The day a breaking Graph
version ships, a sibling module slots in without breaking v1
consumers.
§The coroutine contract
Every exchange implements coroutine::MsgraphCoroutine: resume
takes the bytes read since the last yield and either requests I/O
(coroutine::MsgraphYield WantsRead / WantsWrite) or
completes. The msgraph_try! macro is the coroutine equivalent
of ?.
A Graph call is a single HTTP request/response, so every REST
coroutine is a thin wrapper around one shared primitive,
v1::send::MsgraphSend: it builds the authorized request (bearer
token, JSON in and out) and parses either the 2xx body or Graph’s
error envelope into v1::send::MsgraphSendError. Redirects are
never followed. The terminal v1::send::MsgraphSendOutput carries
the parsed response plus a keep-alive flag so pumps can reuse the
connection across the many small requests a Graph session makes.
Empty 2xx bodies (DELETE, sendMail, send draft) deserialize into the
v1::send::MsgraphNoResponse unit marker. The two $value
endpoints (raw message MIME and raw attachment content) return bytes
rather than JSON: they run the HTTP send directly and yield the
response body.
§Naming
Public items follow <Domain><Target><Verb><Ext>: the domain is
Msgraph, the target-verb pair mirrors the REST operation
(MsgraphMailFolderCreate for creating a mail folder,
MsgraphContactsList for listing contacts) and the extension
distinguishes companions (Params, Response, Error, Yield).
Pure data resources omit the verb (MsgraphMessage,
MsgraphContact); the target is omitted when the verb applies to
the whole exchange (v1::send::MsgraphSend).
§Module layout
v1::rest mirrors the Graph reference. The mail and contacts
surfaces hang off the users resource, so they live under
v1::rest::users, with each sub-resource a directory and each
operation a file named after it: mail_folders (list, get, create,
update, delete, move, copy, child_folders), messages (list, get,
get_raw, create, create_mime, update, delete, move, copy, send,
attachments), contact_folders (list, get, create, update, delete,
child_folders), contacts (list, get, create, update, delete, delta)
and the sendMail action (JSON and MIME form). A reader who knows the
reference knows where to look.
Domain types mirror the Graph schema. Full-resource bodies double as
create and update bodies thanks to skip_serializing_if on every
optional field; contact fields use the v1::MsgraphField
tri-state to distinguish a field left out of a PATCH body from one
explicitly cleared. List operations take borrowed *Params structs
whose fields rename to the OData system query options ($top,
$select, $filter among others), flattened into query pairs by
v1::query::to_query_pairs, a tiny no_std serde serializer.
§Authentication
io-msgraph does no OAuth itself: the Graph API only accepts OAuth
2.0 bearer tokens, so the credential is exactly a bare access token,
and minting or refreshing it is the caller’s responsibility. The
base URL is fixed (v1::send::MSGRAPH_API_BASE); the mailbox
owner is addressed by v1::send::user_path, which yields me for
the authenticated user or users/{id} for an explicit user id or
principal name (Graph rejects users/me).
§Logging
Coroutines pair a debug! lifecycle line with one trace! per
input variable in new(), and a debug! plus trace!("out: ...")
when resume completes; the crate never logs above debug!.
§Example
Running a coroutine against a caller-owned TLS stream:
use std::{
io::{Read, Write},
net::TcpStream,
sync::Arc,
};
use io_http::rfc6750::bearer::HttpAuthBearer;
use io_msgraph::{coroutine::*, v1::rest::users::get::MsgraphUserGet};
use rustls::{ClientConfig, ClientConnection, StreamOwned};
use rustls_platform_verifier::ConfigVerifierExt;
let config = ClientConfig::with_platform_verifier().unwrap();
let server_name = "graph.microsoft.com".try_into().unwrap();
let conn = ClientConnection::new(Arc::new(config), server_name).unwrap();
let tcp = TcpStream::connect(("graph.microsoft.com", 443)).unwrap();
let mut stream = StreamOwned::new(conn, tcp);
let auth = HttpAuthBearer::new("token");
let mut coroutine = MsgraphUserGet::new(&auth, "me").unwrap();
let mut arg: Option<&[u8]> = None;
let mut buf = [0u8; 8192];
let mut read = Vec::new();
let out = loop {
match coroutine.resume(arg.take()) {
MsgraphCoroutineState::Complete(Ok(out)) => break out,
MsgraphCoroutineState::Complete(Err(err)) => panic!("{err}"),
MsgraphCoroutineState::Yielded(MsgraphYield::WantsRead) => {
let n = stream.read(&mut buf).unwrap();
read.clear();
read.extend_from_slice(&buf[..n]);
arg = Some(&read);
}
MsgraphCoroutineState::Yielded(MsgraphYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).unwrap();
}
}
};
println!("user principal name: {:?}", out.response.user_principal_name);Modules§
- coroutine
- Coroutine contract: the
MsgraphCoroutinetrait, itsMsgraphYield/MsgraphCoroutineState, and themsgraph_try!macro (the coroutine equivalent of?). - v1
- Microsoft Graph API v1.0.
Macros§
- msgraph_
try - Coroutine equivalent of
?: forwards aYieldedstate and short-circuits aComplete(Err(_)), unwrapping aComplete(Ok(_)).