Installation and basic usage can be found below and there are extensive examples in the example's directory included in the project on GitHub.
toml
graph-rs-sdk = "1.0.1"
tokio = { version = "1.25.0", features = ["full"] }
For using types that implement serde Serialize
as request bodies or passing serde's json macro:
toml
serde = { version = "1", features = ["derive"] }
serde_json = "1"
To use stream features add futures crate:
toml
futures = "0.3"
And import futures::StreamExt
when using Streaming features.
rust
use futures::StreamExt;
use graph_rs_sdk::*;
For bug reports please file an issue on GitHub and a response or fix will be given as soon as possible.
The Discussions tab on GitHub is enabled so feel free to stop by there with any questions or feature requests as well. For bugs, please file an issue first. Features can be requested through issues or discussions. Either way works. Other than that feel free to ask questions, provide tips to others, and talk about the project in general.
The APIs available are generated from OpenApi configs that are stored in Microsoft's msgraph-metadata repository for the Graph Api. There may be some requests and/or APIs not yet included in this project that are in the OpenApi config but in general most of them are implemented.
For extensive examples see the examples directory on GitHub
The crate provides an OAuth client that can be used to get access and refresh tokens using various OAuth flows such as auth code grant, client credentials, and open id connect.
The following is an auth code grant example. For more extensive examples and explanations see the OAuth Examples in the examples/oauth directory on GitHub.
```rust
/// # Example
///
/// use graph_rs_sdk::*:
///
/// #[tokio::main]
/// async fn main() {
/// start_server_main().await;
/// }
///
extern crate serde;
use graphrssdk::oauth::OAuth; use warp::Filter;
static CLIENTID: &str = "
pub struct AccessCode { code: String, }
fn oauthclient() -> OAuth { let mut oauth = OAuth::new(); oauth .clientid(CLIENTID) .clientsecret(CLIENTSECRET) .addscope("files.read") .addscope("files.readwrite") .addscope("files.read.all") .addscope("files.readwrite.all") .addscope("offlineaccess") .redirecturi("http://localhost:8000/redirect") .authorizeurl("https://login.microsoftonline.com/common/oauth2/v2.0/authorize") .accesstokenurl("https://login.microsoftonline.com/common/oauth2/v2.0/token") .refreshtokenurl("https://login.microsoftonline.com/common/oauth2/v2.0/token") .responsetype("code"); oauth }
pub async fn setandreqaccesscode(accesscode: AccessCode) { let mut oauth = oauthclient(); // The response type is automatically set to token and the grant type is automatically // set to authorizationcode if either of these were not previously set. oauth.accesscode(accesscode.code.asstr()); let mut request = oauth.buildasync().authorizationcode_grant();
let access_token = request.access_token().send().await.unwrap();
oauth.access_token(access_token);
// If all went well here we can print out the OAuth config with the Access Token.
println!("{:#?}", &oauth);
}
async fn handleredirect(
codeoption: Option
// Set the access code and request an access token.
// Callers should handle the Result from requesting an access token
// in case of an error here.
set_and_req_access_code(access_code).await;
// Generic login page response.
Ok(Box::new(
"Successfully Logged In! You can close your browser.",
))
}
None => Err(warp::reject()),
}
}
/// # Example
///
/// use graph_rs_sdk::*:
///
/// #[tokio::main]
/// async fn main() {
/// start_server_main().await;
/// }
///
pub async fn startservermain() {
let query = warp::query::
let routes = warp::get()
.and(warp::path("redirect"))
.and(query)
.and_then(handle_redirect);
let mut oauth = oauth_client();
let mut request = oauth.build_async().authorization_code_grant();
request.browser_authorization().open().unwrap();
warp::serve(routes).run(([127, 0, 0, 1], 8000)).await;
} ```
The crate can do both an async and blocking requests.
graph-rs-sdk = "1.0.1"
tokio = { version = "1.25.0", features = ["full"] }
```rust use graphrssdk::*;
async fn main() -> GraphResult<()> { let client = Graph::new("ACCESS_TOKEN");
let response = client .users() .list_user() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
To use the blocking client use the into_blocking()
method. You should not
use tokio
when using the blocking client.
graph-rs-sdk = "1.0.1"
use graphrssdk::*;
```rust fn main() -> GraphResult<()> { let client = Graph::new("ACCESS_TOKEN");
let response = client
.users()
.list_user()
.into_blocking()
.send()?;
println!("{:#?}", response);
let body: serde_json::Value = response.json()?;
println!("{:#?}", body);
Ok(())
} ```
native-tls
: Use the native-tls
TLS backend (OpenSSL on *nix, SChannel on Windows, Secure Transport on macOS). rustls-tls
: Use the rustls-tls
TLS backend (cross-platform backend, only supports TLS 1.2 and 1.3).brotli
: Enables reqwest feature brotli. For more info see the reqwest crate.defalte
: Enables reqwest feature deflate. For more info see the reqwest crate.trust-dns
: Enables reqwest feature trust-dns. For more info see the reqwest crate.Default features: default=["native-tls"]
The send() method is the main method for sending a request and returns a Result<rewest::Response, GraphFailure>
. See the
reqwest crate for information on the Response type.
```rust use graphrssdk::*;
pub async fn getdriveitem() -> GraphResult<()> { let client = Graph::new("ACCESS_TOKEN");
let response = client .me() .drive() .get_drive() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
You can pass types your own types to API requests that require a request body by implementing serde::Serialize
.
You can implement your own types by utilizing methods from reqwest::Response. These types must implement serde::Deserialize
.
See the reqwest crate for more info.
```rust
extern crate serde;
use graphrssdk::*;
pub struct DriveItem {
#[serde(skipserializingif = "Option::isnone")]
id: Option
static ACCESSTOKEN: &str = "ACCESSTOKEN";
static ITEMID: &str = "ITEMID";
pub async fn getdriveitem() -> GraphResult<()> { let client = Graph::new(ACCESS_TOKEN);
let drive_item = DriveItem { id: None, name: Some("new name".into()) };
let response = client .me() .drive() .item(ITEMID) .updateitems(&drive_item) .send() .await?;
println!("{:#?}", response);
let driveitem: DriveItem = response.json().await?; println!("{:#?}", driveitem);
let response = client .me() .drive() .item(ITEMID) .getitems() .send() .await?;
println!("{:#?}", response);
let driveitem: DriveItem = response.json().await?; println!("{:#?}", driveitem);
Ok(()) } ```
The Graph API will limit the number of returned items per page even if you specify a very large .top()
value and will
provide a next_link
link for you to retrieve the next batch. You can use the .paging()
method to access several
different ways to get/call next link requests.
If you just want a quick and easy way to get all next link responses or the JSON bodies you can use the paging().json()
method which will exhaust all
next link calls and return all the responses in a VecDeque<Response<T>>
. Keep in mind that the larger the volume of next link calls that need to be
made the longer the return delay will be when calling this method.
All paging methods have their response body read in order to get the @odata.nextLink
URL for calling next link requests. Because of this
the original reqwest::Response
is lost. However, the paging responses are re-wrapped in a Response object (http::Response
) that is
similar to reqwest::Response
. The main difference is that the paging calls must specify the type of response body in order to be
called and the response that is returned can provide a reference to the body response.body()
or you can take ownership of the body
which will drop the response using response.into_body()
whereas with reqwest::Response
you don't have to specify the type of body
before getting the response.
There are different levels of support for paging Microsoft Graph APIs. See the documentation, Paging Microsoft Graph data in your app, for more info on supported APIs and availability.
```rust
extern crate serde;
use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN";
pub struct User {
pub(crate) id: Option
pub struct Users {
pub value: Vec
async fn paging() -> GraphResult<()> { let client = Graph::new(ACCESS_TOKEN);
let deque = client
.users()
.list_user()
.select(&["id", "userPrincipalName"])
.paging()
.json::
println!("{:#?}", deque);
Ok(()) } ```
The paging example shows a simple way to list users and call all next links. You can also stream the next link responses or use a channel receiver to get the responses.
Streaming is only available using the async client.
```rust use futures::StreamExt; use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN";
pub async fn streamnextlinks() -> GraphResult<()> { let client = Graph::new(ACCESS_TOKEN);
let mut stream = client
.users()
.list_user()
.select(&["id", "userPrincipalName"])
.paging()
.stream::<serde_json::Value>()?;
while let Some(result) = stream.next().await {
let response = result?;
println!("{:#?}", response);
let body = response.into_body();
println!("{:#?}", body);
}
Ok(())
} ```
```rust use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN";
async fn channelnextlinks() -> GraphResult<()> {
let client = Graph::new(ACCESSTOKEN);
let mut receiver = client
.users()
.listuser()
.paging()
.channel::
while let Some(result) = receiver.recv().await { match result { Ok(response) => { println!("{:#?}", response);
let body = response.into_body();
println!("{:#?}", body);
}
Err(err) => panic!("{:#?}", err),
}
}
Ok(()) }
```
The following shows a few examples of how to use the client and a few of the APIs.
Make requests to drive using a drive id or through specific drives for me, sites, users, and groups.
```rust use graphrssdk::*;
async fn drives() -> GraphResult<()> { let client = Graph::new("ACCESS_TOKEN");
let response = client .drives() .list_drive() .send() .await .unwrap();
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
let response = client .drive("DRIVE-ID") .item("ITEMID") .getitems() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
```rust async fn driveme() -> GraphResult<()> { let client = Graph::new("ACCESSTOKEN");
let response = client .me() .drive() .item("ITEMID") .getitems() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) }
```
```RUST async fn driveusers() -> GraphResult<()> { let client = Graph::new("ACCESSTOKEN");
let response = client .user("USERID") .drive() .item("ITEMID") .get_items() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
```RUST async fn driveusers() -> GraphResult<()> { let client = Graph::new("ACCESSTOKEN");
let response = client .site("SITEID") .drive() .item("ITEMID") .get_items() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
Create a folder.
```rust use graphrssdk::*; use std::collections::HashMap;
static ACCESSTOKEN: &str = "ACCESSTOKEN"; static FOLDERNAME: &str = "NEWFOLDERNAME"; static PARENTID: &str = "PARENT_ID";
// For more info on creating a folder see: // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitempostchildren?view=odsp-graph-online
pub async fn createnewfolder() -> GraphResult<()> {
let client = Graph::new(ACCESS_TOKEN);
let folder: HashMap
let response = client .me() .drive() .item(PARENTID) .createchildren(&serdejson::json!({ "name": FOLDERNAME, "folder": folder, "@microsoft.graph.conflictBehavior": "fail" })) .send() .await?;
println!("{:#?}", response);
Ok(()) }
```
Path based addressing for drive.
```rust // Pass the path location of the item staring from the OneDrive root folder. // Start the path with :/ and end with :
async fn getitembypath() -> GraphResult<()> { let client = Graph::new("ACCESSTOKEN");
let response = client .me() .drive() .itembypath(":/documents/document.docx:") .get_items() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
```rust use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN";
async fn getmailfolder() -> GraphResult<()> { let client = Graph::new(ACCESS_TOKEN);
let response = client.me() .mailfolder(MAILFOLDERID) .getmail_folders() .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await.unwrap(); println!("{:#?}", body);
Ok(()) } ```
```rust use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN"; static MAILFOLDERID: &str = "MAILFOLDERID";
async fn createmessage() -> GraphResult<()> { let client = Graph::new(ACCESSTOKEN);
let response = client .me() .messages() .createmessages(&serdejson::json!({ "subject":"Did you see last night's game?", "importance":"Low", "body":{ "contentType":"HTML", "content":"They were awesome!" }, "toRecipients":[ { "emailAddress":{ "address":"miriamg@sreeise.onmicrosoft.com" } } ] })) .send() .await?;
println!("{:#?}", response);
Ok(()) }
```
```rust use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN";
async fn sendmail() -> GraphResult<()> { let client = Graph::new(ACCESSTOKEN);
let response = client
.me()
.send_mail(&serde_json::json!({
"message": {
"subject": "Meet for lunch?",
"body": {
"contentType": "Text",
"content": "The new cafeteria is open."
},
"toRecipients": [
{
"emailAddress": {
"address": "fannyd@contoso.onmicrosoft.com"
}
}
],
"ccRecipients": [
{
"emailAddress": {
"address": "danas@contoso.onmicrosoft.com"
}
}
]
},
"saveToSentItems": "false"
}))
.send()
.await?;
println!("{:#?}", response);
Ok(())
}
```
```rust use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN"; static MAILFOLDERID: &str = "MAILFOLDERID";
async fn createmailfoldermessage() -> GraphResult<()> { let client = Graph::new(ACCESSTOKEN);
let response = client
.me()
.mail_folder(MAIL_FOLDER_ID)
.messages()
.create_messages(&serde_json::json!({
"subject":"Did you see last night's game?",
"importance":"Low",
"body": {
"contentType":"HTML",
"content":"They were <b>awesome</b>!"
},
"toRecipients":[{
"emailAddress":{
"address":"miriamg@sreeise.onmicrosoft.com"
}
}
]
}))
.send()
.await?;
println!("{:#?}", response);
Ok(())
} ```
```rust use graphrssdk::*;
static ACCESSTOKEN: &str = "ACCESSTOKEN"; static USERID: &str = "USERID";
async fn getuserinboxmessages() -> GraphResult<()> { let client = Graph::new(ACCESSTOKEN);
let response = client .user(USERID) .mailfolder("Inbox") .messages() .list_messages() .top("2") .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
Use your own struct. Anything that implements serde::Serialize can be used for things like creating messages for mail or creating a folder for OneDrive.
```rust
extern crate serde;
use graphrssdk::*;
struct Message {
subject: String,
importance: String,
body: HashMap
struct ToRecipient { #[serde(rename = "emailAddress")] email_address: EmailAddress, }
struct EmailAddress { address: String, }
async fn createmessage() -> GraphResult<()> { let client = Graph::new("ACCESSTOKEN");
let mut body: HashMap
let message = Message { subject: "Did you see last night's game?".into(), importance: "Low".into(), body, torecipients: vec![ ToRecipient { emailaddress: EmailAddress { address : "AdeleV@contoso.onmicrosoft.com".into() } } ] };
let response = client .me() .messages() .create_messages(&message) .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
```rust use graphrssdk::*;
async fn createmessage() -> GraphResult<()> { let client = Graph::new("ACCESSTOKEN");
// Get all files in the root of the drive // and select only specific properties. let response = client .me() .drive() .get_drive() .select(&["id", "name"]) .send() .await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
Batch requests use a mpsc::channel and return the receiver for responses.
```rust use graphrssdk::*;
static USERID: &str = "USERID"; static ACCESSTOKEN: &str = "ACCESSTOKEN";
async fn batch() -> GraphResult<()> { let client = Graph::new(ACCESS_TOKEN);
let json = serdejson::json!({ "requests": [ { "id": "1", "method": "GET", "url": format!("/users/{USERID}/drive") }, { "id": "2", "method": "GET", "url": format!("/users/{USERID}/drive/root") }, { "id": "3", "method": "GET", "url": format!("/users/{USERID}/drive/recent") }, { "id": "4", "method": "GET", "url": format!("/users/{USERID}/drive/root/children") }, { "id": "5", "method": "GET", "url": format!("/users/{USERID}/drive/special/documents") }, ] });
let response = client.batch(&json).send().await?;
let body: serde_json::Value = response.json().await?; println!("{:#?}", body);
Ok(()) } ```
user("user-id")
vs users()
)Many of the available APIs have methods that do not require an id for a resource as well as many of the APIs have methods that do require an id. For most all of these resources the methods are implemented in this sdk by using two different naming schemes and letting the user go directly to the methods they want.
As en example, the users API can list users without an id, and you can find list_users()
by calling the users()
method whereas getting a specific user requires a users id
and you can find get_users()
by calling user<ID: AsRef<str>>(id: ID)
method.
users()
method:```rust use graphrssdk::*;
// For more info on users see: https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0 // For more examples see the examples directory on GitHub.
static ACCESSTOKEN: &str = "ACCESSTOKEN";
async fn listusers() -> GraphResult<()> { let client = Graph::new(ACCESSTOKEN);
let response = client .users() .list_user() .send() .await .unwrap();
println!("{:#?}", response);
let body: serde_json::Value = response.json().await.unwrap(); println!("{:#?}", body);
Ok(()) } ```
user<ID: AsRef<str>>(id: ID)
method:```rust use graphrssdk::*;
// For more info on users see: https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0 // For more examples see the examples directory on GitHub
static ACCESSTOKEN: &str = "ACCESSTOKEN"; static USERID: &str = "USERID";
async fn getuser() -> GraphResult<()> { let client = Graph::new(ACCESSTOKEN);
let response = client
.user(USER_ID)
.get_user()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
} ```
Normal Rust build using cargo.
$ cargo build
Of the portions that are implemented there are also examples and docs. Run:
$ cargo doc --no-deps --open
There are several parts to this project:
The project does validation testing for the Graph Api's using a developer sandbox to ensure the implementation provided here works correctly. However, the total amount of individual requests that can be called and that is provided in this project is well into the hundreds, and some areas are lacking in coverage. The goal is to cover the main parts of each Api.
Tests are run on Ubuntu Linux and Windows 10 instances.