yuki_client/client/
contact.rs1use quick_xml::Reader;
2use quick_xml::events::Event;
3
4use crate::error::YukiError;
5
6use super::local_name;
7use super::soap_client::{SoapClient, SoapEnvelope};
8
9const BASE_URL: &str = "https://api.yukiworks.nl/ws/Contact.asmx";
10
11#[derive(Debug, Clone)]
13pub struct Contact {
14 pub id: String,
15 pub name: String,
16 pub contact_type: String,
17 pub country: String,
18 pub is_supplier: bool,
19 pub is_customer: bool,
20}
21
22pub struct ContactClient {
24 soap: SoapClient,
25}
26
27impl ContactClient {
28 pub fn new() -> Self {
29 Self {
30 soap: SoapClient::new(BASE_URL),
31 }
32 }
33
34 pub fn with_client(http: reqwest::Client) -> Self {
37 Self {
38 soap: SoapClient::with_client(BASE_URL, http),
39 }
40 }
41
42 fn require_session(&self) -> Result<&str, YukiError> {
43 self.soap.session_id().ok_or_else(|| {
44 YukiError::AuthFailed("not authenticated — call authenticate() first".to_string())
45 })
46 }
47
48 pub async fn authenticate(&mut self, api_key: &str) -> Result<String, YukiError> {
50 self.soap.authenticate(api_key).await
51 }
52
53 pub async fn search_contacts(&self, query: &str) -> Result<Vec<Contact>, YukiError> {
55 let session = self.require_session()?;
56 let envelope = SoapEnvelope::new("SearchContacts")
57 .session(session)
58 .param("searchQuery", query)
59 .build();
60 let body = self.soap.call("SearchContacts", envelope).await?;
61 parse_contacts(&body)
62 }
63
64 pub async fn get_suppliers_and_customers(
66 &self,
67 contact_type: &str,
68 ) -> Result<Vec<Contact>, YukiError> {
69 let session = self.require_session()?;
70 let envelope = SoapEnvelope::new("GetSuppliersAndCustomers")
71 .session(session)
72 .param("contactType", contact_type)
73 .build();
74 let body = self.soap.call("GetSuppliersAndCustomers", envelope).await?;
75 parse_contacts(&body)
76 }
77}
78
79pub fn parse_contacts(xml: &str) -> Result<Vec<Contact>, YukiError> {
84 let mut reader = Reader::from_str(xml);
85 reader.config_mut().trim_text(true);
86
87 let mut contacts = Vec::new();
88 let mut in_contact = false;
89 let mut current_field = String::new();
90 let mut contact = Contact {
91 id: String::new(),
92 name: String::new(),
93 contact_type: String::new(),
94 country: String::new(),
95 is_supplier: false,
96 is_customer: false,
97 };
98 let mut buf = Vec::new();
99
100 loop {
101 match reader.read_event_into(&mut buf) {
102 Ok(Event::Start(ref e)) => {
103 let local = local_name(e.name().as_ref()).to_string();
104 match local.as_str() {
105 "Contact" => {
106 in_contact = true;
107 contact = Contact {
108 id: String::new(),
109 name: String::new(),
110 contact_type: String::new(),
111 country: String::new(),
112 is_supplier: false,
113 is_customer: false,
114 };
115 for attr in e.attributes().flatten() {
116 if attr.key.as_ref() == b"ID" {
117 contact.id = String::from_utf8_lossy(&attr.value).to_string();
118 }
119 }
120 }
121 "Type" | "Name" | "Country" | "IsSupplier" | "IsCustomer" if in_contact => {
122 current_field = local;
123 }
124 _ => {}
125 }
126 }
127 Ok(Event::Text(ref e)) if in_contact && !current_field.is_empty() => {
128 let text = e
129 .unescape()
130 .map_err(|e| YukiError::Xml(e.to_string()))?
131 .trim()
132 .to_string();
133 match current_field.as_str() {
134 "Type" => contact.contact_type = text,
135 "Name" => contact.name = text,
136 "Country" => contact.country = text,
137 "IsSupplier" => contact.is_supplier = text.eq_ignore_ascii_case("true"),
138 "IsCustomer" => contact.is_customer = text.eq_ignore_ascii_case("true"),
139 _ => {}
140 }
141 }
142 Ok(Event::End(ref e)) => {
143 let name = e.name();
144 let local = local_name(name.as_ref());
145 match local {
146 "Type" | "Name" | "Country" | "IsSupplier" | "IsCustomer" => {
147 current_field.clear();
148 }
149 "Contact" => {
150 if !contact.id.is_empty() {
151 contacts.push(contact.clone());
152 }
153 in_contact = false;
154 }
155 _ => {}
156 }
157 }
158 Ok(Event::Eof) => break,
159 Err(e) => return Err(YukiError::Xml(e.to_string())),
160 _ => {}
161 }
162 buf.clear();
163 }
164
165 Ok(contacts)
166}
167
168impl Default for ContactClient {
169 fn default() -> Self {
170 Self::new()
171 }
172}