mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Builds on #57. Swap Value::Object's payload from Rc<BTreeMap<Value, Value>> to Rc<Object> and migrate all call sites to the Object API. as_object / as_object_mut keep their names but return &Object / &mut Object. The mutable accessor handles Rc::make_mut internally, so callers no longer do it themselves. Object grows into_value() and From<Object> for Value. Value's serializer now delegates to Object::serialize, dropping a duplicate non-string-key stringification path. RVM IterationState::Object is rewritten around ObjectCursor: O(log n) steps over a shared Rc<Object>, no eager pair snapshot. Snapshot independence is preserved by Rc copy-on-write; setup_next_iteration advances the cursor inline and advance() becomes a no-op for this variant. A new iteration_state_object_is_snapshot_independent_of_source test covers CoW against a mutated alias. Value::Set still wraps Rc<BTreeSet<Value>>; the matching Set abstraction and its swap ship in follow-up PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1498 lines
43 KiB
Rust
1498 lines
43 KiB
Rust
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
#![allow(
|
|
clippy::indexing_slicing,
|
|
clippy::shadow_unrelated,
|
|
clippy::option_if_let_else,
|
|
clippy::semicolon_if_nothing_returned,
|
|
clippy::pattern_type_mismatch,
|
|
clippy::unused_trait_names,
|
|
clippy::as_conversions
|
|
)] // value helpers index paths directly for performance
|
|
|
|
mod object;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
#[allow(unused_imports)] // surface for downstream PRs
|
|
pub use object::{IntoIter, Iter, IterMut, Object};
|
|
|
|
#[cfg(feature = "rvm")]
|
|
#[allow(unused_imports)] // surface for downstream PRs
|
|
pub use object::ObjectCursor;
|
|
|
|
use crate::number::Number;
|
|
|
|
use alloc::collections::{BTreeMap, BTreeSet};
|
|
use alloc::vec::Vec;
|
|
use core::fmt;
|
|
use core::ops;
|
|
|
|
use core::convert::AsRef;
|
|
use core::str::FromStr;
|
|
|
|
use anyhow::{anyhow, bail, Result};
|
|
use serde::de::{self, Deserializer, Error as DeError, MapAccess, SeqAccess, Visitor};
|
|
use serde::ser::Serializer;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::*;
|
|
|
|
/// A value in a Rego document.
|
|
///
|
|
/// Value is similar to a [`serde_json::value::Value`], but has the following additional
|
|
/// capabilities:
|
|
/// - [`Value::Set`] variant to represent sets.
|
|
/// - [`Value::Undefined`] variant to represent absence of value.
|
|
// - [`Value::Object`] keys can be other values, not just strings.
|
|
/// - [`Value::Number`] has at least 100 digits of precision for computations.
|
|
///
|
|
/// Value can be efficiently cloned due to the use of reference counting.
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub enum Value {
|
|
/// JSON null.
|
|
Null,
|
|
|
|
/// JSON boolean.
|
|
Bool(bool),
|
|
|
|
/// JSON number.
|
|
/// At least 100 digits of precision.
|
|
Number(Number),
|
|
|
|
/// JSON string.
|
|
String(Rc<str>),
|
|
|
|
/// JSON array.
|
|
Array(Rc<Vec<Value>>),
|
|
|
|
/// A set of values.
|
|
/// No JSON equivalent.
|
|
/// Sets are serialized as arrays in JSON.
|
|
Set(Rc<BTreeSet<Value>>),
|
|
|
|
/// An object.
|
|
/// Unlike JSON, keys can be any value, not just string.
|
|
Object(Rc<Object>),
|
|
|
|
/// Undefined value.
|
|
/// Used to indicate the absence of a value.
|
|
Undefined,
|
|
}
|
|
|
|
#[inline]
|
|
fn enforce_limit_anyhow() -> Result<()> {
|
|
crate::utils::limits::check_memory_limit_if_needed().map_err(|err| anyhow!(err))
|
|
}
|
|
|
|
#[inline]
|
|
fn enforce_limit_for<E: DeError>() -> core::result::Result<(), E> {
|
|
crate::utils::limits::check_memory_limit_if_needed().map_err(|err| E::custom(err.to_string()))
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
impl Serialize for Value {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match self {
|
|
Value::Null => serializer.serialize_unit(),
|
|
Value::Bool(b) => serializer.serialize_bool(*b),
|
|
Value::String(s) => serializer.serialize_str(s.as_ref()),
|
|
Value::Number(n) => n.serialize(serializer),
|
|
Value::Array(a) => a.serialize(serializer),
|
|
// Delegate to the Object/Set serializers — single canonical path,
|
|
// handles non-string-key stringification internally.
|
|
Value::Object(fields) => fields.serialize(serializer),
|
|
|
|
// display set as an array
|
|
Value::Set(s) => s.serialize(serializer),
|
|
|
|
// display undefined as a special string
|
|
Value::Undefined => serializer.serialize_str("<undefined>"),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ValueVisitor;
|
|
|
|
impl<'de> Visitor<'de> for ValueVisitor {
|
|
type Value = Value;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("a value")
|
|
}
|
|
|
|
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::Null)
|
|
}
|
|
|
|
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::Bool(v))
|
|
}
|
|
|
|
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::Null)
|
|
}
|
|
|
|
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
Value::deserialize(deserializer)
|
|
}
|
|
|
|
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::from(v))
|
|
}
|
|
|
|
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::from(v))
|
|
}
|
|
|
|
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::from(v))
|
|
}
|
|
|
|
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::from(v))
|
|
}
|
|
|
|
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::from(Number::from(v)))
|
|
}
|
|
|
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::String(s.to_string().into()))
|
|
}
|
|
|
|
fn visit_string<E>(self, s: String) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(Value::String(s.into()))
|
|
}
|
|
|
|
fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
|
where
|
|
V: SeqAccess<'de>,
|
|
{
|
|
let mut arr = vec![];
|
|
while let Some(v) = visitor.next_element()? {
|
|
arr.push(v);
|
|
// Enforce allocator limit while expanding a deserialized array.
|
|
enforce_limit_for::<V::Error>()?;
|
|
}
|
|
Ok(Value::from(arr))
|
|
}
|
|
|
|
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
|
where
|
|
V: MapAccess<'de>,
|
|
{
|
|
if let Some((key, value)) = visitor.next_entry()? {
|
|
if let (Value::String(k), Value::String(v)) = (&key, &value) {
|
|
if k.as_ref() == "$serde_json::private::Number" {
|
|
match Number::from_str(v) {
|
|
Ok(n) => return Ok(Value::from(n)),
|
|
_ => return Err(de::Error::custom("failed to read big number")),
|
|
}
|
|
}
|
|
}
|
|
let mut map = BTreeMap::new();
|
|
map.insert(key, value);
|
|
// Enforce allocator limit while expanding a deserialized object.
|
|
enforce_limit_for::<V::Error>()?;
|
|
while let Some((key, value)) = visitor.next_entry()? {
|
|
map.insert(key, value);
|
|
// Enforce allocator limit while expanding a deserialized object.
|
|
enforce_limit_for::<V::Error>()?;
|
|
}
|
|
Ok(Value::from(map))
|
|
} else {
|
|
Ok(Value::new_object())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
impl<'de> Deserialize<'de> for Value {
|
|
fn deserialize<D>(deserializer: D) -> Result<Value, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
deserializer.deserialize_any(ValueVisitor)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Value {
|
|
/// Display a value.
|
|
///
|
|
/// A value is displayed by serializing it to JSON using serde_json::to_string.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from("hello");
|
|
/// assert_eq!(format!("{v}"), "\"hello\"");
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match serde_json::to_string(self) {
|
|
Ok(s) => write!(f, "{s}"),
|
|
Err(_e) => Err(fmt::Error),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
/// Create an empty [`Value::Array`]
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let obj = Value::new_array();
|
|
/// assert_eq!(obj.as_array().expect("not an array").len(), 0);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn new_array() -> Value {
|
|
Value::from(vec![])
|
|
}
|
|
|
|
/// Create an empty [`Value::Object`]
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let obj = Value::new_object();
|
|
/// assert_eq!(obj.as_object().expect("not an object").len(), 0);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn new_object() -> Value {
|
|
Value::from(BTreeMap::new())
|
|
}
|
|
|
|
/// Create an empty [`Value::Set`]
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let obj = Value::new_set();
|
|
/// assert_eq!(obj.as_set().expect("not a set").len(), 0);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn new_set() -> Value {
|
|
Value::from(BTreeSet::new())
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
/// Deserialize a [`Value`] from JSON.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let json = r#"
|
|
/// [
|
|
/// null, true, false,
|
|
/// "hello", 12345,
|
|
/// { "name" : "regorus" }
|
|
/// ]"#;
|
|
///
|
|
/// // Deserialize json.
|
|
/// let value = Value::from_json_str(json)?;
|
|
///
|
|
/// // Assert outer array.
|
|
/// let array = value.as_array().expect("not an array");
|
|
///
|
|
/// // Assert elements.
|
|
/// assert_eq!(array[0], Value::Null);
|
|
/// assert_eq!(array[1], Value::from(true));
|
|
/// assert_eq!(array[2], Value::from(false));
|
|
/// assert_eq!(array[3], Value::from("hello"));
|
|
/// assert_eq!(array[4], Value::from(12345u64));
|
|
/// let obj = array[5].as_object().expect("not an object");
|
|
/// assert_eq!(obj.len(), 1);
|
|
/// assert_eq!(obj.get(&Value::from("name")).expect("missing name"), &Value::from("regorus"));
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn from_json_str(json: &str) -> Result<Value> {
|
|
match serde_json::from_str::<Value>(json) {
|
|
Ok(value) => Ok(value),
|
|
Err(err) => {
|
|
#[cfg(all(feature = "allocator-memory-limits", not(miri)))]
|
|
{
|
|
// Re-validate allocator limits when serde parsing fails to surface LimitError.
|
|
match crate::utils::limits::check_global_memory_limit() {
|
|
Err(limit_err) => Err(anyhow!(limit_err)),
|
|
Ok(_) => Err(anyhow!(err)),
|
|
}
|
|
}
|
|
|
|
#[cfg(any(miri, not(feature = "allocator-memory-limits")))]
|
|
{
|
|
Err(anyhow!(err))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Deserialize a [`Value`] from a file containing JSON.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let value = Value::from_json_file("tests/aci/input.json")?;
|
|
///
|
|
/// // Convert the value back to json.
|
|
/// let json_str = value.to_json_str()?;
|
|
///
|
|
/// assert_eq!(json_str.trim(),
|
|
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[cfg(feature = "std")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
|
pub fn from_json_file<P: AsRef<std::path::Path>>(path: P) -> Result<Value> {
|
|
match std::fs::read_to_string(&path) {
|
|
Ok(c) => Self::from_json_str(c.as_str()),
|
|
Err(e) => bail!("Failed to read {}. {e}", path.as_ref().display()),
|
|
}
|
|
}
|
|
|
|
/// Serialize a value to JSON.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let value = Value::from_json_file("tests/aci/input.json")?;
|
|
///
|
|
/// // Convert the value back to json.
|
|
/// let json_str = value.to_json_str()?;
|
|
///
|
|
/// assert_eq!(json_str.trim(),
|
|
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Sets are serialized as arrays.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeSet;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut set = BTreeSet::new();
|
|
/// set.insert(Value::from("Hello"));
|
|
/// set.insert(Value::from(1u64));
|
|
///
|
|
/// let set_value = Value::from(set);
|
|
///
|
|
/// assert_eq!(
|
|
/// set_value.to_json_str()?,
|
|
/// r#"
|
|
///[
|
|
/// 1,
|
|
/// "Hello"
|
|
///]"#.trim());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Non string keys of objects are serialized to json first and the serialized string representation
|
|
/// is emitted as the key.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeMap;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut obj = BTreeMap::new();
|
|
/// obj.insert(Value::from("Hello"), Value::from("World"));
|
|
/// obj.insert(Value::from([Value::from(1u64)].to_vec()), Value::Null);
|
|
///
|
|
/// let obj_value = Value::from(obj);
|
|
///
|
|
/// assert_eq!(
|
|
/// obj_value.to_json_str()?,
|
|
/// r#"
|
|
///{
|
|
/// "Hello": "World",
|
|
/// "[1]": null
|
|
///}"#.trim());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn to_json_str(&self) -> Result<String> {
|
|
serde_json::to_string_pretty(self).map_err(anyhow::Error::msg)
|
|
}
|
|
|
|
/// Deserialize a value from YAML.
|
|
/// Note: Deserialization from YAML does not support arbitrary precision numbers.
|
|
#[cfg(feature = "yaml")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
|
pub fn from_yaml_str(yaml: &str) -> Result<Value> {
|
|
let value = serde_yaml::from_str(yaml)
|
|
.map_err(|err| anyhow::anyhow!("Failed to parse YAML: {}", err))?;
|
|
Ok(value)
|
|
}
|
|
|
|
/// Deserialize a value from a file containing YAML.
|
|
/// Note: Deserialization from YAML does not support arbitrary precision numbers.
|
|
#[cfg(feature = "std")]
|
|
#[cfg(feature = "yaml")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
|
|
pub fn from_yaml_file(path: &String) -> Result<Value> {
|
|
match std::fs::read_to_string(path) {
|
|
Ok(c) => Self::from_yaml_str(c.as_str()),
|
|
Err(e) => bail!("Failed to read {path}. {e}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<bool> for Value {
|
|
/// Create a [`Value::Bool`] from `bool`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeSet;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(Value::from(true), Value::Bool(true));
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(b: bool) -> Self {
|
|
Value::Bool(b)
|
|
}
|
|
}
|
|
|
|
impl From<String> for Value {
|
|
/// Create a [`Value::String`] from `string`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(Value::from("Hello".to_string()), Value::String("Hello".into()));
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(s: String) -> Self {
|
|
Value::String(s.into())
|
|
}
|
|
}
|
|
|
|
impl From<&str> for Value {
|
|
/// Create a [`Value::String`] from `&str`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(Value::from("Hello"), Value::String("Hello".into()));
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(s: &str) -> Self {
|
|
Value::String(s.into())
|
|
}
|
|
}
|
|
|
|
impl From<u128> for Value {
|
|
/// Create a [`Value::Number`] from `u128`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(340_282_366_920_938_463_463_374_607_431_768_211_455u128).as_u128()?,
|
|
/// 340_282_366_920_938_463_463_374_607_431_768_211_455u128);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(n: u128) -> Self {
|
|
Value::Number(Number::from(n))
|
|
}
|
|
}
|
|
|
|
impl From<i128> for Value {
|
|
/// Create a [`Value::Number`] from `i128`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(-170141183460469231731687303715884105728i128).as_i128()?,
|
|
/// -170141183460469231731687303715884105728i128);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(n: i128) -> Self {
|
|
Value::Number(Number::from(n))
|
|
}
|
|
}
|
|
|
|
impl From<u64> for Value {
|
|
/// Create a [`Value::Number`] from `u64`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(0u64),
|
|
/// Value::from_json_str("0")?);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(n: u64) -> Self {
|
|
Value::Number(Number::from(n))
|
|
}
|
|
}
|
|
|
|
impl From<i64> for Value {
|
|
/// Create a [`Value::Number`] from `i64`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(0i64),
|
|
/// Value::from_json_str("0")?);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(n: i64) -> Self {
|
|
Value::Number(Number::from(n))
|
|
}
|
|
}
|
|
|
|
impl From<u32> for Value {
|
|
/// Create a [`Value::Number`] from `u32`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(0u32),
|
|
/// Value::from_json_str("0")?);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(n: u32) -> Self {
|
|
Value::Number(Number::from(n as u64))
|
|
}
|
|
}
|
|
|
|
impl From<i32> for Value {
|
|
/// Create a [`Value::Number`] from `i32`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(0i32),
|
|
/// Value::from_json_str("0")?);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(n: i32) -> Self {
|
|
Value::Number(Number::from(n as i64))
|
|
}
|
|
}
|
|
|
|
impl From<f64> for Value {
|
|
/// Create a [`Value::Number`] from `f64`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(3.5f64),
|
|
/// Value::from_numeric_string("3.5")?);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// [`Value::Number`] stores floating-point values as `f64`, so it inherits the same
|
|
/// ~15-digit precision limit. Adding additional digits to either the literal or a parsed
|
|
/// numeric string causes both to round to the same `f64` value.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let from_float = Value::from(3.141592653589793238462f64);
|
|
/// let from_string = Value::from_numeric_string("3.141592653589793238462")?;
|
|
/// assert_eq!(from_float, from_string);
|
|
///
|
|
/// // All representations round to approximately 15 digits.
|
|
/// assert_eq!(
|
|
/// from_float,
|
|
/// Value::from_numeric_string("3.141592653589793")?);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// If additional precision is required, keep the raw data as strings or use an external
|
|
/// arbitrary-precision numeric type before converting it into [`Value`].
|
|
fn from(n: f64) -> Self {
|
|
Value::Number(Number::from(n))
|
|
}
|
|
}
|
|
|
|
impl From<serde_json::Value> for Value {
|
|
/// Create a [`Value`] from [`serde_json::Value`].
|
|
///
|
|
/// Returns [`Value::Undefined`] in case of error.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let json_v = serde_json::json!({ "x":10, "y": 20 });
|
|
/// let v = Value::from(json_v);
|
|
///
|
|
/// assert_eq!(v["x"].as_u64()?, 10);
|
|
/// assert_eq!(v["y"].as_u64()?, 20);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(v: serde_json::Value) -> Self {
|
|
match serde_json::from_value(v) {
|
|
Ok(v) => v,
|
|
_ => Value::Undefined,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
|
|
impl From<serde_yaml::Value> for Value {
|
|
/// Create a [`Value`] from [`serde_yaml::Value`].
|
|
///
|
|
/// Returns [`Value::Undefined`] in case of error.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let yaml = "
|
|
/// x: 10
|
|
/// y: 20
|
|
/// ";
|
|
/// let yaml_v : serde_yaml::Value = serde_yaml::from_str(&yaml).unwrap();
|
|
/// let v = Value::from(yaml_v);
|
|
///
|
|
/// assert_eq!(v["x"].as_u64()?, 10);
|
|
/// assert_eq!(v["y"].as_u64()?, 20);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(v: serde_yaml::Value) -> Self {
|
|
match serde_yaml::from_value(v) {
|
|
Ok(v) => v,
|
|
_ => Value::Undefined,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
/// Create a [`Value::Number`] from a string containing numeric representation of a number.
|
|
///
|
|
/// This is the preferred way for creating arbitrary precision numbers.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from_numeric_string("3.14159265358979323846264338327950288419716939937510")?;
|
|
///
|
|
/// println!("{}", v.to_json_str()?);
|
|
/// // Prints 3.1415926535897932384626433832795028841971693993751 if serde_json/arbitrary_precision feature is enabled.
|
|
/// // Prints 3.141592653589793 if serde_json/arbitrary_precision is not enabled.
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn from_numeric_string(s: &str) -> Result<Value> {
|
|
Ok(Value::Number(
|
|
Number::from_str(s).map_err(|_| anyhow!("not a valid numeric string"))?,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl From<usize> for Value {
|
|
/// Create a [`Value::Number`] from `usize`.
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// assert_eq!(
|
|
/// Value::from(0usize),
|
|
/// Value::from_json_str("0")?);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(n: usize) -> Self {
|
|
Value::Number(Number::from(n))
|
|
}
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
impl From<Number> for Value {
|
|
fn from(n: Number) -> Self {
|
|
Value::Number(n)
|
|
}
|
|
}
|
|
|
|
impl From<Vec<Value>> for Value {
|
|
/// Create a [`Value::Array`] from a [`Vec<Value>`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let strings = [ "Hello", "World" ];
|
|
///
|
|
/// let v = Value::from(strings.iter().map(|s| Value::from(*s)).collect::<Vec<Value>>());
|
|
/// assert_eq!(v[0], Value::from(strings[0]));
|
|
/// assert_eq!(v[1], Value::from(strings[1]));
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(a: Vec<Value>) -> Self {
|
|
Value::Array(Rc::new(a))
|
|
}
|
|
}
|
|
|
|
impl From<BTreeSet<Value>> for Value {
|
|
/// Create a [`Value::Set`] from a [`BTreeSet<Value>`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeSet;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let strings = [ "Hello", "World" ];
|
|
/// let v = Value::from(strings
|
|
/// .iter()
|
|
/// .map(|s| Value::from(*s))
|
|
/// .collect::<BTreeSet<Value>>());
|
|
///
|
|
/// let mut iter = v.as_set()?.iter();
|
|
/// assert_eq!(iter.next(), Some(&Value::from(strings[0])));
|
|
/// assert_eq!(iter.next(), Some(&Value::from(strings[1])));
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(s: BTreeSet<Value>) -> Self {
|
|
Value::Set(Rc::new(s))
|
|
}
|
|
}
|
|
|
|
impl From<BTreeMap<Value, Value>> for Value {
|
|
/// Create a [`Value::Object`] from a [`BTreeMap<Value>`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeMap;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let strings = [ ("Hello", "World") ];
|
|
/// let v = Value::from(strings
|
|
/// .iter()
|
|
/// .map(|(k,v)| (Value::from(*k), Value::from(*v)))
|
|
/// .collect::<BTreeMap<Value, Value>>());
|
|
///
|
|
/// let mut iter = v.as_object()?.iter();
|
|
/// assert_eq!(iter.next(), Some((&Value::from(strings[0].0), &Value::from(strings[0].1))));
|
|
/// # Ok(())
|
|
/// # }
|
|
fn from(s: BTreeMap<Value, Value>) -> Self {
|
|
Value::Object(Rc::new(Object::from(s)))
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
pub(crate) fn from_array(a: Vec<Value>) -> Value {
|
|
Value::from(a)
|
|
}
|
|
|
|
pub(crate) fn from_set(s: BTreeSet<Value>) -> Value {
|
|
Value::from(s)
|
|
}
|
|
|
|
pub(crate) fn from_map(m: BTreeMap<Value, Value>) -> Value {
|
|
Value::from(m)
|
|
}
|
|
|
|
pub(crate) fn is_empty_object(&self) -> bool {
|
|
self == &Value::new_object()
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
/// Cast value to [`& bool`] if [`Value::Bool`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(true);
|
|
/// assert_eq!(v.as_bool()?, &true);
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_bool(&self) -> Result<&bool> {
|
|
match self {
|
|
Value::Bool(b) => Ok(b),
|
|
_ => Err(anyhow!("not a bool")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`&mut bool`] if [`Value::Bool`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut v = Value::from(true);
|
|
/// *v.as_bool_mut()? = false;
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_bool_mut(&mut self) -> Result<&mut bool> {
|
|
match self {
|
|
Value::Bool(b) => Ok(b),
|
|
_ => Err(anyhow!("not a bool")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& u128`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a u128.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(10);
|
|
/// assert_eq!(v.as_u128()?, 10u128);
|
|
///
|
|
/// let v = Value::from(-10);
|
|
/// assert!(v.as_u128().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_u128(&self) -> Result<u128> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_u128() {
|
|
return Ok(n);
|
|
}
|
|
bail!("not a u128");
|
|
}
|
|
_ => Err(anyhow!("not a u128")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& i128`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a i128.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(-10);
|
|
/// assert_eq!(v.as_i128()?, -10i128);
|
|
///
|
|
/// let v = Value::from_numeric_string("11111111111111111111111111111111111111111111111111")?;
|
|
/// assert!(v.as_i128().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_i128(&self) -> Result<i128> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_i128() {
|
|
return Ok(n);
|
|
}
|
|
bail!("not a i128");
|
|
}
|
|
_ => Err(anyhow!("not a i128")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& u64`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a u64.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(10);
|
|
/// assert_eq!(v.as_u64()?, 10u64);
|
|
///
|
|
/// let v = Value::from(-10);
|
|
/// assert!(v.as_u64().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_u64(&self) -> Result<u64> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_u64() {
|
|
return Ok(n);
|
|
}
|
|
bail!("not a u64");
|
|
}
|
|
_ => Err(anyhow!("not a u64")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& i64`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a i64.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(-10);
|
|
/// assert_eq!(v.as_i64()?, -10i64);
|
|
///
|
|
/// let v = Value::from(340_282_366_920_938_463_463_374_607_431_768_211_455u128);
|
|
/// assert!(v.as_i64().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_i64(&self) -> Result<i64> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_i64() {
|
|
return Ok(n);
|
|
}
|
|
bail!("not an i64");
|
|
}
|
|
_ => Err(anyhow!("not an i64")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& u32`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a u32.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(10);
|
|
/// assert_eq!(v.as_u32()?, 10u32);
|
|
///
|
|
/// let v = Value::from(-10);
|
|
/// assert!(v.as_u32().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_u32(&self) -> Result<u32> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_u64() {
|
|
if let Ok(v) = u32::try_from(n) {
|
|
return Ok(v);
|
|
}
|
|
}
|
|
bail!("not a u32");
|
|
}
|
|
_ => Err(anyhow!("not a u32")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& i32`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a i32.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(-10);
|
|
/// assert_eq!(v.as_i32()?, -10i32);
|
|
///
|
|
/// let v = Value::from(2_147_483_648i64);
|
|
/// assert!(v.as_i32().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_i32(&self) -> Result<i32> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_i64() {
|
|
if let Ok(v) = i32::try_from(n) {
|
|
return Ok(v);
|
|
}
|
|
}
|
|
bail!("not an i32");
|
|
}
|
|
_ => Err(anyhow!("not an i32")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& u16`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a u16.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(10);
|
|
/// assert_eq!(v.as_u16()?, 10u16);
|
|
///
|
|
/// let v = Value::from(-10);
|
|
/// assert!(v.as_u16().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_u16(&self) -> Result<u16> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_u64() {
|
|
if let Ok(v) = u16::try_from(n) {
|
|
return Ok(v);
|
|
}
|
|
}
|
|
bail!("not a u16");
|
|
}
|
|
_ => Err(anyhow!("not a u16")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& i16`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a i16.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(-10);
|
|
/// assert_eq!(v.as_i16()?, -10i16);
|
|
///
|
|
/// let v = Value::from(32768i64);
|
|
/// assert!(v.as_i16().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_i16(&self) -> Result<i16> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_i64() {
|
|
if let Ok(v) = i16::try_from(n) {
|
|
return Ok(v);
|
|
}
|
|
}
|
|
bail!("not an i16");
|
|
}
|
|
_ => Err(anyhow!("not an i16")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& u8`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a u8.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(10);
|
|
/// assert_eq!(v.as_u8()?, 10u8);
|
|
///
|
|
/// let v = Value::from(-10);
|
|
/// assert!(v.as_u8().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_u8(&self) -> Result<u8> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_u64() {
|
|
if let Ok(v) = u8::try_from(n) {
|
|
return Ok(v);
|
|
}
|
|
}
|
|
bail!("not a u8");
|
|
}
|
|
_ => Err(anyhow!("not a u8")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& i8`] if [`Value::Number`].
|
|
///
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a i8.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(-10);
|
|
/// assert_eq!(v.as_i8()?, -10i8);
|
|
///
|
|
/// let v = Value::from(128);
|
|
/// assert!(v.as_i8().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_i8(&self) -> Result<i8> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_i64() {
|
|
if let Ok(v) = i8::try_from(n) {
|
|
return Ok(v);
|
|
}
|
|
}
|
|
bail!("not an i8");
|
|
}
|
|
_ => Err(anyhow!("not an i8")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& f64`] if [`Value::Number`].
|
|
/// Error is raised if the value is not a number or if the numeric value
|
|
/// does not fit in a i64.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(-10);
|
|
/// assert_eq!(v.as_f64()?, -10f64);
|
|
///
|
|
/// let v = Value::from(340_282_366_920_938_463_463_374_607_431_768_211_455u128);
|
|
/// assert!(v.as_i64().is_err());
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_f64(&self) -> Result<f64> {
|
|
match self {
|
|
Value::Number(b) => {
|
|
if let Some(n) = b.as_f64() {
|
|
return Ok(n);
|
|
}
|
|
bail!("not a f64");
|
|
}
|
|
_ => Err(anyhow!("not a f64")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& Rc<str>`] if [`Value::String`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from("Hello");
|
|
/// assert_eq!(v.as_string()?.as_ref(), "Hello");
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_string(&self) -> Result<&Rc<str>> {
|
|
match self {
|
|
Value::String(s) => Ok(s),
|
|
_ => Err(anyhow!("not a string")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`&mut Rc<str>`] if [`Value::String`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut v = Value::from("Hello");
|
|
/// *v.as_string_mut()? = "World".into();
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_string_mut(&mut self) -> Result<&mut Rc<str>> {
|
|
match self {
|
|
Value::String(s) => Ok(s),
|
|
_ => Err(anyhow!("not a string")),
|
|
}
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub fn as_number(&self) -> Result<&Number> {
|
|
match self {
|
|
Value::Number(n) => Ok(n),
|
|
_ => Err(anyhow!("not a number")),
|
|
}
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub fn as_number_mut(&mut self) -> Result<&mut Number> {
|
|
match self {
|
|
Value::Number(n) => Ok(n),
|
|
_ => Err(anyhow!("not a number")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& Vec<Value>`] if [`Value::Array`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from([Value::from("Hello")].to_vec());
|
|
/// assert_eq!(v.as_array()?[0], Value::from("Hello"));
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_array(&self) -> Result<&Vec<Value>> {
|
|
match self {
|
|
Value::Array(a) => Ok(a),
|
|
_ => Err(anyhow!("not an array")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`&mut Vec<Value>`] if [`Value::Array`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut v = Value::from([Value::from("Hello")].to_vec());
|
|
/// v.as_array_mut()?.push(Value::from("World"));
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_array_mut(&mut self) -> Result<&mut Vec<Value>> {
|
|
match self {
|
|
Value::Array(a) => Ok(Rc::make_mut(a)),
|
|
_ => Err(anyhow!("not an array")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`& BTreeSet<Value>`] if [`Value::Set`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeSet;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(
|
|
/// [Value::from("Hello")]
|
|
/// .iter()
|
|
/// .cloned()
|
|
/// .collect::<BTreeSet<Value>>(),
|
|
/// );
|
|
/// assert_eq!(v.as_set()?.first(), Some(&Value::from("Hello")));
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_set(&self) -> Result<&BTreeSet<Value>> {
|
|
match self {
|
|
Value::Set(s) => Ok(s),
|
|
_ => Err(anyhow!("not a set")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`&mut BTreeSet<Value>`] if [`Value::Set`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeSet;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut v = Value::from(
|
|
/// [Value::from("Hello")]
|
|
/// .iter()
|
|
/// .cloned()
|
|
/// .collect::<BTreeSet<Value>>(),
|
|
/// );
|
|
/// v.as_set_mut()?.insert(Value::from("World"));
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_set_mut(&mut self) -> Result<&mut BTreeSet<Value>> {
|
|
match self {
|
|
Value::Set(s) => Ok(Rc::make_mut(s)),
|
|
_ => Err(anyhow!("not a set")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`&Object`] if [`Value::Object`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use regorus::value::Object;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(
|
|
/// [(Value::from("Hello"), Value::from("World"))]
|
|
/// .iter()
|
|
/// .cloned()
|
|
/// .collect::<Object>(),
|
|
/// );
|
|
/// assert_eq!(
|
|
/// v.as_object()?.iter().next(),
|
|
/// Some((&Value::from("Hello"), &Value::from("World"))),
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_object(&self) -> Result<&Object> {
|
|
match self {
|
|
Value::Object(m) => Ok(m),
|
|
_ => Err(anyhow!("not an object")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`&mut Object`] if [`Value::Object`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use regorus::value::Object;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut v = Value::from(
|
|
/// [(Value::from("Hello"), Value::from("World"))]
|
|
/// .iter()
|
|
/// .cloned()
|
|
/// .collect::<Object>(),
|
|
/// );
|
|
/// v.as_object_mut()?.insert(Value::from("Good"), Value::from("Bye"));
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_object_mut(&mut self) -> Result<&mut Object> {
|
|
match self {
|
|
Value::Object(m) => Ok(Rc::make_mut(m)),
|
|
_ => Err(anyhow!("not an object")),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
pub(crate) fn make_or_get_value_mut<'a>(&'a mut self, paths: &[&str]) -> Result<&'a mut Value> {
|
|
if paths.is_empty() {
|
|
return Ok(self);
|
|
}
|
|
|
|
let key = Value::String(paths[0].into());
|
|
if self == &Value::Undefined {
|
|
*self = Value::new_object();
|
|
}
|
|
if let Value::Object(map) = self {
|
|
if map.get(&key).is_none() {
|
|
Rc::make_mut(map).insert(key.clone(), Value::Undefined);
|
|
// Enforce allocator limit while creating nested object entries.
|
|
enforce_limit_anyhow()?;
|
|
}
|
|
}
|
|
|
|
match self {
|
|
Value::Object(map) => match Rc::make_mut(map).get_mut(&key) {
|
|
Some(v) if paths.len() == 1 => Ok(v),
|
|
Some(v) => Self::make_or_get_value_mut(v, &paths[1..]),
|
|
_ => bail!("internal error: unexpected"),
|
|
},
|
|
Value::Undefined if paths.len() > 1 => {
|
|
*self = Value::new_object();
|
|
Self::make_or_get_value_mut(self, paths)
|
|
}
|
|
Value::Undefined => Ok(self),
|
|
_ => bail!("internal error: make: not an selfect {self:?}"),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn merge(&mut self, mut new: Value) -> Result<()> {
|
|
if self == &new {
|
|
return Ok(());
|
|
}
|
|
match (self, &mut new) {
|
|
(v @ Value::Undefined, _) => *v = new,
|
|
(Value::Set(ref mut set), Value::Set(new)) => {
|
|
Rc::make_mut(set).append(Rc::make_mut(new));
|
|
// Enforce allocator limit after merging set entries.
|
|
enforce_limit_anyhow()?;
|
|
}
|
|
(Value::Object(map), Value::Object(new)) => {
|
|
for (k, v) in new.iter() {
|
|
match map.get(k) {
|
|
Some(pv) if *pv != *v => {
|
|
bail!(
|
|
"value for key `{}` generated multiple times: `{}` and `{}`",
|
|
serde_json::to_string_pretty(&k).map_err(anyhow::Error::msg)?,
|
|
serde_json::to_string_pretty(&pv).map_err(anyhow::Error::msg)?,
|
|
serde_json::to_string_pretty(&v).map_err(anyhow::Error::msg)?,
|
|
)
|
|
}
|
|
_ => {
|
|
Rc::make_mut(map).insert(k.clone(), v.clone());
|
|
// Enforce allocator limit after merging object entries.
|
|
enforce_limit_anyhow()?;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
_ => bail!("error: could not merge value"),
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ops::Index<&Value> for Value {
|
|
type Output = Value;
|
|
|
|
/// Index a [`Value`] using a [`Value`].
|
|
///
|
|
/// [`Value::Undefined`] is returned
|
|
/// - If the index not valid for the collection.
|
|
/// - If the value being indexed is not an array, set or object.
|
|
///
|
|
/// Sets can be indexed only by elements within the set.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeMap;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
///
|
|
/// let arr = Value::from([Value::from("Hello")].to_vec());
|
|
/// // Index an array.
|
|
/// assert_eq!(arr[&Value::from(0)].as_string()?.as_ref(), "Hello");
|
|
/// assert_eq!(arr[&Value::from(10)], Value::Undefined);
|
|
///
|
|
/// let mut set = Value::new_set();
|
|
/// set.as_set_mut()?.insert(Value::from(100));
|
|
/// set.as_set_mut()?.insert(Value::from("Hello"));
|
|
///
|
|
/// // Index a set.
|
|
/// let item = Value::from("Hello");
|
|
/// assert_eq!(&set[&item], &item);
|
|
/// assert_eq!(&set[&Value::from(10)], &Value::Undefined);
|
|
///
|
|
/// let mut obj = Value::new_object();
|
|
/// obj.as_object_mut()?.insert(Value::from("Hello"), Value::from("World"));
|
|
/// obj.as_object_mut()?.insert(Value::new_array(), Value::from("bye"));
|
|
///
|
|
/// // Index an object.
|
|
/// assert_eq!(&obj[Value::from("Hello")].as_string()?.as_ref(), &"World");
|
|
/// assert_eq!(&obj[Value::from("hllo")], &Value::Undefined);
|
|
/// // Index using non-string key.
|
|
/// assert_eq!(&obj[&Value::new_array()].as_string()?.as_ref(), &"bye");
|
|
///
|
|
/// // Index a non-collection.
|
|
/// assert_eq!(&Value::Null[&Value::from(1)], &Value::Undefined);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// This is the preferred way of indexing a value.
|
|
/// Since constructing a value may be a costly operation (e.g. Value::String),
|
|
/// the caller can construct the index value once and use it many times.
|
|
///`
|
|
fn index(&self, key: &Value) -> &Self::Output {
|
|
match (self, key) {
|
|
(Value::Object(o), _) => match &o.get(key) {
|
|
Some(v) => v,
|
|
_ => &Value::Undefined,
|
|
},
|
|
(Value::Set(s), _) => match s.get(key) {
|
|
Some(v) => v,
|
|
_ => &Value::Undefined,
|
|
},
|
|
(Value::Array(a), Value::Number(n)) => match n.as_u64() {
|
|
Some(index) if (index as usize) < a.len() => &a[index as usize],
|
|
_ => &Value::Undefined,
|
|
},
|
|
_ => &Value::Undefined,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> ops::Index<T> for Value
|
|
where
|
|
Value: From<T>,
|
|
{
|
|
type Output = Value;
|
|
|
|
/// Index a [`Value`].
|
|
///
|
|
///
|
|
/// A [`Value`] is constructed from the index which is then used for indexing.
|
|
///
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeMap;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(
|
|
/// [(Value::from("Hello"), Value::from("World")),
|
|
/// (Value::from(1), Value::from(2))]
|
|
/// .iter()
|
|
/// .cloned()
|
|
/// .collect::<BTreeMap<Value, Value>>(),
|
|
/// );
|
|
///
|
|
/// assert_eq!(&v["Hello"].as_string()?.as_ref(), &"World");
|
|
/// assert_eq!(&v[1].as_u64()?, &2u64);
|
|
/// # Ok(())
|
|
/// # }
|
|
fn index(&self, key: T) -> &Self::Output {
|
|
&self[&Value::from(key)]
|
|
}
|
|
}
|