Skip to content

Commit 81ae161

Browse files
anakrishCopilot
andcommitted
feat(value): introduce Array storage abstraction
Add an opaque `Array` newtype paralleling `Object`, living under `src/value/array/` with the same module structure (`mod.rs` / `iter.rs` / `serde.rs`). `Array` wraps `Vec<Value>` today but exposes only a curated surface: constructors, indexed access, push/pop/insert/ remove/truncate/sort/dedup/reverse, iteration, serde, conversion to `Value`, and a hand-written lexicographic `Ord`. The cursor type is re-exported behind the `rvm` feature so the follow-up `IterationState::Array` swap can land additively. No crate-root `Array` re-export is added; the new type lives under `regorus::value::Array`, matching the storage-abstraction namespace strategy for `Object` and `Set`. `Value::Array` is unchanged in this commit (still wraps `Rc<Vec<Value>>`); the payload swap and call-site migration ship in the next PR. Also repair pre-existing doctest examples that `cargo test --doc --all-features` exercises, so the required validation suite passes with this branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ed6ae46 commit 81ae161

10 files changed

Lines changed: 730 additions & 7 deletions

File tree

docs/value/array.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Array
2+
3+
Opaque container for `Value::Array`'s element storage, enabling alternative
4+
backends without call-site changes. Pairs with [`Object`](object.md) under a
5+
shared design philosophy.
6+
7+
## Design
8+
9+
`Array` wraps a `Vec<Value>` today but exposes only a curated method surface
10+
(`get`, `get_mut`, `push`, `pop`, `insert`, `remove`, `iter`, `iter_mut`,
11+
`cursor`, serde). The inner vector is private — callers cannot pattern-match it
12+
or hand out mutable references to the backing store, so the backend can change
13+
without churn at call sites that currently assume `Vec<Value>`.
14+
15+
Iteration follows sequence order. Cursor types support incremental traversal
16+
needed by the RVM iteration state without exposing iterator internals.
17+
18+
`Ord` is hand-written against the sequence iterator rather than derived from the
19+
storage, so future backends compare exactly like today's `Vec<Value>` payload.
20+
21+
## Scenarios enabled
22+
23+
- **Inline-small storage** — store short arrays inline and spill to heap only for
24+
larger values.
25+
- **Lazy/streaming** — wrap a provider over JSON/CBOR/host data and materialize
26+
elements on demand.
27+
- **Arena allocation** — bumpalo-backed arrays for eval-time temporaries; drop
28+
the whole arena at query end with zero per-element free cost.
29+
- **FFI-backed** — host-language lists or arrays without copying into Rust on
30+
every binding boundary.
31+
32+
## Notes
33+
34+
`Value::Array` is not migrated by the foundation PR that introduces this type;
35+
it still wraps `Rc<Vec<Value>>` until the follow-up migration.

