unifly_api/convert/
supporting.rs1use serde_json::Value;
2
3use crate::integration_types;
4use crate::model::common::DataSource;
5use crate::model::entity_id::EntityId;
6use crate::model::hotspot::Voucher;
7use crate::model::supporting::TrafficMatchingList;
8
9use super::helpers::parse_iso;
10
11fn traffic_matching_item_to_string(item: &Value) -> Option<String> {
12 match item {
13 Value::String(value) => Some(value.clone()),
14 Value::Object(map) => {
15 if let Some(value) = map
16 .get("value")
17 .and_then(Value::as_str)
18 .map(str::to_owned)
19 .or_else(|| {
20 map.get("value")
21 .and_then(Value::as_i64)
22 .map(|value| value.to_string())
23 })
24 {
25 return Some(value);
26 }
27
28 let start = map.get("start").or_else(|| map.get("startPort"));
29 let stop = map.get("stop").or_else(|| map.get("endPort"));
30 match (start, stop) {
31 (Some(start), Some(stop)) => {
32 let start = start
33 .as_str()
34 .map(str::to_owned)
35 .or_else(|| start.as_i64().map(|value| value.to_string()));
36 let stop = stop
37 .as_str()
38 .map(str::to_owned)
39 .or_else(|| stop.as_i64().map(|value| value.to_string()));
40 match (start, stop) {
41 (Some(start), Some(stop)) => Some(format!("{start}-{stop}")),
42 _ => None,
43 }
44 }
45 _ => None,
46 }
47 }
48 _ => None,
49 }
50}
51
52impl From<integration_types::TrafficMatchingListResponse> for TrafficMatchingList {
53 fn from(t: integration_types::TrafficMatchingListResponse) -> Self {
54 let items = t
55 .extra
56 .get("items")
57 .and_then(|v| v.as_array())
58 .map(|arr| {
59 arr.iter()
60 .filter_map(traffic_matching_item_to_string)
61 .collect()
62 })
63 .unwrap_or_default();
64
65 TrafficMatchingList {
66 id: EntityId::Uuid(t.id),
67 name: t.name,
68 list_type: t.list_type,
69 items,
70 origin: None,
71 }
72 }
73}
74
75impl From<integration_types::VoucherResponse> for Voucher {
76 fn from(v: integration_types::VoucherResponse) -> Self {
77 #[allow(
78 clippy::as_conversions,
79 clippy::cast_possible_truncation,
80 clippy::cast_sign_loss
81 )]
82 Voucher {
83 id: EntityId::Uuid(v.id),
84 code: v.code,
85 name: Some(v.name),
86 created_at: parse_iso(&v.created_at),
87 activated_at: v.activated_at.as_deref().and_then(parse_iso),
88 expires_at: v.expires_at.as_deref().and_then(parse_iso),
89 expired: v.expired,
90 time_limit_minutes: Some(v.time_limit_minutes as u32),
91 data_usage_limit_mb: v.data_usage_limit_m_bytes.map(|b| b as u64),
92 authorized_guest_limit: v.authorized_guest_limit.map(|l| l as u32),
93 authorized_guest_count: Some(v.authorized_guest_count as u32),
94 rx_rate_limit_kbps: v.rx_rate_limit_kbps.map(|r| r as u64),
95 tx_rate_limit_kbps: v.tx_rate_limit_kbps.map(|r| r as u64),
96 source: DataSource::IntegrationApi,
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use std::collections::HashMap;
104
105 use super::*;
106 use serde_json::json;
107
108 #[test]
109 fn integration_traffic_matching_list_formats_structured_items() {
110 let response = integration_types::TrafficMatchingListResponse {
111 id: uuid::Uuid::nil(),
112 name: "Ports".into(),
113 list_type: "PORT".into(),
114 extra: HashMap::from([(
115 "items".into(),
116 json!([
117 {"type": "PORT_NUMBER", "value": 443},
118 {"type": "PORT_RANGE", "start": 1000, "stop": 2000}
119 ]),
120 )]),
121 };
122
123 let list = TrafficMatchingList::from(response);
124 assert_eq!(list.items, vec!["443".to_owned(), "1000-2000".to_owned()]);
125 }
126}