• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

extphprs / ext-php-rs / 20069099524

09 Dec 2025 03:34PM UTC coverage: 35.015% (-0.3%) from 35.308%
20069099524

Pull #598

github

web-flow
Merge 455b1928b into 34035ed16
Pull Request #598: feat(types): Argument coercion into HashSet/BTreeSet #493

2 of 38 new or added lines in 3 files covered. (5.26%)

2 existing lines in 1 file now uncovered.

1603 of 4578 relevant lines covered (35.02%)

8.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.94
/src/types/array/array_key.rs
1
use crate::{convert::FromZval, error::Error, flags::DataType, types::Zval};
2
use std::str::FromStr;
3
use std::{convert::TryFrom, fmt::Display};
4

5
/// Represents the key of a PHP array, which can be either a long or a string.
6
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
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
        if let Ok(index) = i64::from_str(value.as_str()) {
×
21
            if value == "0" || !value.starts_with('0') {
×
22
                Self::Long(index)
×
23
            } else {
24
                Self::String(value)
×
25
            }
26
        } else {
27
            Self::String(value)
×
28
        }
29
    }
30
}
31

32
impl TryFrom<ArrayKey<'_>> for String {
33
    type Error = Error;
34

35
    fn try_from(value: ArrayKey<'_>) -> Result<Self, Self::Error> {
26✔
36
        match value {
26✔
37
            ArrayKey::String(s) => Ok(s),
32✔
38
            ArrayKey::Str(s) => Ok(s.to_string()),
2✔
39
            ArrayKey::Long(l) => Ok(l.to_string()),
18✔
40
        }
41
    }
42
}
43

44
impl TryFrom<ArrayKey<'_>> for i64 {
45
    type Error = Error;
46

47
    fn try_from(value: ArrayKey<'_>) -> Result<Self, Self::Error> {
18✔
48
        match value {
18✔
49
            ArrayKey::Long(i) => Ok(i),
22✔
50
            ArrayKey::String(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
15✔
51
            ArrayKey::Str(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
8✔
52
        }
53
    }
54
}
55

56
impl ArrayKey<'_> {
57
    /// Check if the key is an integer.
58
    ///
59
    /// # Returns
60
    ///
61
    /// Returns true if the key is an integer, false otherwise.
62
    #[must_use]
63
    pub fn is_long(&self) -> bool {
×
64
        match self {
×
65
            ArrayKey::Long(_) => true,
×
66
            ArrayKey::String(_) | ArrayKey::Str(_) => false,
×
67
        }
68
    }
69
}
70

71
impl Display for ArrayKey<'_> {
72
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
73
        match self {
×
74
            ArrayKey::Long(key) => write!(f, "{key}"),
×
75
            ArrayKey::String(key) => write!(f, "{key}"),
×
76
            ArrayKey::Str(key) => write!(f, "{key}"),
×
77
        }
78
    }
79
}
80

81
impl<'a> From<&'a str> for ArrayKey<'a> {
82
    fn from(value: &'a str) -> ArrayKey<'a> {
57✔
83
        if let Ok(index) = i64::from_str(value) {
70✔
84
            if value == "0" || !value.starts_with('0') {
25✔
85
                ArrayKey::Long(index)
11✔
86
            } else {
87
                ArrayKey::Str(value)
2✔
88
            }
89
        } else {
90
            ArrayKey::Str(value)
44✔
91
        }
92
    }
93
}
94

95
impl<'a> From<i32> for ArrayKey<'a> {
96
    fn from(index: i32) -> ArrayKey<'a> {
40✔
97
        ArrayKey::Long(i64::from(index))
40✔
98
    }
99
}
100

101
impl<'a> From<i64> for ArrayKey<'a> {
UNCOV
102
    fn from(index: i64) -> ArrayKey<'a> {
×
UNCOV
103
        ArrayKey::Long(index)
×
104
    }
105
}
106

107
impl<'a> From<u64> for ArrayKey<'a> {
NEW
108
    fn from(index: u64) -> ArrayKey<'a> {
×
NEW
109
        if let Ok(index) = i64::try_from(index) {
×
NEW
110
            ArrayKey::Long(index)
×
111
        } else {
NEW
112
            ArrayKey::String(index.to_string())
×
113
        }
114
    }
115
}
116

