mod http_client;
#[cfg(test)]
mod test;
use self::http_client::ODataHttpClient;
use crate::{EntityProperties, ExpandQuery};
use anyhow::Context;
use reqwest::{Method, Request, Url};
use serde::Deserialize;
use std::marker::PhantomData;
pub struct EntitySetEndpoint<P> {
pub service_url: &'static str,
pub name: &'static str,
pub marker: PhantomData<P>,
}
impl<P: EntityProperties> EntitySetEndpoint<P> {
pub async fn retrieve<Client: ODataHttpClient>(
&self,
client: &Client,
) -> Result<Vec<Entity<P>>, anyhow::Error> {
retrieve_entity_set(client, self.service_url, self.name).await
}
pub async fn retrieve_entity<Client: ODataHttpClient>(
&self,
client: &Client,
id: &str,
) -> Result<Entity<P>, anyhow::Error> {
retrieve_entity_from_set(client, self.service_url, self.name, id).await
}
}
pub struct SingletonEndpoint<P> {
pub service_url: &'static str,
pub name: &'static str,
pub marker: PhantomData<P>,
}
impl<P: EntityProperties> SingletonEndpoint<P> {
pub async fn get<Client: ODataHttpClient>(
&self,
client: &Client,
) -> Result<Entity<P>, anyhow::Error> {
retrieve_singleton_entity(client, self.service_url, self.name).await
}
}
#[derive(Debug, Deserialize)]
pub struct Entity<P> {
#[serde(rename = "@odata.id")]
pub id: Option<String>,
#[serde(rename = "@odata.etag")]
pub etag: Option<String>,
#[serde(rename = "@odata.editLink")]
pub edit_link: Option<String>,
#[serde(flatten)]
pub properties: P,
}
#[derive(Debug, Deserialize)]
pub struct EntityLink<P: EntityProperties> {
#[serde(skip)]
marker: PhantomData<P>,
#[serde(rename = "@odata.id")]
pub id: String,
}
impl<P: EntityProperties> EntityLink<P> {
pub async fn get(&self, client: &impl ODataHttpClient) -> Result<Entity<P>, anyhow::Error> {
let request = Request::new(
Method::GET,
with_expand_query::<P>(
Url::parse(&self.id).context("Entity link ID is not valid URL")?,
),
);
let response: ServiceResponse<Entity<P>> =
client.execute_request(request).await?.json().await?;
Ok(response.payload)
}
}
#[derive(Debug, Deserialize)]
pub struct EntityLinkStub<P> {
#[serde(skip)]
marker: PhantomData<P>,
#[serde(rename = "@odata.id")]
pub id: String,
}
async fn retrieve_entity_set<P: EntityProperties, Client: ODataHttpClient>(
client: &Client,
service_url: &str,
set_name: &str,
) -> Result<Vec<Entity<P>>, anyhow::Error> {
let mut entities = Vec::new();
let mut next_page_url = with_expand_query::<P>(
Url::parse(&format!("{}/{}", service_url, set_name)).expect("URL parse failed"),
);
loop {
let request = Request::new(Method::GET, next_page_url);
let mut response: ServiceResponse<EntitySetPayload<P>> =
client.execute_request(request).await?.json().await?;
entities.append(&mut response.payload.value);
if let Some(next_link) = response.payload.next_link {
next_page_url = Url::parse(&next_link)?;
} else {
return Ok(entities);
}
}
}
async fn retrieve_entity_from_set<P: EntityProperties, Client: ODataHttpClient>(
client: &Client,
service_url: &str,
set_name: &str,
id: &str,
) -> Result<Entity<P>, anyhow::Error> {
let request = Request::new(
Method::GET,
with_expand_query::<P>(
Url::parse(&format!("{}/{}('{}')", service_url, set_name, id))
.expect("URL parse failed"),
),
);
let response: ServiceResponse<Entity<P>> =
client.execute_request(request).await?.json().await?;
Ok(response.payload)
}
async fn retrieve_singleton_entity<P: EntityProperties, Client: ODataHttpClient>(
client: &Client,
service_url: &str,
singleton_name: &str,
) -> Result<Entity<P>, anyhow::Error> {
let request = Request::new(
Method::GET,
with_expand_query::<P>(
Url::parse(&format!("{}/{}", service_url, singleton_name)).expect("URL parse failed"),
),
);
let response: ServiceResponse<Entity<P>> =
client.execute_request(request).await?.json().await?;
Ok(response.payload)
}
fn with_expand_query<P: EntityProperties>(mut url: Url) -> Url {
match P::EXPAND_QUERY {
ExpandQuery::None => url,
ExpandQuery::Expand(query) => {
url.query_pairs_mut().append_pair("$expand", query);
url
}
}
}
#[derive(Debug, Deserialize)]
struct ServiceResponse<Payload> {
#[serde(rename = "@odata.context")]
context: String,
#[serde(flatten)]
payload: Payload,
}
#[derive(Debug, Deserialize)]
struct EntitySetPayload<P> {
#[serde(rename = "@odata.nextLink")]
next_link: Option<String>,
value: Vec<Entity<P>>,
}