diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 034601720..0c693f3a1 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -523,7 +523,8 @@ mod tests { assert_eq!( r["files"][0]["covered"] .as_array() - .map_err(crate::error_to_jsvalue)?, + .map_err(crate::error_to_jsvalue)? + .as_slice(), &vec![regorus::Value::from(3)] ); diff --git a/docs/value/array.md b/docs/value/array.md new file mode 100644 index 000000000..92a39022f --- /dev/null +++ b/docs/value/array.md @@ -0,0 +1,33 @@ +# Array + +Opaque container for `Value::Array`'s ordered element storage, enabling +alternative backends without call-site changes. It follows the same +abstraction pattern as [`Object`](object.md) and [`Set`](set.md). + +## Design + +`Array` wraps a `Vec` today but exposes only a curated method surface +(`get`, `get_mut`, `first`, `last`, `contains`, `iter`, `iter_mut`, `push`, +`append`, `extend`, `extend_from_slice`, `retain`, `clear`, `reverse`, `sort`, +`cursor`, and serde). The inner vector is private, and `Array` does not +implement `Deref`, so callers cannot depend on the backing representation. + +Indexing uses `Index` and returns `Value::Undefined` for an out-of-range +index, matching `Value` indexing semantics. Use `get` when distinguishing a +missing element from an element whose value is explicitly `Undefined`. + +Iteration follows sequence order. The opaque cursor supports incremental +traversal needed by RVM iteration state without exposing iterator internals. +`Ord` is implemented against the sequence iterator so alternative backends can +preserve the current array comparison behavior. + +## Scenarios enabled + +- **Inline-small storage** — store short arrays inline and spill to the heap + only for larger values. +- **Lazy/streaming storage** — materialize elements from JSON, CBOR, or a host + provider on demand. +- **Arena allocation** — use bump allocation for evaluation-time temporaries + and release them together at query end. +- **FFI-backed storage** — access host-language lists or arrays without + copying at every binding boundary. diff --git a/src/builtins/arrays.rs b/src/builtins/arrays.rs index ceb46607c..3245f2c87 100644 --- a/src/builtins/arrays.rs +++ b/src/builtins/arrays.rs @@ -65,6 +65,6 @@ fn slice(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Re return Ok(Value::new_array()); } - let slice = &array[start..stop]; + let slice = array.as_slice().get(start..stop).unwrap_or_default(); Ok(Value::from(slice.to_vec())) } diff --git a/src/builtins/azure_policy/template_functions_collection.rs b/src/builtins/azure_policy/template_functions_collection.rs index 1f774c544..974063264 100644 --- a/src/builtins/azure_policy/template_functions_collection.rs +++ b/src/builtins/azure_policy/template_functions_collection.rs @@ -59,7 +59,7 @@ fn fn_intersection( match *first { Value::Array(ref first) => { // Intersection of arrays: keep elements from first that appear in all others. - let mut result: Vec = first.as_ref().clone(); + let mut result: Vec = first.as_slice().to_vec(); for arg in rest { let Value::Array(ref other) = *arg else { return Ok(Value::Undefined); @@ -149,7 +149,9 @@ fn fn_take(_span: &Span, _params: &[Ref], args: &[Value], _strict: bool) - match *original { Value::Array(ref arr) => { let n = count.min(arr.len()); - Ok(Value::from(arr.get(..n).unwrap_or_default().to_vec())) + Ok(Value::from( + arr.as_slice().get(..n).unwrap_or_default().to_vec(), + )) } Value::String(ref s) => { let taken: alloc::string::String = s.chars().take(count).collect(); @@ -172,7 +174,9 @@ fn fn_skip(_span: &Span, _params: &[Ref], args: &[Value], _strict: bool) - match *original { Value::Array(ref arr) => { let n = count.min(arr.len()); - Ok(Value::from(arr.get(n..).unwrap_or_default().to_vec())) + Ok(Value::from( + arr.as_slice().get(n..).unwrap_or_default().to_vec(), + )) } Value::String(ref s) => { let skipped: alloc::string::String = s.chars().skip(count).collect(); diff --git a/src/builtins/azure_policy/template_functions_misc.rs b/src/builtins/azure_policy/template_functions_misc.rs index 1fbcacb65..dea7e9c19 100644 --- a/src/builtins/azure_policy/template_functions_misc.rs +++ b/src/builtins/azure_policy/template_functions_misc.rs @@ -90,7 +90,7 @@ fn fn_items(_span: &Span, _params: &[Ref], args: &[Value], _strict: bool) entry.insert(Value::from("value"), v.clone()); result.push(Value::Object(Rc::new(entry))); } - Ok(Value::Array(Rc::new(result))) + Ok(Value::from_array(result)) } // ── indexFromEnd ────────────────────────────────────────────────────── diff --git a/src/builtins/json_patch.rs b/src/builtins/json_patch.rs index 3ab71b3d7..fba53bcd3 100644 --- a/src/builtins/json_patch.rs +++ b/src/builtins/json_patch.rs @@ -79,7 +79,7 @@ impl EditNode { array.push(value.render()?); enforce_limit()?; } - Value::Array(crate::Rc::new(array)) + Value::Array(crate::Rc::new(crate::value::Array::from(array))) } Self::Set(members) => { let mut set = BTreeSet::new(); diff --git a/src/builtins/net.rs b/src/builtins/net.rs index 7c3d17ce5..97212d828 100644 --- a/src/builtins/net.rs +++ b/src/builtins/net.rs @@ -153,7 +153,7 @@ fn _cidr_expand(cidr: Arc) -> Result { enforce_limit()?; } - Ok(Value::Array(Arc::from(hosts))) + Ok(Value::from_array(hosts)) } #[cfg(test)] diff --git a/src/builtins/objects.rs b/src/builtins/objects.rs index a52884c23..9d8a844c3 100644 --- a/src/builtins/objects.rs +++ b/src/builtins/objects.rs @@ -354,7 +354,10 @@ fn is_subset(sup: &Value, sub: &Value) -> bool { }) } (Value::Set(sup), Value::Set(sub)) => sub.is_subset(sup), - (Value::Array(sup), Value::Array(sub)) => sup.windows(sub.len()).any(|w| w == &sub[..]), + (Value::Array(sup), Value::Array(sub)) => sup + .as_slice() + .windows(sub.len()) + .any(|w| w == sub.as_slice()), (Value::Array(sup), Value::Set(_)) => { let sup = Value::from_set(sup.iter().cloned().collect()); is_subset(&sup, sub) @@ -504,7 +507,7 @@ fn json_patch(span: &Span, params: &[Ref], args: &[Value], _strict: bool) let ops = args[1].as_array()?; - let patched = super::json_patch::apply(&args[0], ops); + let patched = super::json_patch::apply(&args[0], ops.as_slice()); match patched { Ok(patched) => Ok(patched), // Resource-limit errors must propagate rather than look like an diff --git a/src/builtins/utils.rs b/src/builtins/utils.rs index bec25fdb7..b790e8b30 100644 --- a/src/builtins/utils.rs +++ b/src/builtins/utils.rs @@ -5,7 +5,7 @@ use crate::ast::{Expr, Ref}; use crate::lexer::Span; use crate::number::Number; -use crate::value::{Object, Set}; +use crate::value::{Array, Object, Set}; use crate::Rc; use crate::Value; use crate::*; @@ -147,7 +147,7 @@ pub fn ensure_string_collection<'a>(fcn: &str, arg: &Expr, v: &'a Value) -> Resu Ok(collection) } -pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result>> { +pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result> { Ok(match v { Value::Array(a) => a, _ => { diff --git a/src/interpreter.rs b/src/interpreter.rs index fc0294891..a4b8d565c 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3918,7 +3918,7 @@ impl Interpreter { if value != Value::Undefined { for (path, value_in_map) in value.as_object()? { let mut full_path = package_components.clone(); - full_path.append(&mut path.as_array()?.clone()); + full_path.append(&mut path.as_array()?.to_vec()); self.check_rule_path(refr, &full_path, value_in_map, is_set)?; self.update_rule_value( span, diff --git a/src/languages/azure_policy/aliases/obj_map.rs b/src/languages/azure_policy/aliases/obj_map.rs index 3598cd249..f86a1d9d7 100644 --- a/src/languages/azure_policy/aliases/obj_map.rs +++ b/src/languages/azure_policy/aliases/obj_map.rs @@ -93,7 +93,7 @@ pub fn make_value(map: ObjMap) -> Value { /// Convert a `Vec` into a `Value::Array`. pub fn make_array(items: Vec) -> Value { - Value::Array(Rc::new(items)) + Value::from_array(items) } /// Extract a `&str` from a `Value::String`. diff --git a/src/languages/azure_rbac/builtins/lists.rs b/src/languages/azure_rbac/builtins/lists.rs index 78b8c586b..e44db009a 100644 --- a/src/languages/azure_rbac/builtins/lists.rs +++ b/src/languages/azure_rbac/builtins/lists.rs @@ -9,7 +9,7 @@ use super::evaluator::RbacBuiltinError; pub(super) fn list_contains(left: &Value, right: &Value) -> Result { match *left { // Lists and sets share containment semantics. - Value::Array(ref list) => Ok(list_contains_values(list, right)), + Value::Array(ref list) => Ok(list_contains_values(list.as_slice(), right)), Value::Set(ref set) => Ok(set_contains_values(set, right)), _ => Ok(false), } diff --git a/src/languages/rego/compiler/expressions/collection_literals.rs b/src/languages/rego/compiler/expressions/collection_literals.rs index fdb37510e..ad6aaeeeb 100644 --- a/src/languages/rego/compiler/expressions/collection_literals.rs +++ b/src/languages/rego/compiler/expressions/collection_literals.rs @@ -35,7 +35,7 @@ pub(in crate::languages::rego::compiler) fn try_eval_const(expr: &Expr) -> Optio .iter() .map(|i| try_eval_const(i.as_ref())) .collect::>>() - .map(|v| Value::Array(Rc::new(v))), + .map(Value::from_array), Expr::Set { items, .. } => items .iter() .map(|i| try_eval_const(i.as_ref())) @@ -59,7 +59,7 @@ impl<'a> Compiler<'a> { let all_const: Option> = items.iter().map(|i| try_eval_const(i.as_ref())).collect(); if let Some(values) = all_const { let dest = self.alloc_register(); - let literal_idx = self.add_literal(Value::Array(Rc::new(values))); + let literal_idx = self.add_literal(Value::from_array(values)); self.emit_instruction(Instruction::Load { dest, literal_idx }, span); return Ok(dest); } diff --git a/src/rvm/program/metadata.rs b/src/rvm/program/metadata.rs index 16d43ed50..df15630fd 100644 --- a/src/rvm/program/metadata.rs +++ b/src/rvm/program/metadata.rs @@ -208,7 +208,7 @@ impl MetadataValue { MetadataValue::Integer(n) => Value::from(n), MetadataValue::List(ref list) => { let values: Vec = list.iter().map(MetadataValue::to_value).collect(); - Value::Array(Rc::new(values)) + Value::from_array(values) } MetadataValue::Map(ref map) => { let mut obj = Object::new(); diff --git a/src/rvm/vm/context.rs b/src/rvm/vm/context.rs index b68e09701..2db4b35d3 100644 --- a/src/rvm/vm/context.rs +++ b/src/rvm/vm/context.rs @@ -2,10 +2,8 @@ // Licensed under the MIT License. use crate::rvm::instructions::{ComprehensionMode, LoopMode}; -use crate::value::Value; -use crate::value::{Object, ObjectCursor, Set, SetCursor}; +use crate::value::{Array, Object, ObjectCursor, Set, SetCursor}; use crate::Rc; -use alloc::vec::Vec; /// Loop execution context for managing iteration state #[derive(Debug, Clone)] @@ -36,7 +34,7 @@ pub struct LoopContext { #[derive(Debug, Clone)] pub enum IterationState { Array { - items: Rc>, + items: Rc, index: usize, }, Object { @@ -134,7 +132,8 @@ pub(super) struct ComprehensionContext { )] mod tests { use super::*; - use crate::value::Object; + use crate::value::{Object, Value}; + use alloc::vec::Vec; /// IterationState::Object holds an `Rc` plus an opaque cursor. /// Mutating an aliased Rc via `Rc::make_mut` allocates a new collection diff --git a/src/rvm/vm/dispatch.rs b/src/rvm/vm/dispatch.rs index 5b91191fe..bff576d48 100644 --- a/src/rvm/vm/dispatch.rs +++ b/src/rvm/vm/dispatch.rs @@ -583,7 +583,7 @@ impl RegoVM { } } ArrayNew { dest } => { - let empty_array = Value::Array(crate::Rc::new(Vec::new())); + let empty_array = Value::new_array(); self.set_register(dest, empty_array)?; Ok(InstructionOutcome::Continue) } @@ -656,7 +656,7 @@ impl RegoVM { .map(|®| self.get_register(reg).cloned()) .collect::>>()?; - let array_value = Value::Array(crate::Rc::new(elements)); + let array_value = Value::from_array(elements); self.set_register(params.dest, array_value)?; } Ok(InstructionOutcome::Continue) diff --git a/src/schema/tests/suite.rs b/src/schema/tests/suite.rs index 4ba2267c3..7153febf6 100644 --- a/src/schema/tests/suite.rs +++ b/src/schema/tests/suite.rs @@ -976,10 +976,7 @@ fn test_deserialize_array_items_with_fields() { assert_eq!(description.as_deref(), Some("outer array")); assert_eq!(min_items, &Some(1)); assert_eq!(max_items, &Some(2)); - assert_eq!( - default, - &Some(Value::Array(Rc::new(vec![Value::from("bar")]))) - ); + assert_eq!(default, &Some(Value::from_array(vec![Value::from("bar")]))); match items.as_type() { Type::String { description, diff --git a/src/value/array/iter.rs b/src/value/array/iter.rs new file mode 100644 index 000000000..44f245875 --- /dev/null +++ b/src/value/array/iter.rs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Opaque iterator types for [`Array`]. + +use alloc::vec; +use core::iter::FusedIterator; +use core::slice; + +use super::Array; +use crate::value::Value; + +/// Owned iterator over `Value` elements. +#[derive(Debug)] +pub struct ArrayIntoIter { + pub(super) inner: vec::IntoIter, +} + +impl Iterator for ArrayIntoIter { + type Item = Value; + #[inline] + fn next(&mut self) -> Option { + self.inner.next() + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl DoubleEndedIterator for ArrayIntoIter { + #[inline] + fn next_back(&mut self) -> Option { + self.inner.next_back() + } +} + +impl ExactSizeIterator for ArrayIntoIter { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for ArrayIntoIter {} + +/// Borrowed iterator over `&Value` elements. +#[derive(Debug, Clone)] +pub struct ArrayIter<'a> { + pub(super) inner: slice::Iter<'a, Value>, +} + +impl<'a> Iterator for ArrayIter<'a> { + type Item = &'a Value; + #[inline] + fn next(&mut self) -> Option { + self.inner.next() + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a> DoubleEndedIterator for ArrayIter<'a> { + #[inline] + fn next_back(&mut self) -> Option { + self.inner.next_back() + } +} + +impl<'a> ExactSizeIterator for ArrayIter<'a> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a> FusedIterator for ArrayIter<'a> {} + +/// Borrowed iterator over `&mut Value` elements. +#[derive(Debug)] +pub struct ArrayIterMut<'a> { + pub(super) inner: slice::IterMut<'a, Value>, +} + +impl<'a> Iterator for ArrayIterMut<'a> { + type Item = &'a mut Value; + #[inline] + fn next(&mut self) -> Option { + self.inner.next() + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a> DoubleEndedIterator for ArrayIterMut<'a> { + #[inline] + fn next_back(&mut self) -> Option { + self.inner.next_back() + } +} + +impl<'a> ExactSizeIterator for ArrayIterMut<'a> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a> FusedIterator for ArrayIterMut<'a> {} + +impl IntoIterator for Array { + type Item = Value; + type IntoIter = ArrayIntoIter; + #[inline] + fn into_iter(self) -> Self::IntoIter { + ArrayIntoIter { + inner: self.inner.into_iter(), + } + } +} + +impl<'a> IntoIterator for &'a Array { + type Item = &'a Value; + type IntoIter = ArrayIter<'a>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut Array { + type Item = &'a mut Value; + type IntoIter = ArrayIterMut<'a>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} diff --git a/src/value/array/mod.rs b/src/value/array/mod.rs new file mode 100644 index 000000000..ba99d1fbc --- /dev/null +++ b/src/value/array/mod.rs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! See [`Array`]. + +mod iter; +mod serde; + +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::fmt; +use core::ops; + +use crate::value::Value; + +pub use iter::{ArrayIntoIter, ArrayIter, ArrayIterMut}; + +/// Opaque, ordered sequence of [`Value`]s. +/// +/// The current backing storage is `Vec`. The inner field is private so +/// the representation can change without touching call sites. +#[derive(Default, Clone, Eq, PartialEq)] +pub struct Array { + inner: Vec, +} + +impl Array { + /// Create an empty `Array`. + #[inline] + pub const fn new() -> Self { + Self { inner: Vec::new() } + } + + #[inline] + pub const fn len(&self) -> usize { + self.inner.len() + } + + #[inline] + pub const fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + #[inline] + pub fn get(&self, index: usize) -> Option<&Value> { + self.inner.get(index) + } + + #[inline] + pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> { + self.inner.get_mut(index) + } + + #[inline] + pub fn first(&self) -> Option<&Value> { + self.inner.first() + } + + #[inline] + pub fn last(&self) -> Option<&Value> { + self.inner.last() + } + + #[inline] + pub fn contains(&self, value: &Value) -> bool { + self.inner.contains(value) + } + + #[inline] + pub const fn as_slice(&self) -> &[Value] { + self.inner.as_slice() + } + + /// Iteration in element order. Non-resumable. + #[inline] + pub fn iter(&self) -> ArrayIter<'_> { + ArrayIter { + inner: self.inner.iter(), + } + } + + #[inline] + pub fn iter_mut(&mut self) -> ArrayIterMut<'_> { + ArrayIterMut { + inner: self.inner.iter_mut(), + } + } + + #[inline] + pub fn push(&mut self, value: Value) { + self.inner.push(value); + } + + #[inline] + pub fn append(&mut self, other: &mut Array) { + self.inner.append(&mut other.inner); + } + + #[inline] + pub fn extend>(&mut self, iter: I) { + self.inner.extend(iter); + } + + /// Append all elements of `other` (by clone) to the end of `self`. + #[inline] + pub fn extend_from_slice(&mut self, other: &[Value]) { + self.inner.extend_from_slice(other); + } + + #[inline] + pub fn retain(&mut self, f: F) + where + F: FnMut(&Value) -> bool, + { + self.inner.retain(f); + } + + #[inline] + pub fn clear(&mut self) { + self.inner.clear(); + } + + #[inline] + pub fn reverse(&mut self) { + self.inner.reverse(); + } + + #[inline] + pub fn sort(&mut self) { + self.inner.sort(); + } + + #[inline] + pub fn to_vec(&self) -> Vec { + self.inner.clone() + } + + #[inline] + pub fn into_vec(self) -> Vec { + self.inner + } + + /// Wrap into a `Value::Array`. + #[inline] + pub fn into_value(self) -> Value { + Value::Array(crate::Rc::new(self)) + } + + /// Create a resumable cursor over elements in order. O(1). + #[inline] + pub const fn cursor(&self) -> ArrayCursor { + ArrayCursor { index: 0 } + } + + /// Advance `cursor` and yield the next element. + pub fn next<'a>(&'a self, cursor: &mut ArrayCursor) -> Option<&'a Value> { + let value = self.inner.get(cursor.index)?; + cursor.index = cursor.index.saturating_add(1); + Some(value) + } +} + +/// Opaque resumable cursor over an [`Array`]'s elements. +#[derive(Debug, Clone)] +pub struct ArrayCursor { + index: usize, +} + +impl Ord for Array { + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other.iter()) + } +} + +impl PartialOrd for Array { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl fmt::Debug for Array { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl Extend for Array { + fn extend>(&mut self, iter: I) { + self.inner.extend(iter); + } +} + +impl FromIterator for Array { + fn from_iter>(iter: I) -> Self { + Self { + inner: Vec::from_iter(iter), + } + } +} + +impl From> for Array { + #[inline] + fn from(values: Vec) -> Self { + Self { inner: values } + } +} + +impl ops::Index for Array { + type Output = Value; + + /// Indexes the array. Returns `&Value::Undefined` for out-of-range + /// indices rather than panicking, matching `Value`'s indexing semantics. + /// Use [`Array::get`] to distinguish "missing" from "present-and-Undefined". + #[inline] + fn index(&self, index: usize) -> &Self::Output { + self.inner.get(index).unwrap_or(&Value::Undefined) + } +} + +impl From for Value { + #[inline] + fn from(a: Array) -> Self { + a.into_value() + } +} diff --git a/src/value/array/serde.rs b/src/value/array/serde.rs new file mode 100644 index 000000000..ae9ecbe93 --- /dev/null +++ b/src/value/array/serde.rs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Serde `Serialize`/`Deserialize` impls for [`Array`]. + +use core::fmt; + +use serde::de::{Deserialize, Deserializer, Error as _, SeqAccess, Visitor}; +use serde::ser::{Serialize, Serializer}; + +use super::Array; +use crate::value::Value; + +impl Serialize for Array { + fn serialize(&self, serializer: S) -> Result { + serializer.collect_seq(self.iter()) + } +} + +struct ArrayVisitor; + +impl<'de> Visitor<'de> for ArrayVisitor { + type Value = Array; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a sequence of Values") + } + + fn visit_seq>(self, mut access: A) -> Result { + let mut array = Array::new(); + while let Some(v) = access.next_element::()? { + array.push(v); + crate::utils::limits::check_memory_limit_if_needed().map_err(A::Error::custom)?; + } + Ok(array) + } +} + +impl<'de> Deserialize<'de> for Array { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_seq(ArrayVisitor) + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs index ff845eb09..78d16fbcf 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -11,6 +11,7 @@ clippy::as_conversions )] // value helpers index paths directly for performance +mod array; mod object; mod set; @@ -18,6 +19,7 @@ mod set; mod tests; #[allow(unused_imports)] // surface for downstream PRs +pub use array::{Array, ArrayCursor, ArrayIntoIter, ArrayIter, ArrayIterMut}; pub use object::{IntoIter, Iter, IterMut, Object}; #[allow(unused_imports)] // surface for downstream PRs pub use set::Set; @@ -71,7 +73,7 @@ pub enum Value { String(Rc), /// JSON array. - Array(Rc>), + Array(Rc), /// A set of values. /// No JSON equivalent. @@ -763,7 +765,7 @@ impl From> for Value { /// # Ok(()) /// # } fn from(a: Vec) -> Self { - Value::Array(Rc::new(a)) + Value::Array(Rc::new(Array::from(a))) } } @@ -1213,7 +1215,7 @@ impl Value { } } - /// Cast value to [`& Vec`] if [`Value::Array`]. + /// Cast value to [`&Array`] if [`Value::Array`]. /// ``` /// # use regorus::*; /// # fn main() -> anyhow::Result<()> { @@ -1221,14 +1223,14 @@ impl Value { /// assert_eq!(v.as_array()?[0], Value::from("Hello")); /// # Ok(()) /// # } - pub fn as_array(&self) -> Result<&Vec> { + pub fn as_array(&self) -> Result<&Array> { match self { Value::Array(a) => Ok(a), _ => Err(anyhow!("not an array")), } } - /// Cast value to [`&mut Vec`] if [`Value::Array`]. + /// Cast value to [`&mut Array`] if [`Value::Array`]. /// ``` /// # use regorus::*; /// # fn main() -> anyhow::Result<()> { @@ -1236,7 +1238,7 @@ impl Value { /// v.as_array_mut()?.push(Value::from("World")); /// # Ok(()) /// # } - pub fn as_array_mut(&mut self) -> Result<&mut Vec> { + pub fn as_array_mut(&mut self) -> Result<&mut Array> { match self { Value::Array(a) => Ok(Rc::make_mut(a)), _ => Err(anyhow!("not an array")), @@ -1620,7 +1622,7 @@ impl ops::Index<&Value> for Value { _ => &Value::Undefined, }, (Value::Array(a), Value::Number(n)) => match n.as_u64() { - Some(index) if (index as usize) < a.len() => &a[index as usize], + Some(index) => a.get(index as usize).unwrap_or(&Value::Undefined), _ => &Value::Undefined, }, _ => &Value::Undefined,