117
impl<'a> From<usize> for ArrayKey<'a> {
NEW
118
    fn from(index: usize) -> ArrayKey<'a> {
×
NEW
119
        if let Ok(index) = i64::try_from(index) {
×
NEW
120
            ArrayKey::Long(index)
×
121
        } else {
NEW
122
            ArrayKey::String(index.to_string())
×
123
        }
124
    }
125
}
126

127
impl<'a> FromZval<'a> for ArrayKey<'_> {
128
    const TYPE: DataType = DataType::String;
129

130
    fn from_zval(zval: &'a Zval) -> Option<Self> {
44✔
131
        if let Some(key) = zval.long() {
70✔
132
            return Some(ArrayKey::Long(key));
26✔
133
        }
134
        if let Some(key) = zval.string() {
36✔
135
            return Some(ArrayKey::String(key));
18✔
136
        }
137
        None
×
138
    }
139
}
140

141
#[cfg(test)]
142
#[cfg(feature = "embed")]
143
#[allow(clippy::unwrap_used)]
144
mod tests {
145
    use crate::error::Error;
146
    use crate::types::ArrayKey;
147

148
    #[test]
149
    fn test_string_try_from_array_key() {
150
        let key = ArrayKey::String("test".to_string());
151
        let result: crate::error::Result<String, _> = key.try_into();
152
        assert!(result.is_ok());
153
        assert_eq!(result.unwrap(), "test".to_string());
154

155
        let key = ArrayKey::Str("test");
156
        let result: crate::error::Result<String, _> = key.try_into();
157
        assert!(result.is_ok());
158
        assert_eq!(result.unwrap(), "test".to_string());
159

160
        let key = ArrayKey::Long(42);
161
        let result: crate::error::Result<String, _> = key.try_into();
162
        assert_eq!(result.unwrap(), "42".to_string());
163

164
        let key = ArrayKey::String("42".to_string());
165
        let result: crate::error::Result<String, _> = key.try_into();
166
        assert!(result.is_ok());
167
        assert_eq!(result.unwrap(), "42".to_string());
168

169
        let key = ArrayKey::Str("123");
170
        let result: crate::error::Result<i64, _> = key.try_into();
171
        assert!(result.is_ok());
172
        assert_eq!(result.unwrap(), 123);
173
    }
174

175
    #[test]
176
    fn test_i64_try_from_array_key() {
177
        let key = ArrayKey::Long(42);
178
        let result: crate::error::Result<i64, _> = key.try_into();
179
        assert!(result.is_ok());
180
        assert_eq!(result.unwrap(), 42);
181

182
        let key = ArrayKey::String("42".to_string());
183
        let result: crate::error::Result<i64, _> = key.try_into();
184
        assert!(result.is_ok());
185
        assert_eq!(result.unwrap(), 42);
186

187
        let key = ArrayKey::Str("123");
188
        let result: crate::error::Result<i64, _> = key.try_into();
189
        assert!(result.is_ok());
190
        assert_eq!(result.unwrap(), 123);
191

192
        let key = ArrayKey::String("not a number".to_string());
193
        let result: crate::error::Result<i64, _> = key.try_into();
194
        assert!(result.is_err());
195
        assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
196
    }
197

198
    #[test]
199
    fn test_from_str_with_leading_zeros() {
200
        let key: ArrayKey = "00".into();
201
        assert_eq!(key, ArrayKey::Str("00"));
202
        let key: ArrayKey = "071".into();
203
        assert_eq!(key, ArrayKey::Str("071"));
204
        let key: ArrayKey = "0".into();
205
        assert_eq!(key, ArrayKey::Long(0));
206
    }
207

208
    #[test]
209
    fn test_from_string_with_leading_zeros() {
210
        let key = ArrayKey::String("042".to_string());
211
        let result: crate::error::Result<String, _> = key.try_into();
212
        assert!(result.is_ok());
213
        assert_eq!(result.unwrap(), "042");
214
        let key = ArrayKey::String("00".to_string());
215
        let result: crate::error::Result<String, _> = key.try_into();
216
        assert!(result.is_ok());
217
        assert_eq!(result.unwrap(), "00");
218
        let key = ArrayKey::String("0".to_string());
219
        let result: crate::error::Result<i64, _> = key.try_into();
220
        assert!(result.is_ok());
221
        assert_eq!(result.unwrap(), 0);
222
    }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc