Client

pub struct Client<'url, A: Authenticate> {
    pub url: Cow<'url, str>,
    /* private fields */
}
Expand description

A client capable of connecting to a dataverse environment

A client should be created once and then reused to take advantage of its connection-pooling.

§Examples

use powerplatform_dataverse_service_client::client::Client;

let client_id = "<clientid>";
let client_secret ="<clientsecret>";

let client = Client::with_client_secret_auth(
    "https://instance.crm.dynamics.com/",
    "12345678-1234-1234-1234-123456789012",
    client_id,
    client_secret,
);

Fields§

§url: Cow<'url, str>

Implementations§

Source§

impl<'url> Client<'url, ClientSecretAuth>

Source

pub fn with_client_secret_auth( url: impl Into<Cow<'url, str>>, tenant_id: &str, client_id: impl Into<String>, client_secret: impl Into<String>, ) -> Self

Creates a dataverse client that uses client/secret authentication

Please note that this function will not fail right away even when the provided credentials are invalid. This is because the authentication is handled lazily and a token is only acquired on the first call or when an acquired token is no longer valid and needs to be refreshed

§Examples
use powerplatform_dataverse_service_client::client::Client;

let client_id = "<clientid>";
let client_secret = "<clientsecret>";

let client = Client::with_client_secret_auth(
    "https://instance.crm.dynamics.com/",
    "12345678-1234-1234-1234-123456789012",
    client_id,
    client_secret,
);
Source§

impl<'url> Client<'url, NoAuth>

Source

pub fn new_dummy() -> Self

Creates a dummy Client that will return errors every time its functions are used

This is only really useful in unit-testing and doc-testing scenarios where you want to prevent a bunch of erronous auth-calls each time a test is run

Source§

impl<'url, A: Authenticate> Client<'url, A>

Source

pub fn new(url: impl Into<Cow<'url, str>>, backend: Client, auth: A) -> Self

Creates a dataverse client with a custom authentication handler and backend

This function may not panic so the custom authentication should follow these rules:

  • tokens should be acquired lazily
  • tokens should be cached and reused where possible
  • each call to the get_valid_token() function should give a token that is valid for at least the next 2 minutes
§Examples
use core::time::Duration;
use powerplatform_dataverse_service_client::auth::client_secret::ClientSecretAuth;
use powerplatform_dataverse_service_client::client::Client;
use powerplatform_dataverse_service_client::result::{IntoDataverseResult, Result};

let tenant_id = "12345678-1234-1234-1234-123456789012";
let client_id = String::from("<some client id>");
let client_secret = String::from("<some client secret>");
let url = "https://instance.crm.dynamics.crm/";

let client = reqwest::Client::builder()
    .https_only(true)
    .connect_timeout(Duration::from_secs(120))
    .timeout(Duration::from_secs(120))
    .build().into_dataverse_result()?;

let auth = ClientSecretAuth::new(
    client.clone(),
    format!(
        "https://login.microsoftonline.com/{}/oauth2/v2.0/token",
        tenant_id
    ),
    format!("{}.default", url),
    client_id,
    client_secret,
);

let client = Client::new(url, client, auth);
Source

pub async fn create(&self, entity: &impl WriteEntity) -> Result<Uuid>

Writes the given entity into the current dataverse instance and returns its generated Uuid

This may fail for any of these reasons

  • An authentication failure
  • A serde serialization error
  • Any http client or server error
  • there is already a record with this Uuid in the table
§Examples
use uuid::Uuid;
use serde::Serialize;
use powerplatform_dataverse_service_client::client::Client;
use powerplatform_dataverse_service_client::entity::WriteEntity;
use powerplatform_dataverse_service_client::reference::{Reference, ReferenceStruct};
use powerplatform_dataverse_service_client::result::{IntoDataverseResult, Result};

async fn test() -> Result<Uuid> {
    let contact = Contact {
        contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
        firstname: String::from("Testy"),
        lastname: String::from("McTestface"),
    };

    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    client.create(&contact).await
}

#[derive(Serialize)]
struct Contact {
    contactid: Uuid,
    firstname: String,
    lastname: String,
}

impl WriteEntity for Contact {}

impl Reference for Contact {
    fn get_reference(&self) -> ReferenceStruct {
        ReferenceStruct::new(
            "contacts",
            self.contactid,
        )
    }
}
Source

pub async fn update(&self, entity: &impl WriteEntity) -> Result<()>

Updates the attributes of the gven entity in the current dataverse instance

Please note that only those attributes are updated that are present in the serialization payload. Other attributes are untouched

This may fail for any of these reasons

  • An authentication failure
  • A serde serialization error
  • Any http client or server error
  • there is no record with this Uuid in the table
§Examples
use uuid::Uuid;
use serde::Serialize;
use powerplatform_dataverse_service_client::client::Client;
use powerplatform_dataverse_service_client::entity::WriteEntity;
use powerplatform_dataverse_service_client::reference::{Reference, ReferenceStruct};
use powerplatform_dataverse_service_client::result::{IntoDataverseResult, Result};

async fn test() -> Result<()> {
    let contact = Contact {
        contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
        firstname: String::from("Testy"),
        lastname: String::from("McTestface"),
    };

    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    client.update(&contact).await
}

#[derive(Serialize)]
struct Contact {
    contactid: Uuid,
    firstname: String,
    lastname: String,
}

impl WriteEntity for Contact {}

impl Reference for Contact {
    fn get_reference(&self) -> ReferenceStruct {
        ReferenceStruct::new(
            "contacts",
            self.contactid,
        )
    }
}
Source

pub async fn upsert(&self, entity: &impl WriteEntity) -> Result<()>

Updates or creates the given entity in the current dataverse instance

Please note that only those attributes are updated that are present in the serialization payload. Other attributes are untouched

This may fail for any of these reasons

  • An authentication failure
  • A serde serialization error
  • Any http client or server error
§Examples
use uuid::Uuid;
use serde::Serialize;
use powerplatform_dataverse_service_client::client::Client;
use powerplatform_dataverse_service_client::entity::WriteEntity;
use powerplatform_dataverse_service_client::reference::{Reference, ReferenceStruct};
use powerplatform_dataverse_service_client::result::{IntoDataverseResult, Result};

async fn test() -> Result<()> {
    let contact = Contact {
        contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
        firstname: String::from("Testy"),
        lastname: String::from("McTestface"),
    };

    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    client.upsert(&contact).await
}

#[derive(Serialize)]
struct Contact {
    contactid: Uuid,
    firstname: String,
    lastname: String,
}

impl WriteEntity for Contact {}

impl Reference for Contact {
    fn get_reference(&self) -> ReferenceStruct {
        ReferenceStruct::new(
            "contacts",
            self.contactid,
        )
    }
}
Source

pub async fn delete(&self, reference: &impl Reference) -> Result<()>

Deletes the entity record this reference points to

Please note that each structs that implements WriteEntity also implements Reference so you can use it as input here, but there is a sensible default implementation called ReferenceStruct for those cases where you only have access to the raw reference data

This may fail for any of these reasons

  • An authentication failure
  • Any http client or server error
  • The referenced entity record doesn’t exist
§Examples
use uuid::Uuid;
use powerplatform_dataverse_service_client::client::Client;
use powerplatform_dataverse_service_client::reference::ReferenceStruct;
use powerplatform_dataverse_service_client::result::{IntoDataverseResult, Result};

let reference = ReferenceStruct::new(
    "contacts",
    Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?
);

let client = Client::new_dummy(); // Please replace this with your preferred authentication method
client.delete(&reference).await?;
Source

pub async fn retrieve<E: ReadEntity>( &self, reference: &impl Reference, ) -> Result<E>

retrieves the entity record that the reference points to from dataverse

This function uses the implementation of the Select trait to only retrieve those attributes relevant to the struct defined. It is an Anti-Pattern to retrieve all attributes when they are not needed, so this library does not give the option to do that

This may fail for any of these reasons

  • An authentication failure
  • A serde deserialization error
  • Any http client or server error
  • The entity record referenced doesn’t exist
§Examples
use serde::Deserialize;
use uuid::Uuid;
use powerplatform_dataverse_service_client::{
    client::Client,
    entity::ReadEntity,
    reference::ReferenceStruct,
    result::{IntoDataverseResult, Result},
    select::Select
};
    
async fn test() -> Result<()> {
    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    let contact: Contact = client
        .retrieve(
            &ReferenceStruct::new(
                "contacts",
                Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?
            )
        )
        .await?;
    Ok(())
}

#[derive(Deserialize)]
struct Contact {
    contactid: Uuid,
    firstname: String,
    lastname: String,
}

impl ReadEntity for Contact {}

impl Select for Contact {
    fn get_columns() -> &'static [&'static str] {
        &["contactid", "firstname", "lastname"]
    }
}
Source

pub async fn retrieve_multiple<E: ReadEntity>( &self, query: &Query, ) -> Result<Page<E>>

Executes the query and retrieves the entities from dataverse

This function uses the implementation of the Select trait to only retrieve those attributes relevant to the struct defined. It is an Anti-Pattern to retrieve all attributes when they are not needed, so this library does not give the option to do that

Please note that if you don’t specify a limit then the client will try to retrieve up to 5000 records. Further records can then be retrieved with the retrieve_next_page() function

This may fail for any of these reasons

  • An authentication failure
  • A serde deserialization error
  • Any http client or server error
§Examples
use uuid::Uuid;
use serde::Deserialize;
use powerplatform_dataverse_service_client::{
    client::{Client, Page},
    entity::ReadEntity,
    reference::ReferenceStruct,
    result::{IntoDataverseResult, Result},
    select::Select,
    query::Query
};

async fn test() -> Result<()> {
    // this query retrieves the first 3 contacts
    let query = Query::new("contacts").limit(3);
    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    let contacts: Page<Contact> = client.retrieve_multiple(&query).await?;
    Ok(())
}

#[derive(Deserialize)]
struct Contact {
    contactid: Uuid,
    firstname: String,
    lastname: String,
}

impl ReadEntity for Contact {}

impl Select for Contact {
    fn get_columns() -> &'static [&'static str] {
        &["contactid", "firstname", "lastname"]
    }
}
Source

