Skip to content

Commit 54e9328

Browse files
kakserpomXenira
andauthored
feat(array): introducing BTreeMap conversion and refactoring HashMap conversion
* feat(array): introducing BTreeMap conversion and refactoring HashMap conversion * feat(array): add support for `BTreeMap` `ArrayKey` * test(array): add `BTreeMap` tests --------- Refs: #535 Co-authored-by: Xenira <[email protected]>
1 parent 1176ef6 commit 54e9328

File tree

8 files changed

+409
-152
lines changed

8 files changed

+409
-152
lines changed

src/types/array/array_key.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use std::{convert::TryFrom, fmt::Display};
2-
31
use crate::{convert::FromZval, error::Error, flags::DataType, types::Zval};
2+
use std::str::FromStr;
3+
use std::{convert::TryFrom, fmt::Display};
44

55
/// Represents the key of a PHP array, which can be either a long or a string.
6-
#[derive(Debug, Clone, PartialEq)]
6+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
77
pub enum ArrayKey<'a> {
88
/// A numerical key.
99
/// In Zend API it's represented by `u64` (`zend_ulong`), so the value needs
@@ -17,26 +17,30 @@ pub enum ArrayKey<'a> {
1717

1818
impl From<String> for ArrayKey<'_> {
1919
fn from(value: String) -> Self {
20-
Self::String(value)
20+
if let Ok(index) = i64::from_str(value.as_str()) {
21+
Self::Long(index)
22+
} else {
23+
Self::String(value)
24+
}
2125
}
2226
}
2327

2428
impl TryFrom<ArrayKey<'_>> for String {
2529
type Error = Error;
2630

27-
fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
31+
fn try_from(value: ArrayKey<'_>) -> Result<Self, Self::Error> {
2832
match value {
2933
ArrayKey::String(s) => Ok(s),
3034
ArrayKey::Str(s) => Ok(s.to_string()),
31-
ArrayKey::Long(_) => Err(Error::InvalidProperty),
35+
ArrayKey::Long(l) => Ok(l.to_string()),
3236
}
3337
}
3438
}
3539

3640
impl TryFrom<ArrayKey<'_>> for i64 {
3741
type Error = Error;
3842

