Skip to content

Commit 9c1285b

Browse files
authored
refactor(array): split array.rs types into smaller files
Fixes: #524
1 parent da9db12 commit 9c1285b

File tree

8 files changed

+1692
-1630
lines changed

8 files changed

+1692
-1630
lines changed

src/types/array.rs

Lines changed: 0 additions & 1630 deletions
This file was deleted.

src/types/array/array_key.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use std::{convert::TryFrom, fmt::Display};
2+
3+
use crate::{convert::FromZval, error::Error, flags::DataType, types::Zval};
4+
5+
/// Represents the key of a PHP array, which can be either a long or a string.
6+
#[derive(Debug, Clone, PartialEq)]
7+
pub enum ArrayKey<'a> {
8+
/// A numerical key.
9+
/// In Zend API it's represented by `u64` (`zend_ulong`), so the value needs
10+
/// to be cast to `zend_ulong` before passing into Zend functions.
11+
Long(i64),
12+
/// A string key.
13+
String(String),
14+
/// A string key by reference.
15+
Str(&'a str),
16+
}
17+
18+
impl From<String> for ArrayKey<'_> {
19+
fn from(value: String) -> Self {
20+
Self::String(value)
21+
}
22+
}
23+
24+
impl TryFrom<ArrayKey<'_>> for String {
25+
type Error = Error;
26+
27+
fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
28+
match value {
29+
ArrayKey::String(s) => Ok(s),
30+
ArrayKey::Str(s) => Ok(s.to_string()),
31+
ArrayKey::Long(_) => Err(Error::InvalidProperty),
32+
}
33+
}
34+
}
35+
36+
impl TryFrom<ArrayKey<'_>> for i64 {
37+
type Error = Error;
38+
39+
fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
40+
match value {
41+
ArrayKey::Long(i) => Ok(i),
42+
ArrayKey::String(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
43+
ArrayKey::Str(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
44+
}
45+
}
46+
}
47+
48+
impl ArrayKey<'_> {
49+
/// Check if the key is an integer.
50+
///
51+
/// # Returns
52+
///
53+
/// Returns true if the key is an integer, false otherwise.
54+
#[must_use]
55+
pub fn is_long(&self) -> bool {
56+
match self {
57+
ArrayKey::Long(_) => true,
58+
ArrayKey::String(_) | ArrayKey::Str(_) => false,
59+
}
60+
}
61+
}
62+
63+
impl Display for ArrayKey<'_> {
64+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65+
match self {
66+
ArrayKey::Long(key) => write!(f, "{key}"),
67+
ArrayKey::String(key) => write!(f, "{key}"),
68+
ArrayKey::Str(key) => write!(f, "{key}"),
69+
}
70+
}
71+
}
72+
73+
impl<'a> From<&'a str> for ArrayKey<'a> {
74+
fn from(key: &'a str) -> ArrayKey<'a> {
75+
ArrayKey::Str(key)
76+
}
77+
}
78+
79+
impl<'a> From<i64> for ArrayKey<'a> {
80+
fn from(index: i64) -> ArrayKey<'a> {
81+
ArrayKey::Long(index)
82+
}
83+
}
84+
85+
impl<'a> FromZval<'a> for ArrayKey<'_> {
86+
const TYPE: DataType = DataType::String;
87+
88+
fn from_zval(zval: &'a Zval) -> Option<Self> {
89+
if let Some(key) = zval.long() {
90+
return Some(ArrayKey::Long(key));
91+
}
92+
if let Some(key) = zval.string() {
93+
return Some(ArrayKey::String(key));
94+
}
95+
None
96+
}
97+
}
98+
99+
#[cfg(test)]
100+
#[cfg(feature = "embed")]
101+
#[allow(clippy::unwrap_used)]
102+
mod tests {
103+
use crate::error::Error;
104+
use crate::types::ArrayKey;
105+
106+
#[test]
107+
fn test_string_try_from_array_key() {
108+
let key = ArrayKey::String("test".to_string());
109+
let result: crate::error::Result<String, _> = key.try_into();
110+
assert!(result.is_ok());
111+
assert_eq!(result.unwrap(), "test".to_string());
112+
113+
let key = ArrayKey::Str("test");
114+
let result: crate::error::Result<String, _> = key.try_into();
115+
assert!(result.is_ok());
116+
assert_eq!(result.unwrap(), "test".to_string());
117+
118+
let key = ArrayKey::Long(42);
119+
let result: crate::error::Result<String, _> = key.try_into();
120+
assert!(result.is_err());
121+
assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
122+
123+
let key = ArrayKey::String("42".to_string());
124+
let result: crate::error::Result<String, _> = key.try_into();
125+
assert!(result.is_ok());
126+
assert_eq!(result.unwrap(), "42".to_string());
127+
128+
let key = ArrayKey::Str("123");
129+
let result: crate::error::Result<i64, _> = key.try_into();
130+
assert!(result.is_ok());
131+
assert_eq!(result.unwrap(), 123);
132+
}
133+
134+
#[test]
135+
fn test_i64_try_from_array_key() {
136+
let key = ArrayKey::Long(42);
137+
let result: crate::error::Result<i64, _> = key.try_into();
138+
assert!(result.is_ok());
139+
assert_eq!(result.unwrap(), 42);
140+
141+
let key = ArrayKey::String("42".to_string());
142+
let result: crate::error::Result<i64, _> = key.try_into();
143+
assert!(result.is_ok());
144+
assert_eq!(result.unwrap(), 42);
145+
146+
let key = ArrayKey::Str("123");
147+
let result: crate::error::Result<i64, _> = key.try_into();
148+
assert!(result.is_ok());
149+
assert_eq!(result.unwrap(), 123);
150+
151+
let key = ArrayKey::String("not a number".to_string());
152+
let result: crate::error::Result<i64, _> = key.try_into();
153+
assert!(result.is_err());
154+
assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
155+
}
156+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::{collections::HashMap, convert::TryFrom};
2+
3+
use crate::{
4+
boxed::ZBox,
5+
convert::{FromZval, IntoZval},
6+
error::{Error, Result},
7+
flags::DataType,
8+
types::Zval,
9+
};
10+
11+
use super::super::ZendHashTable;
12+
13+
// TODO: Generalize hasher
14+
#[allow(clippy::implicit_hasher)]
15+
impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
16+
where
17+
V: FromZval<'a>,
18+
{
19+
type Error = Error;
20+
21+
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
22+
let mut hm = HashMap::with_capacity(value.len());
23+
24+
for (key, val) in value {
25+
hm.insert(
26+
key.to_string(),
27+
V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
28+
);
29+
}
30+
31+
Ok(hm)
32+
}
33+
}
34+
35+
impl<K, V> TryFrom<HashMap<K, V>> for ZBox<ZendHashTable>
36+
where
37+
K: AsRef<str>,
38+
V: IntoZval,
39+
{
40+
type Error = Error;
41+
42+
fn try_from(value: HashMap<K, V>) -> Result<Self> {
43+
let mut ht = ZendHashTable::with_capacity(
44+
value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
45+
);
46+
47+
for (k, v) in value {
48+
ht.insert(k.as_ref(), v)?;
49+
}
50+
51+
Ok(ht)
52+
}
53+
}
54+
55+
// TODO: Generalize hasher
56+
#[allow(clippy::implicit_hasher)]
57+
impl<K, V> IntoZval for HashMap<K, V>
58+
where
59+
K: AsRef<str>,
60+
V: IntoZval,
61+
{
62+
const TYPE: DataType = DataType::Array;
63+
const NULLABLE: bool = false;
64+
65+
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
66+
let arr = self.try_into()?;
67+
zv.set_hashtable(arr);
68+
Ok(())
69+
}
70+
}
71+
72+
// TODO: Generalize hasher
73+
#[allow(clippy::implicit_hasher)]
74+
impl<'a, T> FromZval<'a> for HashMap<String, T>
75+
where
76+
T: FromZval<'a>,
77+
{
78+
const TYPE: DataType = DataType::Array;
79+
80+
fn from_zval(zval: &'a Zval) -> Option<Self> {
81+
zval.array().and_then(|arr| arr.try_into().ok())
82+
}
83+
}

src/types/array/conversions/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! Collection type conversions for `ZendHashTable`.
2+
//!
3+
//! This module provides conversions between Rust collection types and PHP arrays
4+
//! (represented as `ZendHashTable`). Each collection type has its own module for
5+
//! better organization and maintainability.
6+
//!
7+
//! ## Supported Collections
8+
//!
9+
//! - `HashMap<K, V>` ↔ `ZendHashTable` (via `hash_map` module)
10+
//! - `Vec<T>` and `Vec<(K, V)>` ↔ `ZendHashTable` (via `vec` module)
11+
12+
mod hash_map;
13+
mod vec;

0 commit comments

Comments
 (0)