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

extphprs / ext-php-rs / 21330140731

25 Jan 2026 09:08AM UTC coverage: 35.88% (+0.9%) from 34.976%
21330140731

Pull #611

github

web-flow
Merge 876e7a6d0 into 6013c2e62
Pull Request #611: feat(array): Entry API (Issue #525)

62 of 100 new or added lines in 2 files covered. (62.0%)

59 existing lines in 1 file now uncovered.

1907 of 5315 relevant lines covered (35.88%)

14.4 hits per line

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

60.71
/src/types/array/entry.rs
1
//! Entry API for [`ZendHashTable`], similar to Rust's `std::collections::hash_map::Entry`.
2
//!
3
//! This module provides an ergonomic API for working with entries in a PHP
4
//! hashtable, allowing conditional insertion or modification based on whether
5
//! a key already exists.
6
//!
7
//! # Examples
8
//!
9
//! ```no_run
10
//! use ext_php_rs::types::ZendHashTable;
11
//!
12
//! let mut ht = ZendHashTable::new();
13
//!
14
//! // Insert a default value if the key doesn't exist
15
//! ht.entry("counter").or_insert(0i64);
16
//!
17
//! // Modify the value if it exists
18
//! ht.entry("counter").and_modify(|v| {
19
//!     if let Some(n) = v.long() {
20
//!         v.set_long(n + 1);
21
//!     }
22
//! });
23
//!
24
//! // Or use or_insert_with for lazy initialization
25
//! ht.entry("computed").or_insert_with(|| "computed value");
26
//! ```
27

28
use std::ffi::CString;
29

30
use super::{ArrayKey, ZendHashTable};
31
use crate::{
32
    convert::IntoZval,
33
    error::{Error, Result},
34
    ffi::{
35
        zend_hash_index_find, zend_hash_index_update, zend_hash_str_find, zend_hash_str_update,
36
        zend_ulong,
37
    },
38
    types::Zval,
39
};
40

41
/// A view into a single entry in a [`ZendHashTable`], which may either be vacant or
42
/// occupied.
43
///
44
/// This enum is constructed from the [`entry`] method on [`ZendHashTable`].
45
///
46
/// [`entry`]: ZendHashTable::entry
47
pub enum Entry<'a, 'k> {
48
    /// An occupied entry.
49
    Occupied(OccupiedEntry<'a, 'k>),
50
    /// A vacant entry.
51
    Vacant(VacantEntry<'a, 'k>),
52
}
53

54
/// A view into an occupied entry in a [`ZendHashTable`].
55
///
56
/// It is part of the [`Entry`] enum.
57
pub struct OccupiedEntry<'a, 'k> {
58
    ht: &'a mut ZendHashTable,
59
    key: ArrayKey<'k>,
60
}
61

62
/// A view into a vacant entry in a [`ZendHashTable`].
63
///
64
/// It is part of the [`Entry`] enum.
65
pub struct VacantEntry<'a, 'k> {
66
    ht: &'a mut ZendHashTable,
67
    key: ArrayKey<'k>,
68
}
69

70
impl<'a, 'k> Entry<'a, 'k> {
71
    /// Ensures a value is in the entry by inserting the default if empty, and
72
    /// returns a mutable reference to the value in the entry.
73
    ///
74
    /// # Parameters
75
    ///
76
    /// * `default` - The default value to insert if the entry is vacant.
77
    ///
78
    /// # Returns
79
    ///
80
    /// A result containing a mutable reference to the value, or an error if
81
    /// the insertion failed.
82
    ///
83
    /// # Errors
84
    ///
85
    /// Returns an error if the value conversion to [`Zval`] fails or if
86
    /// the key contains a null byte (for string keys).
87
    ///
88
    /// # Example
89
    ///
90
    /// ```no_run
91
    /// use ext_php_rs::types::ZendHashTable;
92
    ///
93
    /// let mut ht = ZendHashTable::new();
94
    /// ht.entry("key").or_insert("default value");
95
    /// assert_eq!(ht.get("key").and_then(|v| v.str()), Some("default value"));
96
    /// ```
97
    pub fn or_insert<V: IntoZval>(self, default: V) -> Result<&'a mut Zval> {
5✔
98
        match self {
5✔
99
            Entry::Occupied(entry) => entry.into_mut(),
6✔
100
            Entry::Vacant(entry) => entry.insert(default),
12✔
101
        }
102
    }