src/compiled_policy.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ impl CompiledPolicy {
9696
/// # // Register a target for the example
9797
/// # #[cfg(feature = "azure_policy")]
9898
/// # {
99-
/// # let target = regorus::target::Target::from_json_file("tests/interpreter/cases/target/definitions/sample_target.json")?;
99+
/// # let target_json = std::fs::read_to_string("tests/interpreter/cases/target/definitions/sample_target.json")?;
100+
/// # let target = regorus::target::Target::from_json_str(&target_json)?;
100101
/// # regorus::registry::targets::register(std::sync::Arc::new(target))?;
101102
/// # }
102103
///

src/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1402,7 +1402,7 @@ impl Engine {
14021402
/// let ast = engine.get_ast_as_json()?;
14031403
/// let value = Value::from_json_str(&ast)?;
14041404
///
1405-
/// assert_eq!(value[0]["ast"]["package"]["refr"]["Var"][1].as_string()?.as_ref(), "test");
1405+
/// assert_eq!(value[0]["ast"]["package"]["refr"]["Var"]["value"].as_string()?.as_ref(), "test");
14061406
/// # Ok(())
14071407
/// # }
14081408
/// ```

src/schema.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ pub mod validate;
216216
/// Schemas are typically created by deserializing from JSON Schema format:
217217
///
218218
/// ```rust
219+
/// use regorus::Schema;
219220
/// use serde_json::json;
220221
///
221222
/// // Create a schema from JSON
@@ -255,12 +256,18 @@ pub mod validate;
255256
///
256257
/// ## Simple String Schema
257258
/// ```rust
259+
/// use regorus::Schema;
260+
/// use serde_json::json;
261+
///
258262
/// let schema = json!({ "type": "string", "minLength": 1 });
259263
/// let parsed: Schema = serde_json::from_value(schema).unwrap();
260264
/// ```
261265
///
262266
/// ## Complex Object Schema
263267
/// ```rust
268+
/// use regorus::Schema;
269+
/// use serde_json::json;
270+
///
264271
/// let schema = json!({
265272
/// "type": "object",
266273
/// "properties": {
@@ -282,6 +289,9 @@ pub mod validate;
282289
///
283290
/// ## Union Types with anyOf
284291
/// ```rust
292+
/// use regorus::Schema;
293+
/// use serde_json::json;
294+
///
285295
/// let schema = json!({
286296
/// "anyOf": [
287297
/// { "type": "string" },
@@ -337,8 +347,7 @@ impl Schema {
337347
///
338348
/// # Example
339349
/// ```rust
340-
/// use regorus::schema::Schema;
341-
/// use regorus::Value;
350+
/// use regorus::{Schema, Value};
342351
/// use serde_json::json;
343352
///
344353
/// let schema_json = json!({

src/schema/validate.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ impl SchemaValidator {
3131
///
3232
/// # Example
3333
/// ```rust
34-
/// use regorus::schema::{Schema, validate::SchemaValidator};
35-
/// use regorus::Value;
34+
/// use regorus::{Schema, SchemaValidator, Value};
3635
/// use serde_json::json;
3736
///
3837
/// let schema_json = json!({

src/value/array/iter.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
//! Opaque iterator types for [`Array`].
5+
//!
6+
//! These newtypes wrap the storage backend's iterators so the backend can be
7+
//! swapped without changing any iterator type signatures observed by callers.
8+
9+
use alloc::vec;
10+
use core::iter::FusedIterator;
11+
use core::slice;
12+
13+
use super::Array;
14+
use crate::value::Value;
15+
16+
/// Owned iterator over `Value` elements.
17+
#[derive(Debug)]
18+
pub struct IntoIter {
19+
pub(super) inner: vec::IntoIter<Value>,
20+
}
21+
22+
impl Iterator for IntoIter {
23+
type Item = Value;
24+
25+
#[inline]
26+
fn next(&mut self) -> Option<Self::Item> {
27+
self.inner.next()
28+
}
29+
30+
#[inline]
31+
fn size_hint(&self) -> (usize, Option<usize>) {
32+
self.inner.size_hint()
33+
}
34+
}
35+
36+
impl DoubleEndedIterator for IntoIter {
37+
#[inline]
38+
fn next_back(&mut self) -> Option<Self::Item> {
39+
self.inner.next_back()
40+
}
41+
}
42+
43+
impl ExactSizeIterator for IntoIter {
44+
#[inline]
45+
fn len(&self) -> usize {
46+
self.inner.len()
47+
}
48+
}
49+
50+
impl FusedIterator for IntoIter {}
51+
52+
/// Borrowed iterator over `&Value` elements.
53+
#[derive(Debug, Clone)]
54+
pub struct Iter<'a> {
55+
pub(super) inner: slice::Iter<'a, Value>,
56+
}
57+
58+
impl<'a> Iterator for Iter<'a> {
59+
type Item = &'a Value;
60+
61+
#[inline]
62+
fn next(&mut self) -> Option<Self::Item> {
63+
self.inner.next()
64+
}
65+
66+
#[inline]
67+
fn size_hint(&self) -> (usize, Option<usize>) {
68+
self.inner.size_hint()
69+
}
70+
}
71+
72+
impl<'a> DoubleEndedIterator for Iter<'a> {
73+
#[inline]
74+
fn next_back(&mut self) -> Option<Self::Item> {
75+
self.inner.next_back()
76+
}
77+
}
78+
79+
impl<'a> ExactSizeIterator for Iter<'a> {
80+
#[inline]
81+
fn len(&self) -> usize {
82+
self.inner.len()
83+
}
84+
}
85+
86+
impl<'a> FusedIterator for Iter<'a> {}
87+
88+
/// Mutable borrowed iterator over `&mut Value` elements.
89+
#[derive(Debug)]
90+
pub struct IterMut<'a> {
91+
pub(super) inner: slice::IterMut<'a, Value>,
92+
}
93+
94+
impl<'a> Iterator for IterMut<'a> {
95+
type Item = &'a mut Value;
96+
97+
#[inline]
98+
fn next(&mut self) -> Option<Self::Item> {
99+
self.inner.next()
100+
}
101+
102+
#[inline]
103+
fn size_hint(&self) -> (usize, Option<usize>) {
104+
self.inner.size_hint()
105+
}
106+
}
107+
108+
impl<'a> DoubleEndedIterator for IterMut<'a> {
109+
#[inline]
110+
fn next_back(&mut self) -> Option<Self::Item> {
111+
self.inner.next_back()
112+
}
113+
}
114+
115+
impl<'a> ExactSizeIterator for IterMut<'a> {
116+
#[inline]
117+
fn len(&self) -> usize {
118+
self.inner.len()
119+
}
120+
}
121+
122+
impl<'a> FusedIterator for IterMut<'a> {}
123+
124+
/// Opaque resumable cursor over an [`Array`]'s elements.
125+
///
126+
/// Self-owned: holds no borrow on the `Array`, so it can be stored as a field
127+
/// of a long-lived state struct (e.g. an RVM iteration frame).
128+
#[cfg(feature = "rvm")]
129+
#[derive(Debug, Clone, Default)]
130+
pub struct Cursor {
131+
pub(super) next: usize,
132+
}
133+
134+
impl IntoIterator for Array {
135+
type Item = Value;
136+
type IntoIter = IntoIter;
137+
138+
#[inline]
139+
fn into_iter(self) -> Self::IntoIter {
140+
IntoIter {
141+
inner: self.inner.into_iter(),
142+
}
143+
}
144+
}
145+
146+
impl<'a> IntoIterator for &'a Array {
147+
type Item = &'a Value;
148+
type IntoIter = Iter<'a>;
149+
150+
#[inline]
151+
fn into_iter(self) -> Self::IntoIter {
152+
Iter {
153+
inner: self.inner.iter(),
154+
}
155+
}
156+
}
157+
158+
impl<'a> IntoIterator for &'a mut Array {
159+
type Item = &'a mut Value;
160+
type IntoIter = IterMut<'a>;
161+
162+
#[inline]
163+
fn into_iter(self) -> Self::IntoIter {
164+
IterMut {
165+
inner: self.inner.iter_mut(),
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)