Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,630 changes: 0 additions & 1,630 deletions src/types/array.rs

This file was deleted.

156 changes: 156 additions & 0 deletions src/types/array/array_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use std::{convert::TryFrom, fmt::Display};

use crate::{convert::FromZval, error::Error, flags::DataType, types::Zval};

/// Represents the key of a PHP array, which can be either a long or a string.
#[derive(Debug, Clone, PartialEq)]
pub enum ArrayKey<'a> {
/// A numerical key.
/// In Zend API it's represented by `u64` (`zend_ulong`), so the value needs
/// to be cast to `zend_ulong` before passing into Zend functions.
Long(i64),
/// A string key.
String(String),
/// A string key by reference.
Str(&'a str),
}

impl From<String> for ArrayKey<'_> {
fn from(value: String) -> Self {
Self::String(value)
}
}

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

fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
match value {
ArrayKey::String(s) => Ok(s),
ArrayKey::Str(s) => Ok(s.to_string()),
ArrayKey::Long(_) => Err(Error::InvalidProperty),
}
}
}

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

fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
match value {
ArrayKey::Long(i) => Ok(i),
ArrayKey::String(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
ArrayKey::Str(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
}
}
}

impl ArrayKey<'_> {
/// Check if the key is an integer.
///
/// # Returns
///
/// Returns true if the key is an integer, false otherwise.
#[must_use]
pub fn is_long(&self) -> bool {
match self {
ArrayKey::Long(_) => true,
ArrayKey::String(_) | ArrayKey::Str(_) => false,
}
}
}

impl Display for ArrayKey<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArrayKey::Long(key) => write!(f, "{key}"),
ArrayKey::String(key) => write!(f, "{key}"),
ArrayKey::Str(key) => write!(f, "{key}"),
}
}
}

impl<'a> From<&'a str> for ArrayKey<'a> {
fn from(key: &'a str) -> ArrayKey<'a> {
ArrayKey::Str(key)
}
}

impl<'a> From<i64> for ArrayKey<'a> {
fn from(index: i64) -> ArrayKey<'a> {
ArrayKey::Long(index)
}
}

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

fn from_zval(zval: &'a Zval) -> Option<Self> {
if let Some(key) = zval.long() {
return Some(ArrayKey::Long(key));
}
if let Some(key) = zval.string() {
return Some(ArrayKey::String(key));
}
None
}
}

#[cfg(test)]
#[cfg(feature = "embed")]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::error::Error;
use crate::types::ArrayKey;

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

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

let key = ArrayKey::Long(42);
let result: crate::error::Result<String, _> = key.try_into();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidProperty));

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

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

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

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

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

let key = ArrayKey::String("not a number".to_string());
let result: crate::error::Result<i64, _> = key.try_into();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
}
}
83 changes: 83 additions & 0 deletions src/types/array/conversions/hash_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use std::{collections::HashMap, convert::TryFrom};

use crate::{
boxed::ZBox,
convert::{FromZval, IntoZval},
error::{Error, Result},
flags::DataType,
types::Zval,
};

use super::super::ZendHashTable;

// TODO: Generalize hasher
#[allow(clippy::implicit_hasher)]
impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
where
V: FromZval<'a>,
{
type Error = Error;

fn try_from(value: &'a ZendHashTable) -> Result<Self> {
let mut hm = HashMap::with_capacity(value.len());

for (key, val) in value {
hm.insert(
key.to_string(),
V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
);
}

Ok(hm)
}
}

impl<K, V> TryFrom<HashMap<K, V>> for ZBox<ZendHashTable>
where
K: AsRef<str>,
V: IntoZval,
{
type Error = Error;

fn try_from(value: HashMap<K, V>) -> Result<Self> {
let mut ht = ZendHashTable::with_capacity(
value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
);

for (k, v) in value {
ht.insert(k.as_ref(), v)?;
}

Ok(ht)
}
}

// TODO: Generalize hasher
#[allow(clippy::implicit_hasher)]
impl<K, V> IntoZval for HashMap<K, V>
where
K: AsRef<str>,
V: IntoZval,
{
const TYPE: DataType = DataType::Array;
const NULLABLE: bool = false;

fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
let arr = self.try_into()?;
zv.set_hashtable(arr);
Ok(())
}
}

// TODO: Generalize hasher
#[allow(clippy::implicit_hasher)]
impl<'a, T> FromZval<'a> for HashMap<String, T>
where
T: FromZval<'a>,
{
const TYPE: DataType = DataType::Array;

fn from_zval(zval: &'a Zval) -> Option<Self> {
zval.array().and_then(|arr| arr.try_into().ok())
}
}
13 changes: 13 additions & 0 deletions src/types/array/conversions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Collection type conversions for `ZendHashTable`.
//!
//! This module provides conversions between Rust collection types and PHP arrays
//! (represented as `ZendHashTable`). Each collection type has its own module for
//! better organization and maintainability.
//!
//! ## Supported Collections
//!
//! - `HashMap<K, V>` ↔ `ZendHashTable` (via `hash_map` module)
//! - `Vec<T>` and `Vec<(K, V)>` ↔ `ZendHashTable` (via `vec` module)

mod hash_map;
mod vec;
Loading
Loading