103

104
    /// Ensures a value is in the entry by inserting the result of the default
105
    /// function if empty, and returns a mutable reference to the value in the
106
    /// entry.
107
    ///
108
    /// # Parameters
109
    ///
110
    /// * `default` - A function that returns the default value to insert.
111
    ///
112
    /// # Returns
113
    ///
114
    /// A result containing a mutable reference to the value, or an error if
115
    /// the insertion failed.
116
    ///
117
    /// # Errors
118
    ///
119
    /// Returns an error if the value conversion to [`Zval`] fails or if
120
    /// the key contains a null byte (for string keys).
121
    ///
122
    /// # Example
123
    ///
124
    /// ```no_run
125
    /// use ext_php_rs::types::ZendHashTable;
126
    ///
127
    /// let mut ht = ZendHashTable::new();
128
    /// ht.entry("key").or_insert_with(|| "computed value");
129
    /// ```
130
    pub fn or_insert_with<V: IntoZval, F: FnOnce() -> V>(self, default: F) -> Result<&'a mut Zval> {
1✔
131
        match self {
1✔
NEW
132
            Entry::Occupied(entry) => entry.into_mut(),
×
133
            Entry::Vacant(entry) => entry.insert(default()),
4✔
134
        }
135
    }
136

137
    /// Ensures a value is in the entry by inserting the result of the default
138
    /// function if empty. The function receives a reference to the key.
139
    ///
140
    /// # Parameters
141
    ///
142
    /// * `default` - A function that takes the key and returns the default value.
143
    ///
144
    /// # Returns
145
    ///
146
    /// A result containing a mutable reference to the value, or an error if
147
    /// the insertion failed.
148
    ///
149
    /// # Errors
150
    ///
151
    /// Returns an error if the value conversion to [`Zval`] fails or if
152
    /// the key contains a null byte (for string keys).
153
    ///
154
    /// # Example
155
    ///
156
    /// ```no_run
157
    /// use ext_php_rs::types::ZendHashTable;
158
    ///
159
    /// let mut ht = ZendHashTable::new();
160
    /// ht.entry("key").or_insert_with_key(|k| format!("value for {}", k));
161
    /// ```
NEW
162
    pub fn or_insert_with_key<V: IntoZval, F: FnOnce(&ArrayKey<'k>) -> V>(
×
163
        self,
164
        default: F,
165
    ) -> Result<&'a mut Zval> {
NEW
166
        match self {
×
NEW
167
            Entry::Occupied(entry) => entry.into_mut(),
×
NEW
168
            Entry::Vacant(entry) => {
×
NEW
169
                let value = default(entry.key());
×
NEW
170
                entry.insert(value)
×
171
            }
172
        }
173
    }
174

175
    /// Returns a reference to this entry's key.
176
    ///
177
    /// # Example
178
    ///
179
    /// ```no_run
180
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
181
    ///
182
    /// let mut ht = ZendHashTable::new();
183
    /// assert_eq!(ht.entry("key").key(), &ArrayKey::Str("key"));
184
    /// ```
185
    #[must_use]
NEW
186
    pub fn key(&self) -> &ArrayKey<'k> {
×
NEW
187
        match self {
×
NEW
188
            Entry::Occupied(entry) => entry.key(),
×
NEW
189
            Entry::Vacant(entry) => entry.key(),
×
190
        }
191
    }
192

193
    /// Provides in-place mutable access to an occupied entry before any
194
    /// potential inserts into the map.
195
    ///
