use reqwest::Method;
use serde::Serialize;
use serde_json::{Map, Value, json};
use crate::client::{Client, object_or_null, segment};
use crate::error::{Error, Result};
use crate::event::{Serialized, object_or_empty};
use crate::models::{
Analysis, Identity, IdentityReview, LinkedIdentity, ScoreHistoryEntry, Scores,
};
use crate::paging::{ItemStream, ListCall, Page, Paging, filter, paging_setters};
#[derive(Debug, Clone, Copy)]
pub struct Identities<'a> {
client: &'a Client,
}
impl<'a> Identities<'a> {
pub(crate) fn new(client: &'a Client) -> Self {
Identities { client }
}
pub async fn list(&self, params: ListIdentities) -> Result<Page<Identity>> {
params.call().page(self.client).await
}
pub fn list_all(&self, params: ListIdentities) -> ItemStream<Identity> {
params.call().walk(self.client)
}
pub async fn get(&self, identity_id: &str) -> Result<Identity> {
let payload = self
.client
.request(Method::GET, &path(identity_id, "")?, &[], None)
.await?;
Ok(Identity::from_value(object_or_null(payload)))
}
pub async fn scores(&self, identity_id: &str) -> Result<Scores> {
let payload = self
.client
.request(Method::GET, &path(identity_id, "/scores")?, &[], None)
.await?;
Ok(Scores::from_value(&payload.unwrap_or(Value::Null)))
}
pub async fn analysis(&self, identity_id: &str) -> Result<Analysis> {
let payload = self
.client
.request(Method::GET, &path(identity_id, "/analysis")?, &[], None)
.await?;
Ok(Analysis::from_value(object_or_null(payload)))
}
pub async fn analysis_cycle(&self, identity_id: &str, cycle_id: i64) -> Result<Analysis> {
let suffix = format!("/analysis/{cycle_id}");
let payload = self
.client
.request(Method::GET, &path(identity_id, &suffix)?, &[], None)
.await?;
Ok(Analysis::from_value(object_or_null(payload)))
}
pub async fn history(
&self,
identity_id: &str,
limit: Option<u32>,
) -> Result<Vec<ScoreHistoryEntry>> {
let mut query = Vec::new();
filter(&mut query, "limit", limit);
let payload = self
.client
.request(Method::GET, &path(identity_id, "/history")?, &query, None)
.await?;
Ok(list(payload, ScoreHistoryEntry::from_value))
}
pub async fn links(&self, identity_id: &str) -> Result<Vec<LinkedIdentity>> {
let payload = self
.client
.request(Method::GET, &path(identity_id, "/links")?, &[], None)
.await?;
Ok(list(payload, LinkedIdentity::from_value))
}
pub async fn analyze(&self, identity_id: &str) -> Result<()> {
self.client
.request(
Method::POST,
&path(identity_id, "/actions/analyze")?,
&[],
None,
)
.await?;
Ok(())
}
pub async fn update(&self, identity_id: &str, update: IdentityUpdate) -> Result<Identity> {
let path = path(identity_id, "")?;
let body = update.into_body()?;
let payload = self
.client
.request(Method::PATCH, &path, &[], Some(&body))
.await?;
Ok(Identity::from_value(object_or_null(payload)))
}
pub async fn reviews(&self, identity_id: &str) -> Result<Vec<IdentityReview>> {
let payload = self
.client
.request(Method::GET, &path(identity_id, "/reviews")?, &[], None)
.await?;
Ok(list(payload, IdentityReview::from_value))
}
pub async fn review(
&self,
identity_id: &str,
feedback: Option<&str>,
) -> Result<IdentityReview> {
let path = path(identity_id, "/actions/review")?;
let body = match feedback.filter(|feedback| !feedback.is_empty()) {
Some(feedback) => json!({ "feedback": feedback }),
None => json!({}),
};
let payload = self
.client
.request(Method::POST, &path, &[], Some(&body))
.await?;
Ok(IdentityReview::from_value(object_or_null(payload)))
}
}
#[derive(Debug, Clone, Default)]
pub struct ListIdentities {
term: Option<String>,
group: Option<String>,
group_type: Option<String>,
device: Option<String>,
badge: Option<String>,
paging: Paging,
}
impl ListIdentities {
pub fn new() -> Self {
ListIdentities::default()
}
pub fn term(mut self, term: impl Into<String>) -> Self {
self.term = Some(term.into());
self
}
pub fn group(mut self, group_id: impl Into<String>) -> Self {
self.group = Some(group_id.into());
self
}
pub fn group_type(mut self, group_type: impl Into<String>) -> Self {
self.group_type = Some(group_type.into());
self
}
pub fn device(mut self, fingerprint: impl Into<String>) -> Self {
self.device = Some(fingerprint.into());
self
}
pub fn badge(mut self, slug: impl Into<String>) -> Self {
self.badge = Some(slug.into());
self
}
paging_setters!();
fn call(self) -> ListCall<Identity> {
let mut filters = Vec::new();
filter(&mut filters, "term", self.term);
filter(&mut filters, "group", self.group);
filter(&mut filters, "groupType", self.group_type);
filter(&mut filters, "device", self.device);
filter(&mut filters, "badge", self.badge);
ListCall {
path: "/identities",
filters,
paging: self.paging,
parse: Identity::from_value,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct IdentityUpdate {
disregarded: Option<bool>,
attributes: Option<Serialized>,
}
impl IdentityUpdate {
pub fn new() -> Self {
IdentityUpdate::default()
}
pub fn disregarded(mut self, disregarded: bool) -> Self {
self.disregarded = Some(disregarded);
self
}
pub fn attributes(mut self, attributes: impl Serialize) -> Self {
self.attributes = Some(Serialized::from(attributes));
self
}
fn into_body(self) -> Result<Value> {
let mut body = Map::new();
if let Some(disregarded) = self.disregarded {
body.insert("disregarded".into(), disregarded.into());
}
if self.attributes.is_some() {
let attributes = object_or_empty(self.attributes, "attributes")?;
body.insert("attributes".into(), Value::Object(attributes));
}
if body.is_empty() {
return Err(Error::InvalidArgument(
"Set disregarded, attributes, or both.".into(),
));
}
Ok(Value::Object(body))
}
}
fn path(identity_id: &str, suffix: &str) -> Result<String> {
Ok(format!(
"/identities/{}{suffix}",
segment(identity_id, "An identity id")?
))
}
pub(crate) fn list<T>(payload: Option<Value>, parse: fn(Value) -> T) -> Vec<T> {
match payload {
Some(Value::Array(items)) => items
.into_iter()
.filter(Value::is_object)
.map(parse)
.collect(),
_ => Vec::new(),
}
}