1use std::collections::HashMap;
40
41use serde::{Deserialize, Serialize};
42
43use crate::clients::RestClient;
44use crate::rest::{
45 build_path, get_path, ResourceError, ResourceOperation, ResourcePath, ResourceResponse,
46 RestResource,
47};
48use crate::HttpMethod;
49
50#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
81pub struct Province {
82 #[serde(skip_serializing)]
85 pub id: Option<u64>,
86
87 #[serde(skip_serializing)]
90 pub country_id: Option<u64>,
91
92 #[serde(skip_serializing)]
95 pub name: Option<String>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub code: Option<String>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub tax: Option<f64>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub tax_name: Option<String>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub tax_type: Option<String>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub tax_percentage: Option<f64>,
116
117 #[serde(skip_serializing)]
120 pub shipping_zone_id: Option<u64>,
121}
122
123impl Province {
124 pub async fn count_with_parent(
132 client: &RestClient,
133 country_id: u64,
134 _params: Option<ProvinceCountParams>,
135 ) -> Result<u64, ResourceError> {
136 let mut ids: HashMap<&str, String> = HashMap::new();
137 ids.insert("country_id", country_id.to_string());
138
139 let available_ids: Vec<&str> = ids.keys().copied().collect();
140 let path = get_path(Self::PATHS, ResourceOperation::Count, &available_ids).ok_or(
141 ResourceError::PathResolutionFailed {
142 resource: Self::NAME,
143 operation: "count",
144 },
145 )?;
146
147 let url = build_path(path.template, &ids);
148 let response = client.get(&url, None).await?;
149
150 if !response.is_ok() {
151 return Err(ResourceError::from_http_response(
152 response.code,
153 &response.body,
154 Self::NAME,
155 None,
156 response.request_id(),
157 ));
158 }
159
160 let count = response
161 .body
162 .get("count")
163 .and_then(serde_json::Value::as_u64)
164 .ok_or_else(|| {
165 ResourceError::Http(crate::clients::HttpError::Response(
166 crate::clients::HttpResponseError {
167 code: response.code,
168 message: "Missing 'count' in response".to_string(),
169 error_reference: response.request_id().map(ToString::to_string),
170 },
171 ))
172 })?;
173
174 Ok(count)
175 }
176
177 pub async fn find_with_parent(
186 client: &RestClient,
187 country_id: u64,
188 id: u64,
189 _params: Option<ProvinceFindParams>,
190 ) -> Result<ResourceResponse<Self>, ResourceError> {
191 let mut ids: HashMap<&str, String> = HashMap::new();
192 ids.insert("country_id", country_id.to_string());
193 ids.insert("id", id.to_string());
194
195 let available_ids: Vec<&str> = ids.keys().copied().collect();
196 let path = get_path(Self::PATHS, ResourceOperation::Find, &available_ids).ok_or(
197 ResourceError::PathResolutionFailed {
198 resource: Self::NAME,
199 operation: "find",
200 },
201 )?;
202
203 let url = build_path(path.template, &ids);
204 let response = client.get(&url, None).await?;
205
206 if !response.is_ok() {
207 return Err(ResourceError::from_http_response(
208 response.code,
209 &response.body,
210 Self::NAME,
211 Some(&id.to_string()),
212 response.request_id(),
213 ));
214 }
215
216 let key = Self::resource_key();
217 ResourceResponse::from_http_response(response, &key)
218 }
219}
220
221impl RestResource for Province {
222 type Id = u64;
223 type FindParams = ProvinceFindParams;
224 type AllParams = ProvinceListParams;
225 type CountParams = ProvinceCountParams;
226
227 const NAME: &'static str = "Province";
228 const PLURAL: &'static str = "provinces";
229
230 const PATHS: &'static [ResourcePath] = &[
235 ResourcePath::new(
236 HttpMethod::Get,
237 ResourceOperation::Find,
238 &["country_id", "id"],
239 "countries/{country_id}/provinces/{id}",
240 ),
241 ResourcePath::new(
242 HttpMethod::Get,
243 ResourceOperation::All,
244 &["country_id"],
245 "countries/{country_id}/provinces",
246 ),
247 ResourcePath::new(
248 HttpMethod::Get,
249 ResourceOperation::Count,
250 &["country_id"],
251 "countries/{country_id}/provinces/count",
252 ),
253 ResourcePath::new(
254 HttpMethod::Put,
255 ResourceOperation::Update,
256 &["country_id", "id"],
257 "countries/{country_id}/provinces/{id}",
258 ),
259 ];
261
262 fn get_id(&self) -> Option<Self::Id> {
263 self.id
264 }
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
269pub struct ProvinceFindParams {
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub fields: Option<String>,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
277pub struct ProvinceListParams {
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub since_id: Option<u64>,
281
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub fields: Option<String>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
289pub struct ProvinceCountParams {}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::rest::{get_path, ResourceOperation};
295
296 #[test]
297 fn test_province_serialization() {
298 let province = Province {
299 id: Some(224293623),
300 country_id: Some(879921427),
301 name: Some("Ontario".to_string()),
302 code: Some("ON".to_string()),
303 tax: Some(0.08),
304 tax_name: Some("HST".to_string()),
305 tax_type: Some("compounded".to_string()),
306 tax_percentage: Some(8.0),
307 shipping_zone_id: Some(123),
308 };
309
310 let json = serde_json::to_string(&province).unwrap();
311 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
312
313 assert_eq!(parsed["code"], "ON");
315 assert_eq!(parsed["tax"], 0.08);
316 assert_eq!(parsed["tax_name"], "HST");
317 assert_eq!(parsed["tax_type"], "compounded");
318 assert_eq!(parsed["tax_percentage"], 8.0);
319
320 assert!(parsed.get("id").is_none());
322 assert!(parsed.get("country_id").is_none());
323 assert!(parsed.get("name").is_none());
324 assert!(parsed.get("shipping_zone_id").is_none());
325 }
326
327 #[test]
328 fn test_province_deserialization() {
329 let json = r#"{
330 "id": 224293623,
331 "country_id": 879921427,
332 "name": "Ontario",
333 "code": "ON",
334 "tax": 0.08,
335 "tax_name": "HST",
336 "tax_type": "compounded",
337 "tax_percentage": 8.0,
338 "shipping_zone_id": 123456
339 }"#;
340
341 let province: Province = serde_json::from_str(json).unwrap();
342
343 assert_eq!(province.id, Some(224293623));
344 assert_eq!(province.country_id, Some(879921427));
345 assert_eq!(province.name, Some("Ontario".to_string()));
346 assert_eq!(province.code, Some("ON".to_string()));
347 assert_eq!(province.tax, Some(0.08));
348 assert_eq!(province.tax_name, Some("HST".to_string()));
349 assert_eq!(province.tax_type, Some("compounded".to_string()));
350 assert_eq!(province.tax_percentage, Some(8.0));
351 assert_eq!(province.shipping_zone_id, Some(123456));
352 }
353
354 #[test]
355 fn test_province_nested_paths_no_create_delete() {
356 let find_path = get_path(
358 Province::PATHS,
359 ResourceOperation::Find,
360 &["country_id", "id"],
361 );
362 assert!(find_path.is_some());
363 assert_eq!(
364 find_path.unwrap().template,
365 "countries/{country_id}/provinces/{id}"
366 );
367
368 let all_path = get_path(Province::PATHS, ResourceOperation::All, &["country_id"]);
370 assert!(all_path.is_some());
371 assert_eq!(
372 all_path.unwrap().template,
373 "countries/{country_id}/provinces"
374 );
375
376 let count_path = get_path(Province::PATHS, ResourceOperation::Count, &["country_id"]);
378 assert!(count_path.is_some());
379 assert_eq!(
380 count_path.unwrap().template,
381 "countries/{country_id}/provinces/count"
382 );
383
384 let update_path = get_path(
386 Province::PATHS,
387 ResourceOperation::Update,
388 &["country_id", "id"],
389 );
390 assert!(update_path.is_some());
391 assert_eq!(
392 update_path.unwrap().template,
393 "countries/{country_id}/provinces/{id}"
394 );
395
396 let create_path = get_path(Province::PATHS, ResourceOperation::Create, &["country_id"]);
398 assert!(create_path.is_none());
399
400 let delete_path = get_path(
402 Province::PATHS,
403 ResourceOperation::Delete,
404 &["country_id", "id"],
405 );
406 assert!(delete_path.is_none());
407 }
408
409 #[test]
410 fn test_province_constants() {
411 assert_eq!(Province::NAME, "Province");
412 assert_eq!(Province::PLURAL, "provinces");
413 }
414
415 #[test]
416 fn test_province_get_id() {
417 let province_with_id = Province {
418 id: Some(224293623),
419 code: Some("ON".to_string()),
420 ..Default::default()
421 };
422 assert_eq!(province_with_id.get_id(), Some(224293623));
423
424 let province_without_id = Province::default();
425 assert_eq!(province_without_id.get_id(), None);
426 }
427}