196
    /// # Parameters
197
    ///
198
    /// * `f` - A function that modifies the value in place.
199
    ///
200
    /// # Returns
201
    ///
202
    /// The entry, allowing for method chaining.
203
    ///
204
    /// # Example
205
    ///
206
    /// ```no_run
207
    /// use ext_php_rs::types::ZendHashTable;
208
    ///
209
    /// let mut ht = ZendHashTable::new();
210
    /// ht.insert("counter", 0i64);
211
    ///
212
    /// ht.entry("counter")
213
    ///     .and_modify(|v| {
214
    ///         if let Some(n) = v.long() {
215
    ///             v.set_long(n + 1);
216
    ///         }
217
    ///     })
218
    ///     .or_insert(0i64);
219
    /// ```
220
    #[must_use]
221
    pub fn and_modify<F: FnOnce(&mut Zval)>(self, f: F) -> Self {
2✔
222
        match self {
2✔
223
            Entry::Occupied(mut entry) => {
1✔
224
                if let Ok(value) = entry.get_mut() {
3✔
225
                    f(value);
1✔
226
                }
227
                Entry::Occupied(entry)
1✔
228
            }
229
            Entry::Vacant(entry) => Entry::Vacant(entry),
2✔
230
        }
231
    }
232
}
233

234
impl<'a, 'k> Entry<'a, 'k>
235
where
236
    'k: 'a,
237
{
238
    /// Ensures a value is in the entry by inserting the default value if empty,
239
    /// and returns a mutable reference to the value in the entry.
240
    ///
241
    /// This is a convenience method that uses `Default::default()` as the
242
    /// default value.
243
    ///
244
    /// # Returns
245
    ///
246
    /// A result containing a mutable reference to the value, or an error if
247
    /// the insertion failed.
248
    ///
249
    /// # Errors
250
    ///
251
    /// Returns an error if the key contains a null byte (for string keys).
252
    ///
253
    /// # Example
254
    ///
255
    /// ```no_run
256
    /// use ext_php_rs::types::ZendHashTable;
257
    ///
258
    /// let mut ht = ZendHashTable::new();
259
    /// // Inserts a null Zval if the key doesn't exist
260
    /// ht.entry("key").or_default();
261
    /// ```
NEW
262
    pub fn or_default(self) -> Result<&'a mut Zval> {
×
NEW
263
        match self {
×
NEW
264
            Entry::Occupied(entry) => entry.into_mut(),
×
NEW
265
            Entry::Vacant(entry) => entry.insert(Zval::new()),
×
266
        }
267
    }
268
}
269

270
impl<'a, 'k> OccupiedEntry<'a, 'k> {
271
    /// Creates a new occupied entry.
272
    pub(super) fn new(ht: &'a mut ZendHashTable, key: ArrayKey<'k>) -> Self {
4✔
273
        Self { ht, key }
274
    }
275

276
    /// Gets a reference to the key in the entry.
277
    ///
278
    /// # Example
279
    ///
280
    /// ```no_run
281
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
282
    ///
283
    /// let mut ht = ZendHashTable::new();
284
    /// ht.insert("key", "value");
285
    ///
286
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
287
    ///     assert_eq!(entry.key(), &ArrayKey::Str("key"));
288
    /// }
289
    /// ```
290
    #[must_use]
NEW
291
    pub fn key(&self) -> &ArrayKey<'k> {
×
NEW
292
        &self.key
×
293
    }
294

295
    /// Gets a reference to the value in the entry.
296
    ///
297
    /// # Returns
298
    ///
299
    /// A result containing a reference to the value, or an error if
300
    /// the key conversion failed.
301
    ///
302
    /// # Errors
303
    ///
304
    /// Returns an error if the key contains a null byte (for string keys).
305
    ///
306
    /// # Example
307
    ///
308
    /// ```no_run
309
    /// use ext_php_rs::types::ZendHashTable;
310
    ///
311
    /// let mut ht = ZendHashTable::new();
