Now available on stable Rust at crates

0 and above use stable Rust

graph-rs

Now available on stable Rust at crates.io

graph-rs-sdk = "0.1.0"

0.1.. Anything before 0.1.0 uses nightly Rust.

Microsoft Graph API Client in Rust

Installation and basic usage can be found below and there are extensive examples in the example's directory included in the project on GitHub.

What Api's are available

The Api's 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 Api's not yet included in this project that are in the OpenApi config but in general most of them are implemented.

Feature requests or Bug reports.

For both feature requests and bug reports please file an issue on GitHub and a response or fix will be given as soon as possible.

Use

The client supports both blocking and async requests.

Blocking Client

To use the blocking client

use graph_rs_sdk::prelude::*;

fn main() {
  let client =  Graph::new("ACCESS_TOKEN");
}

Async Client

To use the async client

use graph_rs_sdk::prelude::*;

fn main() {
  let client = Graph::new_async("ACCESS_TOKEN");
}

The send method and Graph types

The send() method is the main method for sending a request. The return value will be wrapped in a response object, GraphResponse<T> and the body will be a serde_json::Value. If the response is a 204 no content and there is no body then the response body returned will just be a serde_json::Value with an empty string.

use graph_rs_sdk::prelude::*;

let client =  Graph::new("ACCESS_TOKEN");

// Returns GraphResponse<serde_json::Value>
let response = client.v1()
    .me()
    .drive()
    .get_drive()
    .send()
    .unwrap();

For async requests use the await keyword.

use graph_rs_sdk::prelude::*;

let client =  Graph::new_async("ACCESS_TOKEN");

// Returns GraphResponse<serde_json::Value>
let response = client.v1()
    .me()
    .drive()
    .get_drive()
    .send()
    .await
    .unwrap();
        
println!("{:#?}", response);  

// Get the body of the response
println!("{:#?}", response.body());
Custom Types

The json() method can be used to convert the response body to your own types. These types must implement serde::Deserialize.

use graph_rs_sdk::prelude::*;
        
let client = Graph::new("ACCESS_TOKEN");
        
#[derive(Debug, Serialize, Deserialize)]
pub struct DriveItem {
    id: Option<String>,
    name: Option<String>,
    // ... Any other fields
}
        
let response: DriveItem = client.v1()
    .me()
    .drive()
    .get_items("ITEM_ID")
    .json()?;
        
println!("{:#?}", response);   

OneDrive

Make requests to drive using a drive id or through specific drives for me, sites, users, and groups.

use graph_rs_sdk::prelude::*;
    
let client = Graph::new("ACCESS_TOKEN");

// Some requests don't require an id.
let response = client.v1()
    .drives()
    .get_drive();

// Using a drive id.
let response = client.v1()
    .drive("DRIVE-ID")
    .get_items("ITEM_ID")
    .send()?;

// Using me.
let response = client.v1()
    .me()
    .drive()
    .get_items("ITEM_ID")
    .send()?;
    
println!("{:#?}", response);

// Using users.
let response = client.v1()
    .users("USER_ID")
    .drive()
    .get_items("ITEM_ID")
    .send()?;

println!("{:#?}", response);

// Using sites.
let response = client.v1()
    .sites("SITE-ID")
    .drive()
    .get_items("ITEM_ID")
    .send()?;

println!("{:#?}", response);

Create a folder.

let folder: HashMap<String, serde_json::Value> = HashMap::new();

let response = client.v1()
    .me()
    .drive()
    .create_folder(
        "PARENT_FOLDER_ID",
         &serde_json::json!({
            "name": "docs",
            "folder": folder,
            "@microsoft.graph.conflictBehavior": "fail"
         }),
    )
    .send()?;
        
println!("{:#?}", response);

Path based addressing for drive.

// Pass the path location of the item staring from the OneDrive root folder.
// Start the path with :/ and end with :
    
let response = client.v1()
    .me()
    .drive()
    .get_items(":/documents/document.docx:")
    .send()?;
        
println!("{:#?}", response.body());

Mail

use graph_rs_sdk::prelude::*;
        
let client = Graph::new("ACCESS_TOKEN");
        
