1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! # 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.
//!
//! [Microsoft Graph API]: https://learn.microsoft.com/en-us/graph/api/overview
//! [io-http]: https://docs.rs/io-http
//!
//! 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.
//!
//! [io-gmail]: https://docs.rs/io-gmail
//!
//! ## 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,
//! delta, 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::field::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:
//!
//! ```rust,no_run
//! 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);
//! ```
extern crate alloc;
extern crate std;