312
    /// ht.insert("key", "value");
313
    ///
314
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
315
    ///     assert_eq!(entry.get().ok().and_then(|v| v.str()), Some("value"));
316
    /// }
317
    /// ```
318
    pub fn get(&self) -> Result<&Zval> {
2✔
319
        get_value(&self.key, self.ht).ok_or(Error::InvalidCString)
10✔
320
    }
321

322
    /// Gets a mutable reference to the value in the entry.
323
    ///
324
    /// # Returns
325
    ///
326
    /// A result containing a mutable reference to the value, or an error if
327
    /// the key conversion failed.
328
    ///
329
    /// # Errors
330
    ///
331
    /// Returns an error if the key contains a null byte (for string keys).
332
    ///
333
    /// # Example
334
    ///
335
    /// ```no_run
336
    /// use ext_php_rs::types::ZendHashTable;
337
    ///
338
    /// let mut ht = ZendHashTable::new();
339
    /// ht.insert("counter", 0i64);
340
    ///
341
    /// if let ext_php_rs::types::array::Entry::Occupied(mut entry) = ht.entry("counter") {
342
    ///     if let Ok(v) = entry.get_mut() {
343
    ///         if let Some(n) = v.long() {
344
    ///             v.set_long(n + 1);
345
    ///         }
346
    ///     }
347
    /// }
348
    /// ```
349
    pub fn get_mut(&mut self) -> Result<&mut Zval> {
1✔
350
        get_value_mut(&self.key, self.ht).ok_or(Error::InvalidCString)
5✔
351
    }
352

353
    /// Converts the entry into a mutable reference to the value.
354
    ///
355
    /// If you need multiple references to the `OccupiedEntry`, see [`get_mut`].
356
    ///
357
    /// [`get_mut`]: OccupiedEntry::get_mut
358
    ///
359
    /// # Returns
360
    ///
361
    /// A result containing a mutable reference to the value with the entry's
362
    /// lifetime, or an error if the key conversion failed.
363
    ///
364
    /// # Errors
365
    ///
366
    /// Returns an error if the key contains a null byte (for string keys).
367
    ///
368
    /// # Example
369
    ///
370
    /// ```no_run
371
    /// use ext_php_rs::types::ZendHashTable;
372
    ///
373
    /// let mut ht = ZendHashTable::new();
374
    /// ht.insert("key", "value");
375
    ///
376
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
377
    ///     let value = entry.into_mut().unwrap();
378
    ///     // value has the lifetime of the hashtable borrow
379
    /// }
380
    /// ```
381
    pub fn into_mut(self) -> Result<&'a mut Zval> {
2✔
382
        get_value_mut(&self.key, self.ht).ok_or(Error::InvalidCString)
10✔
383
    }
384

385
    /// Sets the value of the entry, returning the old value.
386
    ///
387
    /// # Parameters
388
    ///
389
    /// * `value` - The new value to set.
390
    ///
391
    /// # Returns
392
    ///
393
    /// A result containing the old value (shallow cloned), or an error if the
394
    /// insertion failed.
395
    ///
396
    /// # Errors
397
    ///
398
    /// Returns an error if the value conversion to [`Zval`] fails or if
399
    /// the key contains a null byte (for string keys).
400
    ///
401
    /// # Example
402
    ///
403
    /// ```no_run
404
    /// use ext_php_rs::types::ZendHashTable;
405
    ///
406
    /// let mut ht = ZendHashTable::new();
407
    /// ht.insert("key", "old");
408
    ///
409
    /// if let ext_php_rs::types::array::Entry::Occupied(mut entry) = ht.entry("key") {
410
    ///     let old = entry.insert("new").unwrap();
411
    ///     assert_eq!(old.str(), Some("old"));
412
    /// }
413
    /// ```
414
    pub fn insert<V: IntoZval>(&mut self, value: V) -> Result<Zval> {
1✔
415
        let old = self.get()?.shallow_clone();
4✔
416
        insert_value(&self.key, self.ht, value)?;
4✔
417
        Ok(old)
1✔
418
    }