// List messages for a user.
let response = client.v1()
    .user("USER-ID")
    .messages()
    .list_messages()
    .send()?;

// List messages using me.
let response = client.v1()
    .me()
    .messages()
    .list_messages()
    .send()?;
             
// Create a message
let response = client.v1()
    .user("USER_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":"[email protected]"
            }
        }]
    }))
    .send()?;
        
println!("{:#?}", response.body()); // => Message

// Send mail.
let response = client.v1()
    .user("USER-ID")
    .send_mail(&serde_json::json!({
        "message": {
            "subject": "Meet for lunch?",
            "body": {
                "contentType": "Text",
                "content": "The new cafeteria is open."
            },
            "toRecipients": [
                {
                    "emailAddress": {
                        "address": "[email protected]"
                    }
                }
            ],
            "ccRecipients": [
                {
                    "emailAddress": {
                        "address": "[email protected]"
                    }
                }
            ]
        },
        "saveToSentItems": "false"
        }))
    .send()?;
                                       
println!("{:#?}", response);

Mail folders

// Create a mail folder.
let response = client.v1()
    .user("USER-ID")
    .mail_folders()
    .create_mail_folders(&serde_json::json!({
        "displayName": "Clutter"
    }))
    .send()?;

// List messages in a mail folder.
let response = client.v1()
    .me()
    .mail_folder("drafts")
    .messages()
    .list_messages()
    .send()?;

