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

davidcole1340 / ext-php-rs / 18874866961

28 Oct 2025 12:32PM UTC coverage: 31.115% (-0.006%) from 31.121%
18874866961

Pull #567

github

web-flow
Merge c8f3e8f19 into 5f40f4cb1
Pull Request #567: Treat string keys formed "0[0-9]+" like php does.

2 of 6 new or added lines in 1 file covered. (33.33%)

1356 of 4358 relevant lines covered (31.12%)

8.5 hits per line

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

56.41
/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()) {
×
NEW
21
            if value == "0" || !value.starts_with('0') {
×
NEW
22
                Self::Long(index)
×
23
            } else {
NEW
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> {
24✔
36
        match value {
24✔
37
            ArrayKey::String(s) => Ok(s),
28✔
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> {
17✔
48
        match value {
17✔
49
            ArrayKey::Long(i) => Ok(i),
22✔
50
            ArrayKey::String(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
12✔
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> {
54✔
83
        if let Ok(index) = i64::from_str(value) {
64✔
84
            if value == "0" || !value.starts_with('0') {
20✔
85
                ArrayKey::Long(index)
10✔
86
            } else {
NEW
87
                ArrayKey::Str(value)
×
88
            }
89
        } else {
90
            ArrayKey::Str(value)
44✔
91
        }
92
    }
93
}
94

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

101
impl<'a> FromZval<'a> for ArrayKey<'_> {
102
    const TYPE: DataType = DataType::String;
103

104
    fn from_zval(zval: &'a Zval) -> Option<Self> {
44✔
105
        if let Some(key) = zval.long() {
70✔
106
            return Some(ArrayKey::Long(key));
26✔
107
        }
108
        if let Some(key) = zval.string() {
36✔
109
            return Some(ArrayKey::String(key));
18✔
110
        }
111
        None
×
112
    }
113
}
114

115
#[cfg(test)]
116
#[cfg(feature = "embed")]
117
#[allow(clippy::unwrap_used)]
118
mod tests {
119
    use crate::error::Error;
120
    use crate::types::ArrayKey;
121

122
    #[test]
123
    fn test_string_try_from_array_key() {
124
        let key = ArrayKey::String("test".to_string());
125
        let result: crate::error::Result<String, _> = key.try_into();
126
        assert!(result.is_ok());
127
        assert_eq!(result.unwrap(), "test".to_string());
128

129
        let key = ArrayKey::Str("test");
130
        let result: crate::error::Result<String, _> = key.try_into();
131
        assert!(result.is_ok());
132
        assert_eq!(result.unwrap(), "test".to_string());
133

134
        let key = ArrayKey::Long(42);
135
        let result: crate::error::Result<String, _> = key.try_into();
136
        assert_eq!(result.unwrap(), "42".to_string());
137

138
        let key = ArrayKey::String("42".to_string());
139
        let result: crate::error::Result<String, _> = key.try_into();
140
        assert!(result.is_ok());
141
        assert_eq!(result.unwrap(), "42".to_string());
142

143
        let key = ArrayKey::Str("123");
144
        let result: crate::error::Result<i64, _> = key.try_into();
145
        assert!(result.is_ok());
146
        assert_eq!(result.unwrap(), 123);
147
    }
148

149
    #[test]
150
    fn test_i64_try_from_array_key() {
151
        let key = ArrayKey::Long(42);
152
        let result: crate::error::Result<i64, _> = key.try_into();
153
        assert!(result.is_ok());
154
        assert_eq!(result.unwrap(), 42);
155

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

161
        let key = ArrayKey::Str("123");
162
        let result: crate::error::Result<i64, _> = key.try_into();
163
        assert!(result.is_ok());
164
        assert_eq!(result.unwrap(), 123);
165

166
        let key = ArrayKey::String("not a number".to_string());
167
        let result: crate::error::Result<i64, _> = key.try_into();
168
        assert!(result.is_err());
169
        assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
170
    }
171
}
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