pub async fn retrieve_next_page<E: ReadEntity>( &self, previous_page: &Page<E>, ) -> Result<Page<E>>

Continues a previous query by fetching the next records after a Page

You can check with is_incomplete() if there are further records available to a query

This may fail for any of these reasons

  • An authentication failure
  • A serde deserialization error
  • Any http client or server error
  • The query already finished with the last page
§Examples
use uuid::Uuid;
use serde::Deserialize;
use powerplatform_dataverse_service_client::{
    client::{Client, Page},
    entity::ReadEntity,
    reference::ReferenceStruct,
    result::{IntoDataverseResult, Result},
    select::Select,
    query::Query
};

async fn test() -> Result<()> {
    let query = Query::new("contacts");
    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    let contact_page1: Page<Contact> = client.retrieve_multiple(&query).await?;

    if contact_page1.is_incomplete() {
        let contact_page2 = client.retrieve_next_page(&contact_page1).await?;
    }

    Ok(())
}

#[derive(Deserialize)]
struct Contact {
    contactid: Uuid,
    firstname: String,
    lastname: String,
}

impl ReadEntity for Contact {}

impl Select for Contact {
    fn get_columns() -> &'static [&'static str] {
        &["contactid", "firstname", "lastname"]
    }
}
Source

pub async fn execute(&self, batch: &Batch) -> Result<()>