// Create messages in a mail folder.
let response = client.v1()
    .user("USER-ID")
    .mail_folder("drafts")
    .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":"[email protected]"
            }
        }]
    }))
    .send()?;

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.

 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Message {
    subject: String,
    importance: String,
    body: HashMap<String, String>,
    #[serde(rename = "toRecipients")]
    to_recipients: Vec<ToRecipient>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct ToRecipient {
    #[serde(rename = "emailAddress")]
    email_address: EmailAddress,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
    struct EmailAddress {
        address: String,
    }

let mut body = HashMap::new();
body.insert("contentType".to_string(), "HTML".to_string());
body.insert("content".to_string(), "They were <b>awesome</b>!".to_string());
        
let message = Message {
    subject: "Did you see last night's game?".into(),
    importance: "Low".into(),
    body,
    to_recipients: vec![
        ToRecipient {
            email_address: EmailAddress {
                address : "[email protected]".into()        
            }                
        }
    ]
}
        
// Create a message
let response = client.v1()
    .me()
    .messages()
    .create_messages(&message)
    .send()?;
            
println!(":#?", response);

OData Queries

use graph_rs_sdk::prelude::*;
            
let client = Graph::new("ACCESS_TOKEN");
    
// Get all files in the root of the drive
// and select only specific properties.
let response = client.v1()
    .me()
    .drive()
    .get_drive()
    .select(&["id", "name"])
    .send()?;
    
println!("{:#?}", response.body());

Batch Requests

Batch requests use a mpsc::channel and return the receiver for responses.

use graph_rs_sdk::prelude::*;
use std::error::Error;

static USER_ID: &str = "USER_ID";

let client = Graph::new("ACCESS_TOKEN");

let json = serde_json::json!({
    "requests": [
        {
            "id": "1",
            "method": "GET",
            "url": format!("/users/{}/drive", USER_ID)
        },
        {
            "id": "2",
            "method": "GET",
            "url": format!("/users/{}/drive/root", USER_ID)
        },
        {
            "id": "3",
            "method": "GET",
            "url": format!("/users/{}/drive/recent", USER_ID)
        },
        {
            "id": "4",
            "method": "GET",
            "url": format!("/users/{}/drive/root/children", USER_ID)
        },
        {
            "id": "5",
            "method": "GET",
            "url": format!("/users/{}/drive/special/documents", USER_ID)
        }
    ]
});

let recv = client
    .v1()
    .batch(&json)
    .send();

loop {
    match recv.recv() {
        Ok(delta) => {
            match delta {
                Delta::Next(response) => {
                    println!("{:#?}", response);
                },
                Delta::Done(err) => {
                    println!("Finished");

                    // If the delta request ended in an error Delta::Done
                    // will return Some(GraphFailure)
                    if let Some(err) = err {
                        println!("Error: {:#?}", err);
                        println!("Description: {:#?}", err.description());
                    }

                    // All next links have been called.
                    // Break here. The channel has been closed.
                    break;
                },
            }
        },
        Err(e) => {
            println!("{:#?}", e.description());
            break;
        },
    }
}

For those interested in the code itself

Build

Normal Rust build using cargo.

$ cargo build

Docs

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:

  • graph-oauth: OAuth client for getting access/refresh tokens from the Graph api.
  • graph-error: Errors that come back from the Graph Api.
  • graph-codegen: OpenApi parser and generator specifically for the Graph Api's.
  • graph-core: Common types shared across all or multiple parts of the project
  • test-tools: Helps facilitate project testing.
  • graph-rs (src directory): The Graph client for interacting with the Graph Api including the Api's generated from the OpenApi config. The oauth client is also reexported from here.

Testing

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.

graph-rs versions before 12/13/2020

The graph-rs project is now published on crates.io and that is the recommended version to use. Because of the many changes that came with publishing, if you still need to migrate or would like to use the previous version then you can use the v2master branch which is still the same as the master branch before it was published as a crate.

Issues

Collection of the latest Issues

smndtrl

smndtrl

3

Hi,

is there a way to request /root/delta starting from a delta token that is acquired beforehand? As my Rust knowledge and experience is limited my assumptions may be wrong but I came up with the following ideas.

Add the delta token as parameter to the delta() function like in the official C# SDK. var delta = await graphClient.Me.Drive.Root .Delta("1230919asd190410jlka") .Request() .GetAsync(); which would lead to something similar let mut delta_recv = client.v1().drive(tok.drive_id).delta(tok.last_token).send().await;

or

Add the token query parameter to the OData query parameters like filter or select.

Option 1 would be more inline with other SDK and the separation between what is OData and what is Graph API specific functionality. But meddling with codegen/macros is not something I feel comfortable with.

Option 2 is what I was able to do to get on with my learning project but consider not wise for API design reasons.

sreeise

sreeise

codegen
0

We need a write up of how to generate code for the APIs and to make this easier to do for everyone using the project.

sreeise

sreeise

codegen
0

When a new api request client needs to be generated there are several parts that need to be updated in the graph-codegen crate. This is mostly due to the various steps that are taken when parsing such as adding custom methods not in the OpenApi config, adding configs to the parser to modify URL paths, adding configs to the parser for linking between clients, etc. A good portion of this can be changed to one configuration and added to the parser all at once. Additionally, there are parts that can be added automatically based on certain criteria found when parsing.

sreeise

sreeise

test
0

Test coverage is lacking for a large part of the Api's. Because of the amount of available requests/api's tests should at least cover the core parts of each api. These tests should be ones that make requests and verify the implementation works. An example would be the drive tests located in this file https://github.com/sreeise/graph-rs/blob/950802541cc35f19d6b582648f740e6abee9df5c/tests/drive_request.rs#L15

Tests need to be added for:

  • Activities
  • App catalogs
  • Applications
  • Attachments
  • Audit logs
  • Certificate based auth configuration
  • Communications
  • Contracts
  • Data policy operations
  • Directory
  • Domain dns records
  • Domains
  • Education
  • Identity
  • Inference classification
  • Insights
  • Invitations
  • Managed devices
  • Org contacts
  • places
  • planner
  • Policies
  • Sites
  • Subscribed skus
  • Subscriptions
  • Teams
  • Teamwork
sreeise

sreeise

oauth
0

The graph-oauth crate can perform client credentials with a shared secret. This issue is to add a way to do client credentials with a certificate. Previous ticket for this was #28

sreeise

sreeise

Mail
0

There are a good bit of mailFolder requests that are currently in master that will not be generated by codegen. These will need to be added back. The following should be filtered through to see which ones we need:

sreeise

sreeise

test
0

Add a test that will perform a request that can use async job status and ensure that the request for job status succeeds.

sreeise

sreeise

enhancement
0

Some OAuth fields require specific values for requesting authorization, access tokens, and refresh tokens. For instance, the grant type parameter, when making an access token request for an authorization code grant, must be set to authorization_code. In places where we know what this value is suppose to be for a specific grant we can perform checks for correct usage and log helpful output in the event of a failure.

sreeise

sreeise

oauth
0

There are a few places that uses the methods in OAuthError where the method is expecting a single str value which is then formatted into a message. However, some of the usages pass a whole message and the error could end up not making any sense.

For instance, in the grant_error method the string is formatted as:

and in OAuth, when the error is returned in encode_uri, the error passed is:

Some of the methods are also very similar, it would be nice to see if these can be cleaned up.

sreeise

sreeise

oauth
1

Tests needed to be added check the credentials in OAuth::encode_uri(). Also that the encoding converts only the required credentials. There should be three test methods added, one for each of the grant types. The tests should parse the query of url returned for each open id grant request in OAuth::encode_uri() and check that they are the correct credential type and value.

Versions

Find the latest versions by id

v0.2.0 - Apr 22, 2022

Highlights:

Special thanks to @DevLazio for making this one happen. :rocket:

v0.1.4 - Jan 31, 2022

graph-oauth: Adds a way to set the tenant id which will add the authorization and token URLs with the tenant id already inserted.

v0.1.3 - Jan 15, 2022

v0.1.2-fix - Jan 11, 2022

  • OpenApi modals closely aligned with OpenApi specification.
  • Reports API implemented.
  • Rewrite of how download requests work. For requests that make it possible to download a file, you can now also get this same file data in other forms such as text or bytes.

v0.1.1 - Aug 19, 2021

Changes the OData query methods to use $ as a prefix for all V1 and beta requests. Originally the http client was not including the $ prefix but an issue was found where only certain V1 endpoints support OData queries without the $ prefix. Per https://docs.microsoft.com/en-us/graph/query-parameters:

On the beta endpoint, the $ prefix is optional. For example, instead of $filter, you can use filter. On the v1 endpoint, the $ prefix is optional for only a subset of APIs. For simplicity, always include $ if using the v1 endpoint.

v0.1.0 - Jun 12, 2021

v0.0.2 - Jan 19, 2021

Bug fixes:

  • #299

0.0.1 - Dec 13, 2020

Information - Updated May 25, 2022

Stars: 32
Forks: 10
Issues: 26

Repositories & Extras

Wasm template for Rust hosting without npm-deploy on github pages using Travis script

It automatically hosts your wasm projects on gh-pages using a travis script on the latest commit

Wasm template for Rust hosting without npm-deploy on github pages using Travis script

If you like Rusty Engine, please sponsor me on GitHub or on Patreon, or take...

If you like Rusty Engine, please sponsor me on Patreon, or Bevy, which I encourage you to use directly for more serious game engine needs

If you like Rusty Engine, please sponsor me on GitHub or on Patreon, or take...

Roctogen: a rust client library for the GitHub v3 API

This client API is generated from the Isahc HTTP client

Roctogen: a rust client library for the GitHub v3 API

A rust github template for ease of use

Install the rust toolchain in order to have cargo installed by following

A rust github template for ease of use

📓 Relnotes: Automatic GitHub Release Notes

Tera templates for release notes format

📓 Relnotes: Automatic GitHub Release Notes

Rust-generated WebAssembly GitHub action template

A template to bootstrap the creation of a Rust-generated WebAssembly GitHub action

Rust-generated WebAssembly GitHub action template

Template for Rust lib/bin module with built-in GitHub Action to build and test

You will want to change the lib name and bin name in Cargo

Template for Rust lib/bin module with built-in GitHub Action to build and test

cargo_auto_github_lib

Library for cargo-auto automation tasks written in rust language with functions for github

cargo_auto_github_lib

Huber is to simplify the package management from GitHub projects with a builtin awesome list...

Huber is to simplify the package management from GitHub projects with a builtin awesome list (live updating) of popular projects

Huber is to simplify the package management from GitHub projects with a builtin awesome list...

Renote is a CLI to extend GitHub operation experience, which is a complementary tool to...

Renote is a CLI to extend GitHub operation experience, which is a complementary tool to use with gh advanced search options

Renote is a CLI to extend GitHub operation experience, which is a complementary tool to...
Facebook Instagram Twitter GitHub Dribbble
Privacy