use core::fmt;
use super::{Limits, Number};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
#[derive(Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Number(Number),
String(Zeroizing<String>),
Array(Vec<Value>),
Object(Object),
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_number(&self) -> Option<Number> {
match self {
Value::Number(n) => Some(*n),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
self.as_number().and_then(|n| n.as_i64())
}
pub fn as_u64(&self) -> Option<u64> {
self.as_number().and_then(|n| n.as_u64())
}
pub fn as_f64(&self) -> Option<f64> {
self.as_number().map(|n| n.as_f64())
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s.as_str()),
_ => None,
}
}
pub fn as_array(&self) -> Option<&[Value]> {
match self {
Value::Array(a) => Some(a),
_ => None,
}
}
pub fn as_object(&self) -> Option<&Object> {
match self {
Value::Object(o) => Some(o),
_ => None,
}
}
pub fn into_object(self) -> Option<Object> {
match self {
Value::Object(o) => Some(o),
_ => None,
}
}
pub fn get(&self, name: &str) -> Option<&Value> {
self.as_object().and_then(|o| o.get(name))
}
}
impl Zeroize for Value {
fn zeroize(&mut self) {
match self {
Value::String(s) => s.zeroize(),
Value::Array(items) => items.zeroize(),
Value::Object(o) => o.zeroize(),
Value::Null | Value::Bool(_) | Value::Number(_) => {}
}
*self = Value::Null;
}
}
impl ZeroizeOnDrop for Value {}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Redacted {
value: self,
depth: 0,
}
.fmt(f)
}
}
impl From<bool> for Value {
fn from(b: bool) -> Value {
Value::Bool(b)
}
}
impl From<Number> for Value {
fn from(n: Number) -> Value {
Value::Number(n)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Value {
Value::String(Zeroizing::new(s.to_owned()))
}
}
impl From<String> for Value {
fn from(s: String) -> Value {
Value::String(Zeroizing::new(s))
}
}
impl From<Vec<Value>> for Value {
fn from(a: Vec<Value>) -> Value {
Value::Array(a)
}
}
impl From<Object> for Value {
fn from(o: Object) -> Value {
Value::Object(o)
}
}
#[derive(Clone, Default)]
pub struct Object {
members: Vec<(Zeroizing<String>, Value)>,
}
impl Object {
pub fn new() -> Object {
Object::default()
}
pub(crate) fn from_unique_members(members: Vec<(Zeroizing<String>, Value)>) -> Object {
debug_assert!(
names_are_unique(&members),
"from_unique_members called with a duplicate member name"
);
Object { members }
}
pub fn len(&self) -> usize {
self.members.len()
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
pub fn get(&self, name: &str) -> Option<&Value> {
self.members
.iter()
.find_map(|(n, v)| same_name(n, name).then_some(v))
}
pub fn contains(&self, name: &str) -> bool {
self.get(name).is_some()
}
pub fn insert(&mut self, name: impl Into<String>, value: impl Into<Value>) -> Option<Value> {
let name = Zeroizing::new(name.into());
let value = value.into();
match self.members.iter_mut().find(|(n, _)| same_name(n, &name)) {
Some((_, slot)) => Some(core::mem::replace(slot, value)),
None => {
self.members.push((name, value));
None
}
}
}
pub fn remove(&mut self, name: &str) -> Option<Value> {
let index = self.members.iter().position(|(n, _)| same_name(n, name))?;
Some(self.members.remove(index).1)
}
pub fn sort_members(&mut self) {
self.members
.sort_unstable_by(|(a, _), (b, _)| cmp_names(a, b));
}
pub fn iter(&self) -> Iter<'_> {
Iter(self.members.iter())
}
}
impl Zeroize for Object {
fn zeroize(&mut self) {
for (name, value) in &mut self.members {
name.zeroize();
value.zeroize();
}
self.members.clear();
}
}
impl ZeroizeOnDrop for Object {}
impl fmt::Debug for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
RedactedMembers {
members: &self.members,
depth: 1,
}
.fmt(f)
}
}
const REDACTED: &str = "<redacted>";
const TOO_DEEP: &str = "<too deep>";
struct Redacted<'a> {
value: &'a Value,
depth: usize,
}
impl fmt::Debug for Redacted<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let depth = self.depth + 1;
match self.value {
Value::Null => f.write_str("Null"),
Value::Bool(b) => f.debug_tuple("Bool").field(b).finish(),
Value::Number(n) => f.debug_tuple("Number").field(n).finish(),
Value::String(_) => f.debug_tuple("String").field(&REDACTED).finish(),
Value::Array(_) | Value::Object(_) if self.depth >= Limits::MAX_DEPTH_CEILING => {
f.write_str(TOO_DEEP)
}
Value::Array(items) => f
.debug_tuple("Array")
.field(&RedactedItems { items, depth })
.finish(),
Value::Object(o) => f
.debug_tuple("Object")
.field(&RedactedMembers {
members: &o.members,
depth,
})
.finish(),
}
}
}
struct RedactedItems<'a> {
items: &'a [Value],
depth: usize,
}
impl fmt::Debug for RedactedItems<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let depth = self.depth;
f.debug_list()
.entries(self.items.iter().map(|value| Redacted { value, depth }))
.finish()
}
}
struct RedactedMembers<'a> {
members: &'a [(Zeroizing<String>, Value)],
depth: usize,
}
impl fmt::Debug for RedactedMembers<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let depth = self.depth;
f.debug_map()
.entries(
self.members
.iter()
.map(|(name, value)| (name.as_str(), Redacted { value, depth })),
)
.finish()
}
}
impl PartialEq for Object {
fn eq(&self, other: &Object) -> bool {
self.len() == other.len()
&& self
.sorted_by_name()
.into_iter()
.zip(other.sorted_by_name())
.all(|((a_name, a_value), (b_name, b_value))| {
same_name(a_name, b_name) && a_value == b_value
})
}
}
impl Object {
fn sorted_by_name(&self) -> Vec<&(Zeroizing<String>, Value)> {
let mut members: Vec<_> = self.members.iter().collect();
members.sort_unstable_by(|(a, _), (b, _)| cmp_names(a, b));
members
}
}
fn names_are_unique(members: &[(Zeroizing<String>, Value)]) -> bool {
let mut names: Vec<&str> = members.iter().map(|(n, _)| n.as_str()).collect();
names.sort_unstable();
names.windows(2).all(|w| w[0] != w[1])
}
#[inline]
fn same_name(a: &str, b: &str) -> bool {
#[cfg(test)]
name_comparisons::tick();
a == b
}
#[inline]
fn cmp_names(a: &str, b: &str) -> core::cmp::Ordering {
#[cfg(test)]
name_comparisons::tick();
a.cmp(b)
}
#[cfg(test)]
use crate::test_counters::name_comparisons;
#[cfg(test)]
impl Object {
pub(crate) fn count_name_comparisons<R>(f: impl FnOnce() -> R) -> (R, u64) {
let before = name_comparisons::get();
let result = f();
(result, name_comparisons::get() - before)
}
}
#[derive(Clone)]
pub struct Iter<'a>(core::slice::Iter<'a, (Zeroizing<String>, Value)>);
impl<'a> Iterator for Iter<'a> {
type Item = (&'a str, &'a Value);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(n, v)| (n.as_str(), v))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for Iter<'_> {}
impl fmt::Debug for Iter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Iter")
.field(&RedactedMembers {
members: self.0.as_slice(),
depth: 1,
})
.finish()
}
}
impl<'a> IntoIterator for &'a Object {
type Item = (&'a str, &'a Value);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Iter<'a> {
self.iter()
}
}
pub struct IntoIter(std::vec::IntoIter<(Zeroizing<String>, Value)>);
impl Iterator for IntoIter {
type Item = (Zeroizing<String>, Value);
fn next(&mut self) -> Option<(Zeroizing<String>, Value)> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for IntoIter {}
impl fmt::Debug for IntoIter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("IntoIter")
.field(&RedactedMembers {
members: self.0.as_slice(),
depth: 1,
})
.finish()
}
}
impl IntoIterator for Object {
type Item = (Zeroizing<String>, Value);
type IntoIter = IntoIter;
fn into_iter(self) -> IntoIter {
IntoIter(self.members.into_iter())
}
}
#[cfg(test)]
mod tests {
use super::*;
use zeroize::ZeroizeOnDrop;
fn int(i: i64) -> Value {
Number::from_i64(i).map_or(Value::Null, Value::Number)
}
fn sample() -> Object {
let mut o = Object::new();
o.insert("kty", "EC");
o.insert("n", int(3));
o.insert("ok", true);
o.insert("list", vec![Value::Null, Value::from("x")]);
o
}
#[test]
fn accessors_match_only_their_own_variant() {
let o = sample();
assert_eq!(o.get("kty").and_then(Value::as_str), Some("EC"));
assert_eq!(o.get("n").and_then(Value::as_i64), Some(3));
assert_eq!(o.get("n").and_then(Value::as_u64), Some(3));
assert_eq!(o.get("n").and_then(Value::as_f64), Some(3.0));
assert_eq!(o.get("ok").and_then(Value::as_bool), Some(true));
assert_eq!(
o.get("list").and_then(Value::as_array).map(<[_]>::len),
Some(2)
);
assert!(
o.get("list")
.and_then(Value::as_array)
.is_some_and(|a| a[0].is_null())
);
let s = Value::from("1");
assert_eq!(s.as_i64(), None);
assert_eq!(s.as_bool(), None);
assert_eq!(s.as_array(), None);
assert!(s.as_object().is_none());
assert!(s.get("kty").is_none());
assert!(!s.is_null());
assert_eq!(Value::Bool(true).as_str(), None);
assert_eq!(Value::Null.as_number(), None);
}
#[test]
fn get_and_into_object_on_values() {
let v = Value::from(sample());
assert_eq!(v.get("kty").and_then(Value::as_str), Some("EC"));
assert!(v.get("missing").is_none());
assert_eq!(v.into_object().map(|o| o.len()), Some(4));
assert!(Value::Null.into_object().is_none());
}
#[test]
fn insert_keeps_names_unique_and_position_stable() {
let mut o = sample();
assert_eq!(o.insert("n", int(4)), Some(int(3)));
let names: Vec<&str> = o.iter().map(|(n, _)| n).collect();
assert_eq!(names, ["kty", "n", "ok", "list"]);
assert_eq!(o.get("n"), Some(&int(4)));
assert!(o.contains("ok"));
assert!(!o.contains("OK"));
}
#[test]
fn remove_takes_one_member_and_keeps_the_rest_in_order() {
let mut o = sample();
assert_eq!(o.remove("n"), Some(int(3)));
assert_eq!(o.remove("n"), None);
let names: Vec<&str> = o.iter().map(|(n, _)| n).collect();
assert_eq!(names, ["kty", "ok", "list"]);
}
#[test]
fn sort_members_uses_code_point_order() {
let mut o = Object::new();
for name in [
"y",
"\u{e9}",
"crv",
"x",
"kty",
"Z",
"\u{1F600}",
"\u{ffff}",
] {
o.insert(name, Value::Null);
}
o.sort_members();
let names: Vec<&str> = o.iter().map(|(n, _)| n).collect();
assert_eq!(
names,
[
"Z",
"crv",
"kty",
"x",
"y",
"\u{e9}",
"\u{ffff}",
"\u{1F600}"
]
);
}
#[test]
fn object_equality_ignores_member_order() {
let mut a = Object::new();
a.insert("a", int(1));
a.insert("b", int(2));
let mut b = Object::new();
b.insert("b", int(2));
b.insert("a", int(1));
assert_eq!(a, b);
b.insert("a", int(9));
assert_ne!(a, b);
b.insert("a", int(1));
b.insert("c", int(3));
assert_ne!(a, b);
assert!(Object::new().is_empty());
}
#[test]
fn iterators_yield_members_in_order() {
let o = sample();
assert_eq!(o.iter().len(), 4);
let borrowed: Vec<String> = (&o).into_iter().map(|(n, _)| n.to_owned()).collect();
let owned: Vec<String> = o.into_iter().map(|(n, _)| n.as_str().to_owned()).collect();
assert_eq!(borrowed, owned);
assert_eq!(owned, ["kty", "n", "ok", "list"]);
}
#[test]
fn from_conversions_pick_the_matching_variant() {
assert_eq!(Value::from(false), Value::Bool(false));
assert_eq!(
Value::from("s"),
Value::String(Zeroizing::new("s".to_owned()))
);
assert_eq!(
Value::from(String::from("s")),
Value::String(Zeroizing::new("s".to_owned()))
);
assert_eq!(Value::from(Vec::new()), Value::Array(Vec::new()));
assert_eq!(Value::from(Object::new()), Value::Object(Object::new()));
assert_eq!(
Number::from_f64(0.5)
.map(Value::from)
.and_then(|v| v.as_f64()),
Some(0.5)
);
}
fn assert_zeroize_on_drop<T: Zeroize + ZeroizeOnDrop>() {}
#[test]
fn values_and_objects_zeroize_their_strings_on_drop() {
assert_zeroize_on_drop::<Value>();
assert_zeroize_on_drop::<Object>();
let Value::String(payload) = Value::from("secret") else {
unreachable!("From<&str> builds a string")
};
let _: &Zeroizing<String> = &payload;
let names: Vec<Zeroizing<String>> = sample().into_iter().map(|(n, _)| n).collect();
assert_eq!(names.len(), 4);
}
#[test]
fn zeroize_wipes_a_value_in_place() {
let mut v = Value::from(vec![Value::from("d"), Value::from(sample())]);
v.zeroize();
assert!(v.is_null());
let mut o = sample();
o.zeroize();
assert!(o.is_empty());
let mut s = Value::from(String::from("k"));
s.zeroize();
assert!(s.is_null());
}
#[test]
fn the_comparison_counter_sees_every_name_comparison() {
let o = sample();
let (found, count) = Object::count_name_comparisons(|| o.get("missing").is_some());
assert!(!found);
assert_eq!(count, 4);
let ((_, inner), outer) =
Object::count_name_comparisons(|| Object::count_name_comparisons(|| o.contains("kty")));
assert_eq!((inner, outer), (1, 1));
}
#[test]
fn unique_member_names_are_checked_in_debug_builds() {
let member = |name: &str| (Zeroizing::new(name.to_owned()), Value::Null);
assert!(names_are_unique(&[]));
assert!(names_are_unique(&[member("a"), member("b"), member("A")]));
assert!(!names_are_unique(&[member("a"), member("b"), member("a")]));
let o = Object::from_unique_members(vec![member("b"), member("a")]);
let names: Vec<&str> = o.iter().map(|(n, _)| n).collect();
assert_eq!(names, ["b", "a"], "order is kept");
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "duplicate member name")]
fn from_unique_members_rejects_duplicates_in_debug_builds() {
let member = |name: &str| (Zeroizing::new(name.to_owned()), Value::Null);
let _ = Object::from_unique_members(vec![member("a"), member("a")]);
}
#[test]
fn object_equality_pairs_values_by_name_at_every_level() {
let inner = |first: &str, second: &str| {
let mut o = Object::new();
o.insert(first, Value::from(first));
o.insert(second, Value::from(second));
Value::from(o)
};
let mut a = Object::new();
a.insert("x", int(1));
a.insert("y", int(2));
a.insert("nested", inner("p", "q"));
let mut b = Object::new();
b.insert("nested", inner("q", "p"));
b.insert("y", int(2));
b.insert("x", int(1));
assert_eq!(a, b);
let mut swapped = b.clone();
swapped.insert("x", int(2));
swapped.insert("y", int(1));
assert_ne!(a, swapped);
assert_eq!(Object::new(), Object::new());
assert_ne!(a, Object::new());
}
fn n_log_n_bound(n: usize) -> u64 {
let n = n as u64;
4 * n * u64::from(u64::BITS - n.leading_zeros())
}
fn numbered_object(n: usize, order: impl Fn(usize) -> usize) -> Object {
Object::from_unique_members(
(0..n)
.map(order)
.map(|i| (Zeroizing::new(format!("m{i}")), int(i as i64)))
.collect(),
)
}
#[test]
fn object_equality_is_n_log_n_in_the_member_count() {
const N: usize = 10_000;
let a = numbered_object(N, |i| i);
let b = numbered_object(N, |i| (i * 7919) % N);
let (equal, count) = Object::count_name_comparisons(|| a == b);
assert!(equal);
assert!(count <= n_log_n_bound(N), "{count} comparisons");
let mut other_value = b.clone();
other_value.insert("m1234", int(-1));
let mut other_name = b.clone();
other_name.remove("m4321");
other_name.insert("x", int(4321));
for unequal in [&other_value, &other_name] {
let (equal, count) = Object::count_name_comparisons(|| a == *unequal);
assert!(!equal);
assert!(count <= n_log_n_bound(N), "{count} comparisons");
}
}
const SECRET: &str = "MARKERsecret";
#[test]
fn debug_prints_strings_as_a_placeholder() {
let v = Value::from(SECRET);
assert_eq!(format!("{v:?}"), r#"String("<redacted>")"#);
assert_eq!(format!("{v:#?}"), "String(\n \"<redacted>\",\n)");
assert_eq!(format!("{:?}", Value::from("")), format!("{v:?}"));
}
#[test]
fn debug_prints_null_bools_and_numbers() {
assert_eq!(format!("{:?}", Value::Null), "Null");
assert_eq!(format!("{:?}", Value::Bool(true)), "Bool(true)");
let Some(n) = Number::from_i64(3) else {
unreachable!("3 is an exact integer")
};
assert_eq!(format!("{:?}", Value::Number(n)), format!("Number({n:?})"));
}
#[test]
fn debug_prints_object_names_and_structure_but_no_string() {
let mut inner = Object::new();
inner.insert("d", SECRET);
let mut o = Object::new();
o.insert("kty", "EC");
o.insert("ok", true);
o.insert("keys", vec![Value::from(inner), Value::from(SECRET)]);
assert_eq!(
format!("{o:?}"),
r#"{"kty": String("<redacted>"), "ok": Bool(true), "keys": Array([Object({"d": String("<redacted>")}), String("<redacted>")])}"#
);
let v = Value::from(o);
let debug = format!("{v:?}");
assert!(debug.starts_with(r#"Object({"kty": "#), "{debug}");
let pretty = format!("{v:#?}");
assert!(!pretty.contains(SECRET), "{pretty}");
assert!(pretty.contains("\"d\": String(\n"), "{pretty}");
assert_eq!(format!("{:?}", Object::new()), "{}");
}
#[test]
fn debug_of_the_iterators_is_redacted() {
let mut o = Object::new();
o.insert("k", SECRET);
let mut iter = o.iter();
assert_eq!(format!("{iter:?}"), r#"Iter({"k": String("<redacted>")})"#);
assert!(iter.next().is_some());
assert_eq!(format!("{iter:?}"), "Iter({})");
assert_eq!(
format!("{:?}", o.into_iter()),
r#"IntoIter({"k": String("<redacted>")})"#
);
}
fn nested_arrays(depth: usize) -> Value {
let mut v = Value::from(SECRET);
for _ in 0..depth {
v = Value::Array(vec![v]);
}
v
}
fn dismantle(mut v: Value) {
while let Value::Array(mut items) = v {
v = items.pop().unwrap_or(Value::Null);
}
}
#[test]
fn debug_prints_everything_the_parser_can_produce() {
let ceiling = super::super::Limits::MAX_DEPTH_CEILING;
let v = nested_arrays(ceiling);
let debug = format!("{v:?}");
assert_eq!(debug.matches("Array(").count(), ceiling);
assert!(debug.contains("String(\"<redacted>\")"), "{debug}");
assert!(!debug.contains("<too deep>"));
dismantle(v);
}
#[test]
fn debug_of_a_deep_hand_built_value_is_bounded() {
let ceiling = super::super::Limits::MAX_DEPTH_CEILING;
let v = nested_arrays(100_000);
for debug in [format!("{v:?}"), format!("{v:#?}")] {
assert_eq!(debug.matches("Array(").count(), ceiling);
assert_eq!(debug.matches("<too deep>").count(), 1);
assert!(!debug.contains(SECRET));
}
dismantle(v);
let mut o = Object::new();
for _ in 0..200 {
let mut outer = Object::new();
outer.insert("x", o);
o = outer;
}
let debug = format!("{o:?}");
assert_eq!(debug.matches("Object(").count(), ceiling - 1);
assert_eq!(debug.matches("<too deep>").count(), 1);
}
#[test]
fn replaced_and_removed_members_come_back_zeroizing() {
let mut o = sample();
let old = o.insert("kty", "oct");
assert!(matches!(old, Some(Value::String(_))));
let removed = o.remove("kty");
assert_eq!(removed.as_ref().and_then(Value::as_str), Some("oct"));
}
}