executes the batch against the dataverse environment

This function will fail if:

  • the batch size exceeds 1000 calls
  • the batch execution time exceeds 2 minutes

the second restriction is especially tricky to handle because the execution time depends on the complexity of the entity in dataverse. So it is possible to create 300 records of an entity with low complexity but only 50 records of an entity with high complexity in that timeframe.

Based on experience a batch size of 50 should be safe for all entities though

§Examples
use uuid::Uuid;
use serde::Serialize;
use powerplatform_dataverse_service_client::{
    batch::Batch,
    client::Client,
    entity::WriteEntity,
    reference::{Reference, ReferenceStruct},
    result::{IntoDataverseResult, Result}
};

async fn test() -> Result<()> {
    let testy_contact = Contact {
        contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
        firstname: String::from("Testy"),
        lastname: String::from("McTestface"),
    };

    let marianne_contact = Contact {
        contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789abc").into_dataverse_result()?,
        firstname: String::from("Marianne"),
        lastname: String::from("McTestface"),
    };

    // this batch creates both contacts in one call
    let mut batch = Batch::new("https://instance.crm.dynamics.com/");
    batch.create(&testy_contact)?;
    batch.create(&marianne_contact)?;
    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    client.execute(&batch).await?;
    Ok(())
}

#[derive(Serialize)]
struct Contact {
    contactid: Uuid,
    firstname: String,
    lastname: String,
}

impl WriteEntity for Contact {}

impl Reference for Contact {
    fn get_reference(&self) -> ReferenceStruct {
        ReferenceStruct::new(
            "contacts",
            self.contactid,
        )
    }
}
Source

pub async fn merge( &self, entity_name: impl Display, target: Uuid, subordinate: Uuid, ) -> Result<()>

Tries to merge two entities with and deactivates the subordinate after the process

This method is only supported for the following entities:

  • account
  • contact
  • lead
  • incident
§Examples
use uuid::Uuid;
use powerplatform_dataverse_service_client::client::Client;
use powerplatform_dataverse_service_client::result::{IntoDataverseResult, Result};

async fn test() -> Result<()> {
    let target_id = Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?;
    let subordinate_id = Uuid::parse_str("12345687-1234-1234-1234-123456879012").into_dataverse_result()?;

    let client = Client::new_dummy(); // Please replace this with your preferred authentication method
    client.merge("account", target_id, subordinate_id).await
}

Auto Trait Implementations§

§

impl<'url, A> Freeze for Client<'url, A>
where A: Freeze,

§

impl<'url, A> !RefUnwindSafe for Client<'url, A>

§

impl<'url, A> Send for Client<'url, A>
where A: Send,

§

impl<'url, A> Sync for Client<'url, A>
where A: Sync,

§

impl<'url, A> Unpin for Client<'url, A>
where A: Unpin,

§

impl<'url, A> !UnwindSafe for Client<'url, A>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more