419

420
    /// Takes ownership of the key and value from the entry, removing it from
421
    /// the hashtable.
422
    ///
423
    /// # Returns
424
    ///
425
    /// A result containing a tuple of the key and the removed value (shallow
426
    /// cloned), or an error if the removal failed.
427
    ///
428
    /// # Errors
429
    ///
430
    /// Returns an error if the key contains a null byte (for string keys).
431
    ///
432
    /// # Example
433
    ///
434
    /// ```no_run
435
    /// use ext_php_rs::types::ZendHashTable;
436
    ///
437
    /// let mut ht = ZendHashTable::new();
438
    /// ht.insert("key", "value");
439
    ///
440
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
441
    ///     let (key, value) = entry.remove_entry().unwrap();
442
    ///     assert!(ht.get("key").is_none());
443
    /// }
444
    /// ```
NEW
445
    pub fn remove_entry(self) -> Result<(ArrayKey<'k>, Zval)> {
×
NEW
446
        let value = self.get()?.shallow_clone();
×
NEW
447
        self.ht.remove(self.key.clone());
×
NEW
448
        Ok((self.key, value))
×
449
    }
450

451
    /// Removes the value from the entry, returning it.
452
    ///
453
    /// # Returns
454
    ///
455
    /// A result containing the removed value (shallow cloned), or an error if
456
    /// the removal failed.
457
    ///
458
    /// # Errors
459
    ///
460
    /// Returns an error if the key contains a null byte (for string keys).
461
    ///
462
    /// # Example
463
    ///
464
    /// ```no_run
465
    /// use ext_php_rs::types::ZendHashTable;
466
    ///
467
    /// let mut ht = ZendHashTable::new();
468
    /// ht.insert("key", "value");
469
    ///
470
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
471
    ///     let value = entry.remove().unwrap();
472
    ///     assert_eq!(value.str(), Some("value"));
473
    /// }
474
    /// ```
475
    pub fn remove(self) -> Result<Zval> {
1✔
476
        let value = self.get()?.shallow_clone();
4✔
477
        self.ht.remove(self.key);
3✔
478
        Ok(value)
1✔
479
    }
480
}
481

482
impl<'a, 'k> VacantEntry<'a, 'k> {
483
    /// Creates a new vacant entry.
484
    pub(super) fn new(ht: &'a mut ZendHashTable, key: ArrayKey<'k>) -> Self {
5✔
485
        Self { ht, key }
486
    }
487

488
    /// Gets a reference to the key that would be used when inserting a value
489
    /// through the `VacantEntry`.
490
    ///
491
    /// # Example
492
    ///
493
    /// ```no_run
494
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
495
    ///
496
    /// let mut ht = ZendHashTable::new();
497
    ///
498
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
499
    ///     assert_eq!(entry.key(), &ArrayKey::Str("key"));
500
    /// }
501
    /// ```
502
    #[must_use]
NEW
503
    pub fn key(&self) -> &ArrayKey<'k> {
×
NEW
504
        &self.key
×
505
    }
506

507
    /// Take ownership of the key.
508
    ///
509
    /// # Example
510
    ///
511
    /// ```no_run
512
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
513
    ///
514
    /// let mut ht = ZendHashTable::new();
515
    ///
516
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
517
    ///     let key = entry.into_key();
518
    ///     assert_eq!(key, ArrayKey::Str("key"));
519
    /// }
520
    /// ```
521
    #[must_use]
522
    pub fn into_key(self) -> ArrayKey<'k> {
1✔
523
        self.key
1✔
524
    }
525

526
    /// Sets the value of the entry with the `VacantEntry`'s key, and returns
527
    /// a mutable reference to it.
528
    ///
529
    /// # Parameters
530
    ///
531
    /// * `value` - The value to insert.
532
    ///
533
    /// # Returns
