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

extphprs / ext-php-rs / 23718488839

29 Mar 2026 08:35PM UTC coverage: 65.13% (+31.0%) from 34.103%
23718488839

push

github

web-flow
ci(coverage): switch from tarpaulin to cargo-llvm-cov (#702)

7811 of 11993 relevant lines covered (65.13%)

32.6 hits per line

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

85.37
/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, Hash)]
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 {
3✔
20
        if let Ok(index) = i64::from_str(value.as_str()) {
3✔
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)
3✔
28
        }
29
    }
3✔
30
}
31

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

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

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

47
    fn try_from(value: ArrayKey<'_>) -> Result<Self, Self::Error> {
30✔
48
        match value {
30✔
49
            ArrayKey::Long(i) => Ok(i),
21✔
50
            ArrayKey::String(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
7✔
51
            ArrayKey::Str(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
2✔
52
        }
53
    }
30✔
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 {
3✔
73
        match self {
3✔
74
            ArrayKey::Long(key) => write!(f, "{key}"),
×
75
            ArrayKey::String(key) => write!(f, "{key}"),
3✔
76
            ArrayKey::Str(key) => write!(f, "{key}"),
×
77
        }
78
    }
3✔
79
}
80

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

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

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

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

117
impl<'a> From<usize> for ArrayKey<'a> {
118
    fn from(index: usize) -> ArrayKey<'a> {
11✔
119
        if let Ok(index) = i64::try_from(index) {
11✔
120
            ArrayKey::Long(index)
11✔
121
        } else {
122
            ArrayKey::String(index.to_string())
×
123
        }
124
    }
11✔
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> {
128✔
131
        if let Some(key) = zval.long() {
128✔
132
            return Some(ArrayKey::Long(key));
79✔
133
        }
49✔
134
        if let Some(key) = zval.string() {
49✔
135
            return Some(ArrayKey::String(key));
49✔
136
        }
×
137
        None
×
138
    }
128✔
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() {
1✔
150
        let key = ArrayKey::String("test".to_string());
1✔
151
        let result: crate::error::Result<String, _> = key.try_into();
1✔
152
        assert!(result.is_ok());
1✔
153
        assert_eq!(result.unwrap(), "test".to_string());
1✔
154

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

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

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

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

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

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

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

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

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

208
    #[test]
209
    fn test_from_string_with_leading_zeros() {
1✔
210
        let key = ArrayKey::String("042".to_string());
1✔
211
        let result: crate::error::Result<String, _> = key.try_into();
1✔
212
        assert!(result.is_ok());
1✔
213
        assert_eq!(result.unwrap(), "042");
1✔
214
        let key = ArrayKey::String("00".to_string());
1✔
215
        let result: crate::error::Result<String, _> = key.try_into();
1✔
216
        assert!(result.is_ok());
1✔
217
        assert_eq!(result.unwrap(), "00");
1✔
218
        let key = ArrayKey::String("0".to_string());
1✔
219
        let result: crate::error::Result<i64, _> = key.try_into();
1✔
220
        assert!(result.is_ok());
1✔
221
        assert_eq!(result.unwrap(), 0);
1✔
222
    }
1✔
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