use crate::client::contact::{Contact, ContactClient};
use crate::config::Config;
use crate::error::YukiError;
use crate::output::{
ListOptions, OutputFormat, apply_pagination, format_json, format_table, is_tty, select_fields,
};
fn contacts_to_rows(contacts: &[Contact]) -> Vec<Vec<String>> {
contacts
.iter()
.map(|c| {
vec![
c.id.clone(),
c.name.clone(),
c.contact_type.clone(),
c.country.clone(),
if c.is_supplier { "Yes" } else { "No" }.to_string(),
if c.is_customer { "Yes" } else { "No" }.to_string(),
]
})
.collect()
}
pub async fn search(
config: &Config,
_admin: Option<&str>,
query: &str,
format: Option<&str>,
) -> Result<(), YukiError> {
let mut client = ContactClient::new();
client.authenticate(&config.api_key).await?;
let contacts = client.search_contacts(query).await?;
let headers = vec![
"ID".into(),
"Name".into(),
"Type".into(),
"Country".into(),
"Supplier".into(),
"Customer".into(),
];
let rows = contacts_to_rows(&contacts);
let fmt = OutputFormat::from_flag(format, is_tty());
match fmt {
OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
}
Ok(())
}
const CONTACT_TYPES: &[&str] = &["Customer", "Supplier", "Both", "None"];
fn contact_type_value(requested: Option<&str>) -> Result<&'static str, YukiError> {
let Some(raw) = requested.map(str::trim).filter(|s| !s.is_empty()) else {
return Ok("Both");
};
CONTACT_TYPES
.iter()
.find(|valid| valid.eq_ignore_ascii_case(raw))
.copied()
.ok_or_else(|| {
YukiError::Config(format!(
"unknown contact type: {raw} (expected one of: {})",
CONTACT_TYPES.join(", ")
))
})
}
pub async fn list(
config: &Config,
_admin: Option<&str>,
contact_type: Option<&str>,
format: Option<&str>,
opts: ListOptions<'_>,
) -> Result<(), YukiError> {
let contact_type = contact_type_value(contact_type)?;
let mut client = ContactClient::new();
client.authenticate(&config.api_key).await?;
let contacts = client.get_suppliers_and_customers(contact_type).await?;
let mut headers = vec![
"ID".into(),
"Name".into(),
"Type".into(),
"Country".into(),
"Supplier".into(),
"Customer".into(),
];
let mut rows = contacts_to_rows(&contacts);
apply_pagination(&mut rows, &opts);
select_fields(&mut headers, &mut rows, &opts)?;
let fmt = OutputFormat::from_flag(format, is_tty());
match fmt {
OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_to_both_when_unset() {
assert_eq!(contact_type_value(None).unwrap(), "Both");
assert_eq!(contact_type_value(Some("")).unwrap(), "Both");
assert_eq!(contact_type_value(Some(" ")).unwrap(), "Both");
}
#[test]
fn normalises_casing_to_the_schema() {
assert_eq!(contact_type_value(Some("customer")).unwrap(), "Customer");
assert_eq!(contact_type_value(Some("SUPPLIER")).unwrap(), "Supplier");
assert_eq!(contact_type_value(Some("Both")).unwrap(), "Both");
assert_eq!(contact_type_value(Some("none")).unwrap(), "None");
}
#[test]
fn rejects_values_outside_the_enum() {
let err = contact_type_value(Some("vendor")).unwrap_err();
assert!(err.to_string().contains("unknown contact type"));
}
}