odata_client 0.1.0

Client for accessing OData APIs
Documentation
// TODO: use mock client with prepared responses for offline tests
use std::marker::PhantomData;

use super::EntityLink;
use crate::{EntityProperties, EntitySetEndpoint, ExpandQuery, SingletonEndpoint};
use chrono::{DateTime, FixedOffset};
use iso8601::Duration;
use odata_client_util::deserialize_with::Iso8601DurationDeserialize;
use serde::Deserialize;
use serde_with::serde_as;
use uuid::Uuid;

const TRIP_PIN_SERVICE_URL: &str = "https://services.odata.org/V4/TripPinService";

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Person {
    user_name: String,
    first_name: String,
    last_name: String,
    #[serde(default)]
    emails: Vec<String>,
    #[serde(default)]
    address_info: Vec<Location>,
    gender: Option<PersonGender>,
    concurrency: i64,
    friends: Vec<EntityLink<Person>>,
    trips: Vec<Trip>,
    photo: Option<EntityLink<Photo>>,
}

impl EntityProperties for Person {
    const EXPAND_QUERY: ExpandQuery =
        ExpandQuery::Expand("Friends/$ref,Trips($expand=Photos/$ref,PlanItems),Photo/$ref");
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Location {
    address: String,
    city: City,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct City {
    country_region: String,
    name: String,
    region: String,
}

#[derive(Debug, Deserialize)]
enum PersonGender {
    Male = 0,
    Female = 1,
    Unknown = 2,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Trip {
    trip_id: i32,
    share_id: Option<Uuid>,
    description: Option<String>,
    name: String,
    budget: f32,
    starts_at: DateTime<FixedOffset>,
    ends_at: DateTime<FixedOffset>,
    tags: Vec<String>,
    photos: Vec<EntityLink<Photo>>,
    plan_items: Vec<PlanItem>,
}

impl EntityProperties for Trip {
    const EXPAND_QUERY: ExpandQuery = ExpandQuery::Expand("Photos/$ref,PlanItems");
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Photo {
    id: i64,
    name: Option<String>,
}

impl EntityProperties for Photo {
    const EXPAND_QUERY: ExpandQuery = ExpandQuery::None;
}

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct PlanItem {
    plan_item_id: i32,
    confirmation_code: Option<String>,
    starts_at: Option<DateTime<FixedOffset>>,
    ends_at: Option<DateTime<FixedOffset>>,
    #[serde_as(as = "Option<Iso8601DurationDeserialize>")]
    duration: Option<Duration>,
}

impl EntityProperties for PlanItem {
    const EXPAND_QUERY: ExpandQuery = ExpandQuery::None;
}

const PEOPLE: EntitySetEndpoint<Person> = EntitySetEndpoint {
    service_url: TRIP_PIN_SERVICE_URL,
    name: "People",
    marker: PhantomData,
};

const ME: SingletonEndpoint<Person> = SingletonEndpoint {
    service_url: TRIP_PIN_SERVICE_URL,
    name: "Me",
    marker: PhantomData,
};

#[tokio::test]
async fn retrieve_entity_set_succeeds() {
    let client = get_client();

    PEOPLE.retrieve(&client).await.unwrap();
}

#[tokio::test]
async fn retrieve_entity_set_gets_all_pages() {
    let client = get_client();

    let entity_set = PEOPLE.retrieve(&client).await.unwrap();

    assert_eq!(
        20,
        entity_set.len(),
        "Retrieved entity set count is incorrect"
    )
}

#[tokio::test]
async fn retrieve_entity_from_set_succeeds() {
    let client = get_client();

    PEOPLE
        .retrieve_entity(&client, "russellwhyte")
        .await
        .unwrap();
}

#[tokio::test]
async fn retrieve_singleton_entity_succeeds() {
    let client = get_client();

    ME.get(&client).await.unwrap();
}

fn get_client() -> reqwest::Client {
    reqwest::Client::builder()
        .danger_accept_invalid_certs(true)
        .build()
        .unwrap()
}