534
    ///
535
    /// A result containing a mutable reference to the inserted value, or an
536
    /// error if the insertion failed.
537
    ///
538
    /// # Errors
539
    ///
540
    /// Returns an error if the value conversion to [`Zval`] fails or if
541
    /// the key contains a null byte (for string keys).
542
    ///
543
    /// # Example
544
    ///
545
    /// ```no_run
546
    /// use ext_php_rs::types::ZendHashTable;
547
    ///
548
    /// let mut ht = ZendHashTable::new();
549
    ///
550
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
551
    ///     entry.insert("value");
552
    /// }
553
    /// assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
554
    /// ```
555
    pub fn insert<V: IntoZval>(self, value: V) -> Result<&'a mut Zval> {
4✔
556
        insert_value(&self.key, self.ht, value)?;
16✔
557
        get_value_mut(&self.key, self.ht).ok_or(Error::InvalidCString)
20✔
558
    }
559
}
560

561
/// Helper function to get a value from the hashtable by key.
562
fn get_value<'a>(key: &ArrayKey<'_>, ht: &'a ZendHashTable) -> Option<&'a Zval> {
2✔
563
    match key {
2✔
564
        ArrayKey::Long(index) => unsafe {
565
            #[allow(clippy::cast_sign_loss)]
NEW
566
            zend_hash_index_find(ht, *index as zend_ulong).as_ref()
×
567
        },
568
        ArrayKey::String(key) => unsafe {
NEW
569
            zend_hash_str_find(ht, CString::new(key.as_str()).ok()?.as_ptr(), key.len()).as_ref()
×
570
        },
571
        ArrayKey::Str(key) => unsafe {
572
            zend_hash_str_find(ht, CString::new(*key).ok()?.as_ptr(), key.len()).as_ref()
18✔
573
        },
574
    }
575
}
576

577
/// Helper function to get a mutable value from the hashtable by key.
578
fn get_value_mut<'a>(key: &ArrayKey<'_>, ht: &'a mut ZendHashTable) -> Option<&'a mut Zval> {
7✔
579
    match key {
7✔
580
        ArrayKey::Long(index) => unsafe {
581
            #[allow(clippy::cast_sign_loss)]
582
            zend_hash_index_find(ht, *index as zend_ulong).as_mut()
4✔
583
        },
584
        ArrayKey::String(key) => unsafe {
585
            zend_hash_str_find(
NEW
586
                ht,
×
NEW
587
                CString::new(key.as_str()).ok()?.as_ptr(),
×
NEW
588
                key.len() as _,
×
589
            )
590
            .as_mut()
591
        },
592
        ArrayKey::Str(key) => unsafe {
593
            zend_hash_str_find(ht, CString::new(*key).ok()?.as_ptr(), key.len() as _).as_mut()
54✔
594
        },
595
    }
596
}
597

598
/// Helper function to insert a value into the hashtable by key.
599
fn insert_value<V: IntoZval>(key: &ArrayKey<'_>, ht: &mut ZendHashTable, value: V) -> Result<()> {
5✔
600
    let mut val = value.into_zval(false)?;
15✔
601
    match key {
5✔
602
        ArrayKey::Long(index) => {
2✔
603
            unsafe {
604
                #[allow(clippy::cast_sign_loss)]
605
                zend_hash_index_update(ht, *index as zend_ulong, &raw mut val)
3✔
606
            };
607
        }
NEW
608
        ArrayKey::String(key) => {
×
609
            unsafe {
610
                zend_hash_str_update(
NEW
611
                    ht,
×
NEW
612
                    CString::new(key.as_str())?.as_ptr(),
×
NEW
613
                    key.len(),
×
NEW
614
                    &raw mut val,
×
615
                )
616
            };
617
        }
618
        ArrayKey::Str(key) => {
4✔
619
            unsafe {
620
                zend_hash_str_update(ht, CString::new(*key)?.as_ptr(), key.len(), &raw mut val)
32✔
621
            };
622
        }
623
    }
624
    val.release();
10✔
625
    Ok(())
5✔
626
}
627

