use std::borrow::Cow;
use crate::client::Response;
use crate::error::Error;
use crate::form::FormResponse;
use crate::generated::routes;
use crate::generated::types::{Extenzion as ExtenzionPayload, NavigationItem, NavigationResponse};
use crate::http::Method;
use crate::observability::OperationInfo;
use crate::services::write_info;
pub use crate::generated::services::extenzions::*;
const NAVIGATION_GROUP: &str = "Extensions";
const CONTACT_PATH: &str = "/contacts/";
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Extenzion {
pub id: i64,
pub name: String,
pub app_url: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CreateExtenzionParams {
pub name: String,
pub members: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct UpdateExtenzionParams {
pub name: Option<String>,
pub members: Option<Vec<String>>,
}
impl Extenzions<'_> {
pub async fn list(&self) -> Result<Vec<Extenzion>, Error> {
let mut operation = self.client().operation(&routes::GET_NAVIGATION, &[]);
operation.info(OperationInfo {
service: Cow::Borrowed("Extenzions"),
operation: Cow::Borrowed("ListExtenzions"),
resource_type: Cow::Borrowed("extenzion"),
is_mutation: false,
resource_id: None,
});
let navigation: NavigationResponse = self.client().send(operation).await?;
extenzions_from_navigation(&navigation)
}
pub async fn create(
&self,
account_id: i64,
params: &CreateExtenzionParams,
) -> Result<Option<Extenzion>, Error> {
let mut fields = vec![("extenzion[name]", params.name.as_str())];
for member in ¶ms.members {
fields.push(("extenzion[members][]", member));
}
let mut operation = self.client().form(
Method::POST,
&format!("/accounts/{account_id}/domains/extenzions.json"),
)?;
operation.info(write_info(
"Extenzions",
"CreateExtenzion",
"extenzion",
None,
));
operation.form(&fields);
extenzion_from_form_response(&self.client().execute(operation).await?)
}
pub async fn update(
&self,
account_id: i64,
extenzion_id: i64,
params: &UpdateExtenzionParams,
) -> Result<Option<Extenzion>, Error> {
let mut fields = Vec::new();
if let Some(name) = ¶ms.name
&& !name.is_empty()
{
fields.push(("extenzion[name]", name.as_str()));
}
if let Some(members) = ¶ms.members {
for member in members {
fields.push(("extenzion[members][]", member));
}
}
let mut operation = self.client().form(
Method::PATCH,
&format!("/accounts/{account_id}/domains/extenzions/{extenzion_id}.json"),
)?;
operation.info(write_info(
"Extenzions",
"UpdateExtenzion",
"extenzion",
Some(extenzion_id),
));
operation.form(&fields);
extenzion_from_form_response(&self.client().execute(operation).await?)
}
}
fn extenzions_from_navigation(navigation: &NavigationResponse) -> Result<Vec<Extenzion>, Error> {
navigation
.items
.iter()
.flatten()
.filter(|item| item.title.as_deref() == Some(NAVIGATION_GROUP))
.flat_map(|group| group.menu_items.iter().flatten())
.filter_map(|entry| listed_extenzion(entry).transpose())
.collect()
}
fn listed_extenzion(entry: &NavigationItem) -> Result<Option<Extenzion>, Error> {
let app_url = entry.app_url.clone().unwrap_or_default();
Ok(contact_id_from_url(&app_url)?.map(|id| Extenzion {
id,
name: entry.title.clone().unwrap_or_default(),
app_url,
}))
}
fn extenzion_from_form_response(answered: &Response) -> Result<Option<Extenzion>, Error> {
let written = FormResponse::new(answered);
if written.body.is_empty() {
Ok(None)
} else {
let payload: ExtenzionPayload = serde_json::from_str(&written.body)?;
let app_url = payload.app_url.unwrap_or_default();
let id = contact_id_from_url(&app_url)?
.ok_or_else(|| Error::api(0, format!("no contact id in {app_url:?}")))?;
Ok(Some(Extenzion {
id,
name: payload.name.unwrap_or_default(),
app_url,
}))
}
}
fn contact_id_from_url(contact_url: &str) -> Result<Option<i64>, Error> {
for (at, _) in contact_url.match_indices(CONTACT_PATH) {
let digits = digit_run(&contact_url[at + CONTACT_PATH.len()..]);
if !digits.is_empty() {
return digits.parse().map(Some).map_err(|error| {
Error::api(
0,
format!("contact id in {contact_url:?} is not a number: {error}"),
)
});
}
}
Ok(None)
}
fn digit_run(text: &str) -> String {
text.chars().take_while(char::is_ascii_digit).collect()
}