mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Policy evaluation at scale needs to be able to set memory limits so that a bad policy does not hog memory or to ensure that policy evaluation itself does not use too much memory which could cause other components to suffer. This PR introduces capability to set and enforce global memory limits. It also lays the groundwork for enabling per evaluation limits in future. Once a global memory limit is set, Regorus maintains per thread counters to track memory activity (allocation, deallocation) of a thread. These counters are periodically flushed to global memory counters. Per thread counters avoid the contention that updating global counters on each alloc/free would cause. Policy evaluation periodically checks these counters and raises errors if allocated memory has exceeded the configured limit. Currently memory limit capability is exposed only to FFI and C#. Also update mimalloc to v2.2.6 Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
1497 lines
44 KiB
Rust
1497 lines
44 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
|
|
|
|
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::{SerializeMap, 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<BTreeMap<Value, Value>>),
|
|
|
|
/// 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,
|
|
{
|
|
use serde::ser::Error;
|
|
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),
|
|
Value::Object(fields) => {
|
|
let mut map = serializer.serialize_map(Some(fields.len()))?;
|
|
for (k, v) in fields.iter() {
|
|
match k {
|
|
Value::String(_) => map.serialize_entry(k, v)?,
|
|
_ => {
|
|
let key_str = serde_json::to_string(k).map_err(Error::custom)?;
|
|
map.serialize_entry(&key_str, v)?
|
|
}
|
|
}
|
|
}
|
|
map.end()
|
|
}
|
|
|
|
// 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[&Value::from("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(feature = "allocator-memory-limits")]
|
|
{
|
|
// 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(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(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 [`& BTreeMap<Value, Value>`] if [`Value::Object`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeMap;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let v = Value::from(
|
|
/// [(Value::from("Hello"), Value::from("World"))]
|
|
/// .iter()
|
|
/// .cloned()
|
|
/// .collect::<BTreeMap<Value, Value>>(),
|
|
/// );
|
|
/// assert_eq!(
|
|
/// v.as_object()?.iter().next(),
|
|
/// Some((&Value::from("Hello"), &Value::from("World"))),
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_object(&self) -> Result<&BTreeMap<Value, Value>> {
|
|
match self {
|
|
Value::Object(m) => Ok(m),
|
|
_ => Err(anyhow!("not an object")),
|
|
}
|
|
}
|
|
|
|
/// Cast value to [`&mut BTreeMap<Value, Value>`] if [`Value::Object`].
|
|
/// ```
|
|
/// # use regorus::*;
|
|
/// # use std::collections::BTreeMap;
|
|
/// # fn main() -> anyhow::Result<()> {
|
|
/// let mut v = Value::from(
|
|
/// [(Value::from("Hello"), Value::from("World"))]
|
|
/// .iter()
|
|
/// .cloned()
|
|
/// .collect::<BTreeMap<Value, Value>>(),
|
|
/// );
|
|
/// v.as_object_mut()?.insert(Value::from("Good"), Value::from("Bye"));
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn as_object_mut(&mut self) -> Result<&mut BTreeMap<Value, Value>> {
|
|
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)]
|
|
}
|
|
}
|