628
#[cfg(test)]
629
#[cfg(feature = "embed")]
630
mod tests {
631
    use super::*;
632
    use crate::embed::Embed;
633

634
    #[test]
635
    fn test_entry_or_insert() {
636
        Embed::run(|| {
637
            let mut ht = ZendHashTable::new();
638

639
            // Insert into vacant entry
640
            let result = ht.entry("key").or_insert("value");
641
            assert!(result.is_ok());
642
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
643

644
            // Entry already exists, should return existing value
645
            let result = ht.entry("key").or_insert("other");
646
            assert!(result.is_ok());
647
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
648
        });
649
    }
650

651
    #[test]
652
    fn test_entry_or_insert_with() {
653
        Embed::run(|| {
654
            let mut ht = ZendHashTable::new();
655

656
            let result = ht.entry("key").or_insert_with(|| "computed");
657
            assert!(result.is_ok());
658
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("computed"));
659
        });
660
    }
661

662
    #[test]
663
    fn test_entry_and_modify() {
664
        Embed::run(|| {
665
            let mut ht = ZendHashTable::new();
666
            let _ = ht.insert("counter", 5i64);
667

668
            let result = ht
669
                .entry("counter")
670
                .and_modify(|v| {
671
                    if let Some(n) = v.long() {
672
                        v.set_long(n + 1);
673
                    }
674
                })
675
                .or_insert(0i64);
676

677
            assert!(result.is_ok());
678
            assert_eq!(ht.get("counter").and_then(Zval::long), Some(6));
679
        });
680
    }
681

682
    #[test]
683
    fn test_entry_and_modify_vacant() {
684
        Embed::run(|| {
685
            let mut ht = ZendHashTable::new();
686

687
            // and_modify on vacant entry should be a no-op
688
            let result = ht
689
                .entry("key")
690
                .and_modify(|v| {
691
                    v.set_long(100);
692
                })
693
                .or_insert(42i64);
694

695
            assert!(result.is_ok());
696
            assert_eq!(ht.get("key").and_then(Zval::long), Some(42));
697
        });
698
    }
699

700
    #[test]
701
    fn test_occupied_entry_insert() {
702
        Embed::run(|| {
703
            let mut ht = ZendHashTable::new();
704
            let _ = ht.insert("key", "old");
705

706
            if let Entry::Occupied(mut entry) = ht.entry("key") {
707
                let old = entry.insert("new").expect("insert should succeed");
708
                assert_eq!(old.str(), Some("old"));
709
            }
710
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("new"));
711
        });
712
    }
713

714
    #[test]
715
    fn test_occupied_entry_remove() {
716
        Embed::run(|| {
717
            let mut ht = ZendHashTable::new();
718
            let _ = ht.insert("key", "value");
719

720
            if let Entry::Occupied(entry) = ht.entry("key") {
721
                let value = entry.remove().expect("remove should succeed");
722
                assert_eq!(value.str(), Some("value"));
723
            }
724
            assert!(ht.get("key").is_none());
725
        });
726
    }
727

728
    #[test]
729
    fn test_entry_with_numeric_key() {
730
        Embed::run(|| {
731
            let mut ht = ZendHashTable::new();
732

733
            let result = ht.entry(42i64).or_insert("value");
734
            assert!(result.is_ok());
735
            assert_eq!(ht.get_index(42).and_then(|v| v.str()), Some("value"));
736
        });
737
    }
738

739
    #[test]
740
    fn test_vacant_entry_into_key() {
741
        Embed::run(|| {
742
            let mut ht = ZendHashTable::new();
743

744
            if let Entry::Vacant(entry) = ht.entry("my_key") {
745
                let key = entry.into_key();
746
                assert_eq!(key, ArrayKey::Str("my_key"));
747
            }
748
        });
749
    }
750
}
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