1use crate::client::contact::{Contact, ContactClient};
2use crate::config::Config;
3use crate::error::YukiError;
4use crate::output::{
5 ListOptions, OutputFormat, apply_pagination, format_json, format_table, is_tty, select_fields,
6};
7
8fn contacts_to_rows(contacts: &[Contact]) -> Vec<Vec<String>> {
9 contacts
10 .iter()
11 .map(|c| {
12 vec![
13 c.id.clone(),
14 c.name.clone(),
15 c.contact_type.clone(),
16 c.country.clone(),
17 if c.is_supplier { "Yes" } else { "No" }.to_string(),
18 if c.is_customer { "Yes" } else { "No" }.to_string(),
19 ]
20 })
21 .collect()
22}
23
24pub async fn search(
25 config: &Config,
26 _admin: Option<&str>,
27 query: &str,
28 format: Option<&str>,
29) -> Result<(), YukiError> {
30 let mut client = ContactClient::new();
31 client.authenticate(&config.api_key).await?;
32 let contacts = client.search_contacts(query).await?;
33
34 let headers = vec![
35 "ID".into(),
36 "Name".into(),
37 "Type".into(),
38 "Country".into(),
39 "Supplier".into(),
40 "Customer".into(),
41 ];
42 let rows = contacts_to_rows(&contacts);
43
44 let fmt = OutputFormat::from_flag(format, is_tty());
45 match fmt {
46 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
47 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
48 }
49 Ok(())
50}
51
52const CONTACT_TYPES: &[&str] = &["Customer", "Supplier", "Both", "None"];
54
55fn contact_type_value(requested: Option<&str>) -> Result<&'static str, YukiError> {
60 let Some(raw) = requested.map(str::trim).filter(|s| !s.is_empty()) else {
61 return Ok("Both");
62 };
63 CONTACT_TYPES
64 .iter()
65 .find(|valid| valid.eq_ignore_ascii_case(raw))
66 .copied()
67 .ok_or_else(|| {
68 YukiError::Config(format!(
69 "unknown contact type: {raw} (expected one of: {})",
70 CONTACT_TYPES.join(", ")
71 ))
72 })
73}
74
75pub async fn list(
76 config: &Config,
77 _admin: Option<&str>,
78 contact_type: Option<&str>,
79 format: Option<&str>,
80 opts: ListOptions<'_>,
81) -> Result<(), YukiError> {
82 let contact_type = contact_type_value(contact_type)?;
83 let mut client = ContactClient::new();
84 client.authenticate(&config.api_key).await?;
85 let contacts = client.get_suppliers_and_customers(contact_type).await?;
86
87 let mut headers = vec![
88 "ID".into(),
89 "Name".into(),
90 "Type".into(),
91 "Country".into(),
92 "Supplier".into(),
93 "Customer".into(),
94 ];
95 let mut rows = contacts_to_rows(&contacts);
96 apply_pagination(&mut rows, &opts);
97 select_fields(&mut headers, &mut rows, &opts)?;
98
99 let fmt = OutputFormat::from_flag(format, is_tty());
100 match fmt {
101 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
102 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
103 }
104 Ok(())
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn defaults_to_both_when_unset() {
113 assert_eq!(contact_type_value(None).unwrap(), "Both");
116 assert_eq!(contact_type_value(Some("")).unwrap(), "Both");
117 assert_eq!(contact_type_value(Some(" ")).unwrap(), "Both");
118 }
119
120 #[test]
121 fn normalises_casing_to_the_schema() {
122 assert_eq!(contact_type_value(Some("customer")).unwrap(), "Customer");
123 assert_eq!(contact_type_value(Some("SUPPLIER")).unwrap(), "Supplier");
124 assert_eq!(contact_type_value(Some("Both")).unwrap(), "Both");
125 assert_eq!(contact_type_value(Some("none")).unwrap(), "None");
126 }
127
128 #[test]
129 fn rejects_values_outside_the_enum() {
130 let err = contact_type_value(Some("vendor")).unwrap_err();
131 assert!(err.to_string().contains("unknown contact type"));
132 }
133}