use std::collections::BTreeMap;
use ciborium::Value;
use crate::metadata::RESERVED_KEY;
use crate::types::GlobalMetadata;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetaType {
Null,
Bool,
Int,
Float,
String,
Bytes,
Array,
Map,
}
#[derive(Debug, Clone, Copy)]
pub struct MetaValue<'a> {
inner: Ref<'a>,
}
#[derive(Debug, Clone, Copy)]
enum Ref<'a> {
Cbor(&'a Value),
Section(&'a BTreeMap<String, Value>),
}
impl<'a> MetaValue<'a> {
pub(crate) fn cbor(value: &'a Value) -> Self {
MetaValue {
inner: Ref::Cbor(value),
}
}
pub(crate) fn section(map: &'a BTreeMap<String, Value>) -> Self {
MetaValue {
inner: Ref::Section(map),
}
}
#[must_use]
pub fn value_type(&self) -> MetaType {
match self.inner {
Ref::Section(_) => MetaType::Map,
Ref::Cbor(v) => match v {
Value::Null => MetaType::Null,
Value::Bool(_) => MetaType::Bool,
Value::Integer(_) => MetaType::Int,
Value::Float(_) => MetaType::Float,
Value::Text(_) => MetaType::String,
Value::Bytes(_) => MetaType::Bytes,
Value::Array(_) => MetaType::Array,
Value::Map(_) => MetaType::Map,
_ => MetaType::Null,
},
}
}
#[must_use]
pub fn is_null(&self) -> bool {
self.value_type() == MetaType::Null
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match self.inner {
Ref::Cbor(Value::Bool(b)) => Some(*b),
_ => None,
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
match self.inner {
Ref::Cbor(Value::Integer(i)) => {
let n: i128 = (*i).into();
i64::try_from(n).ok()
}
_ => None,
}
}
#[must_use]
pub fn as_u64(&self) -> Option<u64> {
match self.inner {
Ref::Cbor(Value::Integer(i)) => {
let n: i128 = (*i).into();
u64::try_from(n).ok()
}
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
match self.inner {
Ref::Cbor(Value::Float(f)) => Some(*f),
Ref::Cbor(Value::Integer(i)) => {
let n: i128 = (*i).into();
Some(n as f64)
}
_ => None,
}
}
#[must_use]
pub fn as_str(&self) -> Option<&'a str> {
match self.inner {
Ref::Cbor(Value::Text(s)) => Some(s.as_str()),
_ => None,
}
}
#[must_use]
pub fn as_bytes(&self) -> Option<&'a [u8]> {
match self.inner {
Ref::Cbor(Value::Bytes(b)) => Some(b.as_slice()),
_ => None,
}
}
#[must_use]
pub fn len(&self) -> usize {
match self.inner {
Ref::Section(m) => m.len(),
Ref::Cbor(Value::Array(a)) => a.len(),
Ref::Cbor(Value::Map(m)) => m.len(),
_ => 0,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn get_index(&self, index: usize) -> Option<MetaValue<'a>> {
match self.inner {
Ref::Cbor(Value::Array(a)) => a.get(index).map(MetaValue::cbor),
_ => None,
}
}
#[must_use]
pub fn get_key(&self, key: &str) -> Option<MetaValue<'a>> {
match self.inner {
Ref::Section(m) => m.get(key).map(MetaValue::cbor),
Ref::Cbor(Value::Map(entries)) => entries.iter().find_map(|(k, v)| match k {
Value::Text(s) if s == key => Some(MetaValue::cbor(v)),
_ => None,
}),
_ => None,
}
}
#[must_use]
pub fn contains_key(&self, key: &str) -> bool {
self.get_key(key).is_some()
}
#[must_use]
pub fn key_at(&self, index: usize) -> Option<&'a str> {
match self.inner {
Ref::Section(m) => m.keys().nth(index).map(String::as_str),
Ref::Cbor(Value::Map(entries)) => entries.get(index).and_then(|(k, _)| match k {
Value::Text(s) => Some(s.as_str()),
_ => None,
}),
_ => None,
}
}
#[must_use]
pub fn value_at(&self, index: usize) -> Option<MetaValue<'a>> {
match self.inner {
Ref::Section(m) => m.values().nth(index).map(MetaValue::cbor),
Ref::Cbor(Value::Map(entries)) => entries.get(index).map(|(_, v)| MetaValue::cbor(v)),
_ => None,
}
}
}
impl GlobalMetadata {
#[must_use]
pub fn num_objects(&self) -> usize {
self.base.len()
}
#[must_use]
pub fn get_value(&self, path: &str) -> Option<&Value> {
message_level_lookup(self, path)
}
#[must_use]
pub fn get_value_at(&self, obj: usize, path: &str) -> Option<&Value> {
per_object_lookup(self, obj, path)
}
#[must_use]
pub fn get(&self, path: &str) -> Option<MetaValue<'_>> {
self.get_value(path).map(MetaValue::cbor)
}
#[must_use]
pub fn get_at(&self, obj: usize, path: &str) -> Option<MetaValue<'_>> {
self.get_value_at(obj, path).map(MetaValue::cbor)
}
#[must_use]
pub fn contains(&self, path: &str) -> bool {
self.get_value(path).is_some()
}
#[must_use]
pub fn contains_at(&self, obj: usize, path: &str) -> bool {
self.get_value_at(obj, path).is_some()
}
#[must_use]
pub fn object(&self, obj: usize) -> Option<MetaValue<'_>> {
self.base.get(obj).map(MetaValue::section)
}
#[must_use]
pub fn extra_view(&self) -> MetaValue<'_> {
MetaValue::section(&self.extra)
}
#[must_use]
pub fn reserved_view(&self) -> MetaValue<'_> {
MetaValue::section(&self.reserved)
}
}
fn message_level_lookup<'a>(meta: &'a GlobalMetadata, path: &str) -> Option<&'a Value> {
if path.is_empty() {
return None;
}
let parts: Vec<&str> = path.split('.').collect();
if parts[0].is_empty() {
return None;
}
if parts[0] == "_extra_" || parts[0] == "extra" {
if parts.len() > 1 {
return resolve_in_btree(&meta.extra, &parts[1..]);
}
return None;
}
for entry in &meta.base {
if let Some(v) = resolve_in_btree_skip_reserved(entry, &parts) {
return Some(v);
}
}
resolve_in_btree(&meta.extra, &parts)
}
fn per_object_lookup<'a>(meta: &'a GlobalMetadata, obj: usize, path: &str) -> Option<&'a Value> {
if path.is_empty() {
return None;
}
let parts: Vec<&str> = path.split('.').collect();
if parts[0].is_empty() {
return None;
}
let entry = meta.base.get(obj)?;
resolve_in_btree_skip_reserved(entry, &parts)
}
fn resolve_in_btree_skip_reserved<'a>(
map: &'a BTreeMap<String, Value>,
parts: &[&str],
) -> Option<&'a Value> {
let (first, rest) = parts.split_first()?;
if *first == RESERVED_KEY {
return None;
}
let value = map.get(*first)?;
resolve_cbor_path(value, rest)
}
fn resolve_in_btree<'a>(map: &'a BTreeMap<String, Value>, parts: &[&str]) -> Option<&'a Value> {
let (first, rest) = parts.split_first()?;
let value = map.get(*first)?;
resolve_cbor_path(value, rest)
}
fn resolve_cbor_path<'a>(value: &'a Value, remaining: &[&str]) -> Option<&'a Value> {
if remaining.is_empty() {
return Some(value);
}
if let Value::Map(entries) = value {
for (k, v) in entries {
if matches!(k, Value::Text(s) if s == remaining[0]) {
return resolve_cbor_path(v, &remaining[1..]);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(pairs: &[(&str, Value)]) -> BTreeMap<String, Value> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect()
}
fn map(pairs: &[(&str, Value)]) -> Value {
Value::Map(
pairs
.iter()
.map(|(k, v)| (Value::Text(k.to_string()), v.clone()))
.collect(),
)
}
#[test]
fn get_present_vs_absent_is_distinguishable() {
let meta = GlobalMetadata {
base: vec![entry(&[("count", Value::Integer(0.into()))])],
..Default::default()
};
assert!(meta.contains("count"));
assert_eq!(meta.get("count").unwrap().as_i64(), Some(0));
assert!(!meta.contains("missing"));
assert!(meta.get("missing").is_none());
}
#[test]
fn empty_string_is_present_not_absent() {
let meta = GlobalMetadata {
base: vec![entry(&[("name", Value::Text(String::new()))])],
..Default::default()
};
assert!(meta.contains("name"));
assert_eq!(meta.get("name").unwrap().as_str(), Some(""));
}
#[test]
fn typed_accessors_are_precise_no_coercion() {
let meta = GlobalMetadata {
base: vec![entry(&[
("s", Value::Text("2t".into())),
("i", Value::Integer(42.into())),
("f", Value::Float(2.5)),
("b", Value::Bool(true)),
])],
..Default::default()
};
assert_eq!(meta.get("i").unwrap().as_i64(), Some(42));
assert_eq!(meta.get("i").unwrap().as_f64(), Some(42.0)); assert_eq!(meta.get("i").unwrap().as_str(), None); assert_eq!(meta.get("s").unwrap().as_i64(), None);
assert_eq!(meta.get("f").unwrap().as_f64(), Some(2.5));
assert_eq!(meta.get("f").unwrap().as_i64(), None);
assert_eq!(meta.get("b").unwrap().as_bool(), Some(true));
assert_eq!(meta.get("i").unwrap().value_type(), MetaType::Int);
assert_eq!(meta.get("s").unwrap().value_type(), MetaType::String);
}
#[test]
fn nested_map_navigation() {
let meta = GlobalMetadata {
base: vec![entry(&[(
"mars",
map(&[("class", Value::Text("od".into()))]),
)])],
..Default::default()
};
assert_eq!(meta.get("mars.class").unwrap().as_str(), Some("od"));
let mars = meta.get("mars").unwrap();
assert_eq!(mars.value_type(), MetaType::Map);
assert_eq!(mars.get_key("class").unwrap().as_str(), Some("od"));
assert!(mars.contains_key("class"));
assert_eq!(mars.key_at(0), Some("class"));
}
#[test]
fn array_navigation() {
let arr = Value::Array(vec![
Value::Integer(1.into()),
Value::Integer(2.into()),
Value::Integer(3.into()),
]);
let meta = GlobalMetadata {
base: vec![entry(&[("shape", arr)])],
..Default::default()
};
let shape = meta.get("shape").unwrap();
assert_eq!(shape.value_type(), MetaType::Array);
assert_eq!(shape.len(), 3);
assert_eq!(shape.get_index(1).unwrap().as_i64(), Some(2));
assert!(shape.get_index(3).is_none());
}
#[test]
fn first_match_then_extra_fallback() {
let meta = GlobalMetadata {
base: vec![
entry(&[("p", Value::Text("first".into()))]),
entry(&[("p", Value::Text("second".into()))]),
],
extra: entry(&[("only_extra", Value::Text("x".into()))]),
..Default::default()
};
assert_eq!(meta.get("p").unwrap().as_str(), Some("first"));
assert_eq!(meta.get("only_extra").unwrap().as_str(), Some("x"));
assert_eq!(meta.get("extra.only_extra").unwrap().as_str(), Some("x"));
}
#[test]
fn per_object_scoping_and_reserved_rules() {
let meta = GlobalMetadata {
base: vec![
entry(&[("p", Value::Text("a".into()))]),
entry(&[
("p", Value::Text("b".into())),
(RESERVED_KEY, map(&[("tensor", Value::Text("t".into()))])),
]),
],
..Default::default()
};
assert_eq!(meta.get_at(0, "p").unwrap().as_str(), Some("a"));
assert_eq!(meta.get_at(1, "p").unwrap().as_str(), Some("b"));
assert!(meta.get_at(2, "p").is_none()); assert!(!meta.contains_at(1, "_reserved_.tensor"));
let obj1 = meta.object(1).unwrap();
assert!(obj1.contains_key(RESERVED_KEY));
assert!(obj1.contains_key("p"));
}
#[test]
fn section_views_enumerate() {
let meta = GlobalMetadata {
extra: entry(&[
("a", Value::Integer(1.into())),
("b", Value::Integer(2.into())),
]),
..Default::default()
};
let extra = meta.extra_view();
assert_eq!(extra.value_type(), MetaType::Map);
assert_eq!(extra.len(), 2);
assert_eq!(extra.key_at(0), Some("a"));
assert_eq!(extra.value_at(1).unwrap().as_i64(), Some(2));
assert_eq!(extra.get_key("b").unwrap().as_i64(), Some(2));
}
#[test]
fn version_is_a_plain_key_not_a_pseudo_key() {
let empty = GlobalMetadata::default();
assert!(empty.get("version").is_none());
assert!(!empty.contains("version"));
let meta = GlobalMetadata {
base: vec![entry(&[("version", Value::Text("1.2".into()))])],
..Default::default()
};
assert!(meta.contains("version"));
assert_eq!(meta.get("version").unwrap().as_str(), Some("1.2"));
assert_eq!(meta.get_at(0, "version").unwrap().as_str(), Some("1.2"));
}
#[test]
fn bytes_extraction_and_wrong_type() {
let meta = GlobalMetadata {
base: vec![entry(&[
("raw", Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF])),
("empty", Value::Bytes(vec![])),
("s", Value::Text("x".into())),
])],
..Default::default()
};
let raw = meta.get("raw").unwrap();
assert_eq!(raw.value_type(), MetaType::Bytes);
assert_eq!(raw.as_bytes(), Some([0xDE, 0xAD, 0xBE, 0xEF].as_slice()));
assert_eq!(meta.get("empty").unwrap().as_bytes(), Some([].as_slice()));
assert_eq!(meta.get("s").unwrap().as_bytes(), None);
assert_eq!(raw.as_str(), None);
}
#[test]
fn integer_range_boundaries() {
let meta = GlobalMetadata {
base: vec![entry(&[
("huge", Value::Integer(u64::MAX.into())), ("neg", Value::Integer((-1i64).into())),
("max_i64", Value::Integer(i64::MAX.into())),
])],
..Default::default()
};
assert_eq!(meta.get("huge").unwrap().as_u64(), Some(u64::MAX));
assert_eq!(meta.get("huge").unwrap().as_i64(), None);
assert_eq!(meta.get("neg").unwrap().as_i64(), Some(-1));
assert_eq!(meta.get("neg").unwrap().as_u64(), None);
assert_eq!(meta.get("max_i64").unwrap().as_i64(), Some(i64::MAX));
assert_eq!(meta.get("max_i64").unwrap().as_u64(), Some(i64::MAX as u64));
assert!(meta.get("huge").unwrap().as_f64().unwrap() > 1e19);
}
#[test]
fn interior_nul_string_is_preserved() {
let meta = GlobalMetadata {
base: vec![entry(&[("s", Value::Text("a\0b".into()))])],
..Default::default()
};
let s = meta.get("s").unwrap().as_str().unwrap();
assert_eq!(s, "a\0b");
assert_eq!(s.len(), 3); }
#[test]
fn array_of_maps_and_map_of_arrays() {
let meta = GlobalMetadata {
base: vec![entry(&[
(
"items",
Value::Array(vec![
map(&[("id", Value::Integer(1.into()))]),
map(&[("id", Value::Integer(2.into()))]),
]),
),
(
"grid",
map(&[(
"shape",
Value::Array(vec![Value::Integer(2.into()), Value::Integer(3.into())]),
)]),
),
])],
..Default::default()
};
let items = meta.get("items").unwrap();
assert_eq!(items.len(), 2);
assert_eq!(
items.get_index(0).unwrap().get_key("id").unwrap().as_i64(),
Some(1)
);
assert_eq!(
items.get_index(1).unwrap().get_key("id").unwrap().as_i64(),
Some(2)
);
let shape = meta.get("grid.shape").unwrap();
assert_eq!(shape.value_type(), MetaType::Array);
assert_eq!(shape.get_index(1).unwrap().as_i64(), Some(3));
assert!(meta.get("items.0").is_none());
}
#[test]
fn is_null_agrees_with_value_type_for_exotic_kinds() {
let null = Value::Null;
assert_eq!(MetaValue::cbor(&null).value_type(), MetaType::Null);
assert!(MetaValue::cbor(&null).is_null());
let tagged = Value::Tag(0, Box::new(Value::Text("x".into())));
assert_eq!(MetaValue::cbor(&tagged).value_type(), MetaType::Null);
assert!(MetaValue::cbor(&tagged).is_null());
let s = Value::Text("x".into());
assert!(!MetaValue::cbor(&s).is_null());
}
}