39-
fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
43+
fn try_from(value: ArrayKey<'_>) -> Result<Self, Self::Error> {
4044
match value {
4145
ArrayKey::Long(i) => Ok(i),
4246
ArrayKey::String(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
@@ -71,8 +75,12 @@ impl Display for ArrayKey<'_> {
7175
}
7276

7377
impl<'a> From<&'a str> for ArrayKey<'a> {
74-
fn from(key: &'a str) -> ArrayKey<'a> {
75-
ArrayKey::Str(key)
78+
fn from(value: &'a str) -> ArrayKey<'a> {
79+
if let Ok(index) = i64::from_str(value) {
80+
Self::Long(index)
81+
} else {
82+
ArrayKey::Str(value)
83+
}
7684
}
7785
}
7886

@@ -117,8 +125,7 @@ mod tests {
117125

118126
let key = ArrayKey::Long(42);
119127
let result: crate::error::Result<String, _> = key.try_into();
120-
assert!(result.is_err());
121-
assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
128+
assert_eq!(result.unwrap(), "42".to_string());
122129

123130
let key = ArrayKey::String("42".to_string());
124131
let result: crate::error::Result<String, _> = key.try_into();
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
use std::{collections::BTreeMap, convert::TryFrom};
2+
3+
use super::super::ZendHashTable;
4+
use crate::types::ArrayKey;
5+
use crate::{
6+
boxed::ZBox,
7+
convert::{FromZval, IntoZval},
8+
error::{Error, Result},
9+
flags::DataType,
10+
types::Zval,
11+
};
12+
13+
impl<'a, K, V> TryFrom<&'a ZendHashTable> for BTreeMap<K, V>
14+
where
15+
K: TryFrom<ArrayKey<'a>, Error = Error> + Ord,
16+
V: FromZval<'a>,
17+
{
18+
type Error = Error;
19+
20+
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
21+
let mut map = Self::new();
22+
23+
for (key, val) in value {
24+
map.insert(
25+
key.try_into()?,
26+
V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
27+
);
28+
}
29+
30+
Ok(map)
31+
}
32+
}
33+
34+
impl<'a, V> TryFrom<&'a ZendHashTable> for BTreeMap<ArrayKey<'a>, V>
35+
where
36+
V: FromZval<'a>,
37+
{
38+
type Error = Error;
39+
40+
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
41+
let mut map = Self::new();
42+
43+
for (key, val) in value {
44+
map.insert(
45+
key,
46+
V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
47+
);
48+
}
49+
50+
Ok(map)
51+
}
52+
}
53+
54+
impl<'a, K, V> TryFrom<BTreeMap<K, V>> for ZBox<ZendHashTable>
55+
where
56+
K: Into<ArrayKey<'a>>,
57+
V: IntoZval,
58+
{
59+
type Error = Error;
60+
61+
fn try_from(value: BTreeMap<K, V>) -> Result<Self> {
62+
let mut ht = ZendHashTable::with_capacity(
63+
value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
64+
);
65+
66+
for (k, v) in value {
67+
ht.insert(k, v)?;
68+
}
69+
70+
Ok(ht)
71+
}
72+
}
73+
74+
impl<'a, K, V> IntoZval for BTreeMap<K, V>
75+
where
76+
K: Into<ArrayKey<'a>>,
77+
V: IntoZval,
78+
{
79+
const TYPE: DataType = DataType::Array;
80+
const NULLABLE: bool = false;
81+
82+
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
83+
let arr = self.try_into()?;
84+
zv.set_hashtable(arr);
85+
Ok(())
86+
}
87+
}
88+
89+
impl<'a, K, V> FromZval<'a> for BTreeMap<K, V>
90+
where
91+
K: TryFrom<ArrayKey<'a>, Error = Error> + Ord,
92+
V: FromZval<'a>,
93+
{
94+
const TYPE: DataType = DataType::Array;
95+
96+
fn from_zval(zval: &'a Zval) -> Option<Self> {
97+
zval.array().and_then(|arr| arr.try_into().ok())
98+
}
99+
}
100+
101+
impl<'a, V> FromZval<'a> for BTreeMap<ArrayKey<'a>, V>
102+
where
103+
V: FromZval<'a>,
104+
{
105+
const TYPE: DataType = DataType::Array;
106+
107+
fn from_zval(zval: &'a Zval) -> Option<Self> {
108+
zval.array().and_then(|arr| arr.try_into().ok())
109+
}
110+
}
111+
112+
#[cfg(test)]
113+
#[cfg(feature = "embed")]
114+
#[allow(clippy::unwrap_used)]
115+
mod tests {
116+
use std::collections::BTreeMap;
117+
118+
use crate::boxed::ZBox;
119+
use crate::convert::{FromZval, IntoZval};
120+
use crate::embed::Embed;
121+
use crate::error::Error;
122+
use crate::types::{ArrayKey, ZendHashTable, Zval};
123+
124+
#[test]
125+
fn test_hash_table_try_from_btree_mab() {
126+
Embed::run(|| {
127+
let mut map = BTreeMap::new();
128+
map.insert("key1", "value1");
129+
map.insert("key2", "value2");
130+
map.insert("key3", "value3");
131+
132+
let ht: ZBox<ZendHashTable> = map.try_into().unwrap();
133+
assert_eq!(ht.len(), 3);
134+
assert_eq!(ht.get("key1").unwrap().string().unwrap(), "value1");
135+
assert_eq!(ht.get("key2").unwrap().string().unwrap(), "value2");
136+
assert_eq!(ht.get("key3").unwrap().string().unwrap(), "value3");
137+
138+
let mut map_i64 = BTreeMap::new();
139+
map_i64.insert(1, "value1");
140+
map_i64.insert(2, "value2");
141+
map_i64.insert(3, "value3");
142+
143+
let ht_i64: ZBox<ZendHashTable> = map_i64.try_into().unwrap();
144+
assert_eq!(ht_i64.len(), 3);
145+
assert_eq!(ht_i64.get(1).unwrap().string().unwrap(), "value1");
146+
assert_eq!(ht_i64.get(2).unwrap().string().unwrap(), "value2");
147+
assert_eq!(ht_i64.get(3).unwrap().string().unwrap(), "value3");
148+
});
149+
}
150+
151+
#[test]
152+
fn test_btree_map_into_zval() {
153+
Embed::run(|| {
154+
let mut map = BTreeMap::new();
155+
map.insert("key1", "value1");
156+
map.insert("key2", "value2");
157+
map.insert("key3", "value3");
158+
159+
let zval = map.into_zval(false).unwrap();
160+
assert!(zval.is_array());
161+
let ht: &ZendHashTable = zval.array().unwrap();
162+
assert_eq!(ht.len(), 3);
163+
assert_eq!(ht.get("key1").unwrap().string().unwrap(), "value1");
164+
assert_eq!(ht.get("key2").unwrap().string().unwrap(), "value2");
165+
assert_eq!(ht.get("key3").unwrap().string().unwrap(), "value3");
166+
167+
let mut map_i64 = BTreeMap::new();
168+
map_i64.insert(1, "value1");
169+
map_i64.insert(2, "value2");
170+
map_i64.insert(3, "value3");
171+
let zval_i64 = map_i64.into_zval(false).unwrap();
172+
assert!(zval_i64.is_array());
173+
let ht_i64: &ZendHashTable = zval_i64.array().unwrap();
174+
assert_eq!(ht_i64.len(), 3);
175+
assert_eq!(ht_i64.get(1).unwrap().string().unwrap(), "value1");
176+
assert_eq!(ht_i64.get(2).unwrap().string().unwrap(), "value2");
177+
assert_eq!(ht_i64.get(3).unwrap().string().unwrap(), "value3");
178+
});
179+
}
180+
181+
#[test]
182+
fn test_btree_map_from_zval() {
183+
Embed::run(|| {
184+
let mut ht = ZendHashTable::new();
185+
ht.insert("key1", "value1").unwrap();
186+
ht.insert("key2", "value2").unwrap();
187+
ht.insert("key3", "value3").unwrap();
188+
let mut zval = Zval::new();
189+
zval.set_hashtable(ht);
190+
191+
let map = BTreeMap::<String, String>::from_zval(&zval).unwrap();
192+
assert_eq!(map.len(), 3);
193+
assert_eq!(map.get("key1").unwrap(), "value1");
194+
assert_eq!(map.get("key2").unwrap(), "value2");
195+
assert_eq!(map.get("key3").unwrap(), "value3");
196+
197+
let mut ht_i64 = ZendHashTable::new();
198+
ht_i64.insert(1, "value1").unwrap();
199+
ht_i64.insert("2", "value2").unwrap();
200+
ht_i64.insert(3, "value3").unwrap();
201+
let mut zval_i64 = Zval::new();
202+
zval_i64.set_hashtable(ht_i64);
203+
204+
let map_i64 = BTreeMap::<i64, String>::from_zval(&zval_i64).unwrap();
205+
assert_eq!(map_i64.len(), 3);
206+
assert_eq!(map_i64.get(&1).unwrap(), "value1");
207+
assert_eq!(map_i64.get(&2).unwrap(), "value2");
208+
assert_eq!(map_i64.get(&3).unwrap(), "value3");
209+
210+
let mut ht_mixed = ZendHashTable::new();
211+
ht_mixed.insert("key1", "value1").unwrap();
212+
ht_mixed.insert(2, "value2").unwrap();
213+
ht_mixed.insert("3", "value3").unwrap();
214+
let mut zval_mixed = Zval::new();
215+
zval_mixed.set_hashtable(ht_mixed);
216+
217+
let map_mixed = BTreeMap::<String, String>::from_zval(&zval_mixed);
218+
assert!(map_mixed.is_some());
219+
});
220+
}
221+
222+
#[test]
223+
fn test_btree_map_array_key_from_zval() {
224+
Embed::run(|| {
225+
let mut ht = ZendHashTable::new();
226+
ht.insert("key1", "value1").unwrap();
227+
ht.insert(2, "value2").unwrap();
228+
ht.insert("3", "value3").unwrap();
229+
let mut zval = Zval::new();
230+
zval.set_hashtable(ht);
231+
232+
let map = BTreeMap::<ArrayKey, String>::from_zval(&zval).unwrap();
233+
assert_eq!(map.len(), 3);
234+
assert_eq!(
235+
map.get(&ArrayKey::String("key1".to_string())).unwrap(),
236+
"value1"
237+
);
238+
assert_eq!(map.get(&ArrayKey::Long(2)).unwrap(), "value2");
239+
assert_eq!(map.get(&ArrayKey::Long(3)).unwrap(), "value3");
240+
});
241+
}
242+
243+
#[test]
244+
fn test_btree_map_i64_v_try_from_hash_table() {
245+
Embed::run(|| {
246+
let mut ht = ZendHashTable::new();
247+
ht.insert(1, "value1").unwrap();
248+
ht.insert("2", "value2").unwrap();
249+
250+
let map: BTreeMap<i64, String> = ht.as_ref().try_into().unwrap();
251+
assert_eq!(map.len(), 2);
252+
assert_eq!(map.get(&1).unwrap(), "value1");
253+
assert_eq!(map.get(&2).unwrap(), "value2");
254+
255+
let mut ht2 = ZendHashTable::new();
256+
ht2.insert("key1", "value1").unwrap();
257+
ht2.insert("key2", "value2").unwrap();
258+
259+
let map_err: crate::error::Result<BTreeMap<i64, String>> = ht2.as_ref().try_into();
260+
assert!(map_err.is_err());
261+
assert!(matches!(map_err.unwrap_err(), Error::InvalidProperty));
262+
});
263+
}
264+
265+
#[test]
266+
fn test_btree_map_string_v_try_from_hash_table() {
267+
Embed::run(|| {
268+
let mut ht = ZendHashTable::new();
269+
ht.insert("key1", "value1").unwrap();
270+
ht.insert("key2", "value2").unwrap();
271+
272+
let map: BTreeMap<String, String> = ht.as_ref().try_into().unwrap();
273+
assert_eq!(map.len(), 2);
274+
assert_eq!(map.get("key1").unwrap(), "value1");
275+
assert_eq!(map.get("key2").unwrap(), "value2");
276+
277+
let mut ht2 = ZendHashTable::new();
278+
ht2.insert(1, "value1").unwrap();
279+
ht2.insert(2, "value2").unwrap();
280+
281+
let map2: crate::error::Result<BTreeMap<String, String>> = ht2.as_ref().try_into();
282+
assert!(map2.is_ok());
283+
});
284+
}
285+
286+
#[test]
287+
fn test_btree_map_array_key_v_try_from_hash_table() {
288+
Embed::run(|| {
289+
let mut ht = ZendHashTable::new();
290+
ht.insert("key1", "value1").unwrap();
291+
ht.insert(2, "value2").unwrap();
292+
ht.insert("3", "value3").unwrap();
293+
294+
let map: BTreeMap<ArrayKey, String> = ht.as_ref().try_into().unwrap();
295+
assert_eq!(map.len(), 3);
296+
assert_eq!(
297+
map.get(&ArrayKey::String("key1".to_string())).unwrap(),
298+
"value1"
299+
);
300+
assert_eq!(map.get(&ArrayKey::Long(2)).unwrap(), "value2");
301+
assert_eq!(map.get(&ArrayKey::Long(3)).unwrap(), "value3");
302+
});
303+
}
304+
}

0 commit comments

Comments
 (0)