use base64::{engine::general_purpose::STANDARD, Engine as _};
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::io::Read;
use crate::error::{Error, Result};
#[derive(Default)]
pub enum Value {
#[default]
Null,
Bool(bool),
Integer(i64),
Float(f64),
String(String),
Bytes(Vec<u8>),
Stream(Box<dyn Read + Send + Sync>),
Sequence(Vec<Value>),
Mapping(IndexMap<String, Value>),
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, "Null"),
Value::Bool(b) => f.debug_tuple("Bool").field(b).finish(),
Value::Integer(i) => f.debug_tuple("Integer").field(i).finish(),
Value::Float(fl) => f.debug_tuple("Float").field(fl).finish(),
Value::String(s) => f.debug_tuple("String").field(s).finish(),
Value::Bytes(bytes) => f.debug_tuple("Bytes").field(bytes).finish(),
Value::Stream(_) => write!(f, "Stream(<stream>)"),
Value::Sequence(seq) => f.debug_tuple("Sequence").field(seq).finish(),
Value::Mapping(map) => f.debug_tuple("Mapping").field(map).finish(),
}
}
}
impl Clone for Value {
fn clone(&self) -> Self {
match self {
Value::Null => Value::Null,
Value::Bool(b) => Value::Bool(*b),
Value::Integer(i) => Value::Integer(*i),
Value::Float(f) => Value::Float(*f),
Value::String(s) => Value::String(s.clone()),
Value::Bytes(bytes) => Value::Bytes(bytes.clone()),
Value::Stream(_) => {
panic!("Cannot clone Value::Stream - must materialize() before cloning")
}
Value::Sequence(seq) => Value::Sequence(seq.clone()),
Value::Mapping(map) => Value::Mapping(map.clone()),
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Null, Value::Null) => true,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Integer(a), Value::Integer(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Bytes(a), Value::Bytes(b)) => a == b,
(Value::Stream(_), Value::Stream(_)) => {
panic!("Cannot compare Value::Stream - must materialize() first")
}
(Value::Sequence(a), Value::Sequence(b)) => a == b,
(Value::Mapping(a), Value::Mapping(b)) => a == b,
_ => false,
}
}
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Null => serializer.serialize_none(),
Value::Bool(b) => serializer.serialize_bool(*b),
Value::Integer(i) => serializer.serialize_i64(*i),
Value::Float(f) => serializer.serialize_f64(*f),
Value::String(s) => serializer.serialize_str(s),
Value::Bytes(bytes) => {
let encoded = STANDARD.encode(bytes);
serializer.serialize_str(&encoded)
}
Value::Stream(_) => {
Err(serde::ser::Error::custom(
"Cannot serialize Value::Stream - must materialize() first",
))
}
Value::Sequence(seq) => seq.serialize(serializer),
Value::Mapping(map) => map.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum ValueHelper {
Null,
Bool(bool),
Integer(i64),
Float(f64),
String(String),
Sequence(Vec<Value>),
Mapping(IndexMap<String, Value>),
}
match ValueHelper::deserialize(deserializer)? {
ValueHelper::Null => Ok(Value::Null),
ValueHelper::Bool(b) => Ok(Value::Bool(b)),
ValueHelper::Integer(i) => Ok(Value::Integer(i)),
ValueHelper::Float(f) => Ok(Value::Float(f)),
ValueHelper::String(s) => Ok(Value::String(s)),
ValueHelper::Sequence(seq) => Ok(Value::Sequence(seq)),
ValueHelper::Mapping(map) => Ok(Value::Mapping(map)),
}
}
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
pub fn is_integer(&self) -> bool {
matches!(self, Value::Integer(_))
}
pub fn is_float(&self) -> bool {
matches!(self, Value::Float(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub fn is_sequence(&self) -> bool {
matches!(self, Value::Sequence(_))
}
pub fn is_mapping(&self) -> bool {
matches!(self, Value::Mapping(_))
}
pub fn is_bytes(&self) -> bool {
matches!(self, Value::Bytes(_))
}
pub fn is_stream(&self) -> bool {
matches!(self, Value::Stream(_))
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Value::Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Float(f) => Some(*f),
Value::Integer(i) => Some(*i as f64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_sequence(&self) -> Option<&[Value]> {
match self {
Value::Sequence(s) => Some(s),
_ => None,
}
}
pub fn as_mapping(&self) -> Option<&IndexMap<String, Value>> {
match self {
Value::Mapping(m) => Some(m),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
Value::Bytes(b) => Some(b),
_ => None,
}
}
pub fn get_path(&self, path: &str) -> Result<&Value> {
if path.is_empty() {
return Ok(self);
}
let segments = parse_path(path)?;
let mut current = self;
for segment in &segments {
current = match segment {
PathSegment::Key(key) => match current {
Value::Mapping(map) => map
.get(key.as_str())
.ok_or_else(|| Error::path_not_found(path))?,
_ => return Err(Error::path_not_found(path)),
},
PathSegment::Index(idx) => match current {
Value::Sequence(seq) => {
seq.get(*idx).ok_or_else(|| Error::path_not_found(path))?
}
_ => return Err(Error::path_not_found(path)),
},
};
}
Ok(current)
}
pub fn get_path_mut(&mut self, path: &str) -> Result<&mut Value> {
if path.is_empty() {
return Ok(self);
}
let segments = parse_path(path)?;
let mut current = self;
for segment in segments {
current = match segment {
PathSegment::Key(key) => match current {
Value::Mapping(map) => map
.get_mut(&key)
.ok_or_else(|| Error::path_not_found(path))?,
_ => return Err(Error::path_not_found(path)),
},
PathSegment::Index(idx) => match current {
Value::Sequence(seq) => seq
.get_mut(idx)
.ok_or_else(|| Error::path_not_found(path))?,
_ => return Err(Error::path_not_found(path)),
},
};
}
Ok(current)
}
pub fn set_path(&mut self, path: &str, value: Value) -> Result<()> {
if path.is_empty() {
*self = value;
return Ok(());
}
let segments = parse_path(path)?;
let mut current = self;
for (i, segment) in segments.iter().enumerate() {
let is_last = i == segments.len() - 1;
if is_last {
match segment {
PathSegment::Key(key) => {
if let Value::Mapping(map) = current {
map.insert(key.clone(), value);
return Ok(());
}
return Err(Error::path_not_found(path));
}
PathSegment::Index(idx) => {
if let Value::Sequence(seq) = current {
if *idx < seq.len() {
seq[*idx] = value;
return Ok(());
}
}
return Err(Error::path_not_found(path));
}
}
}
current = match segment {
PathSegment::Key(key) => {
if let Value::Mapping(map) = current {
let next_is_index = segments
.get(i + 1)
.map(|s| matches!(s, PathSegment::Index(_)))
.unwrap_or(false);
if !map.contains_key(key) {
let new_value = if next_is_index {
Value::Sequence(vec![])
} else {
Value::Mapping(IndexMap::new())
};
map.insert(key.clone(), new_value);
}
map.get_mut(key).unwrap()
} else {
return Err(Error::path_not_found(path));
}
}
PathSegment::Index(idx) => {
if let Value::Sequence(seq) = current {
seq.get_mut(*idx)
.ok_or_else(|| Error::path_not_found(path))?
} else {
return Err(Error::path_not_found(path));
}
}
};
}
Ok(())
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Integer(_) => "integer",
Value::Float(_) => "float",
Value::String(_) => "string",
Value::Bytes(_) => "bytes",
Value::Stream(_) => "stream",
Value::Sequence(_) => "sequence",
Value::Mapping(_) => "mapping",
}
}
pub fn materialize(self) -> Result<Value> {
match self {
Value::Stream(mut reader) => {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).map_err(|e| {
Error::resolver_custom("stream", format!("Failed to read stream: {}", e))
})?;
Ok(Value::Bytes(bytes))
}
other => Ok(other),
}
}
pub fn merge(&mut self, other: Value) {
match (self, other) {
(Value::Mapping(base), Value::Mapping(overlay)) => {
for (key, overlay_value) in overlay {
if overlay_value.is_null() {
base.shift_remove(&key);
} else if let Some(base_value) = base.get_mut(&key) {
base_value.merge(overlay_value);
} else {
base.insert(key, overlay_value);
}
}
}
(this, other) => {
*this = other;
}
}
}
pub fn merged(mut self, other: Value) -> Value {
self.merge(other);
self
}
pub fn merge_tracking_sources(
&mut self,
other: Value,
source: &str,
path_prefix: &str,
sources: &mut std::collections::HashMap<String, String>,
) {
match (self, other) {
(Value::Mapping(base), Value::Mapping(overlay)) => {
for (key, overlay_value) in overlay {
let full_path = if path_prefix.is_empty() {
key.clone()
} else {
format!("{}.{}", path_prefix, key)
};
if overlay_value.is_null() {
base.shift_remove(&key);
remove_sources_with_prefix(sources, &full_path);
} else if let Some(base_value) = base.get_mut(&key) {
base_value.merge_tracking_sources(
overlay_value,
source,
&full_path,
sources,
);
} else {
collect_leaf_paths(&overlay_value, &full_path, source, sources);
base.insert(key, overlay_value);
}
}
}
(this, other) => {
remove_sources_with_prefix(sources, path_prefix);
collect_leaf_paths(&other, path_prefix, source, sources);
*this = other;
}
}
}
pub fn collect_leaf_paths(
&self,
path_prefix: &str,
source: &str,
sources: &mut std::collections::HashMap<String, String>,
) {
collect_leaf_paths(self, path_prefix, source, sources);
}
}
fn collect_leaf_paths(
value: &Value,
path_prefix: &str,
source: &str,
sources: &mut std::collections::HashMap<String, String>,
) {
match value {
Value::Mapping(map) => {
for (key, val) in map {
let full_path = if path_prefix.is_empty() {
key.clone()
} else {
format!("{}.{}", path_prefix, key)
};
collect_leaf_paths(val, &full_path, source, sources);
}
}
Value::Sequence(seq) => {
for (i, val) in seq.iter().enumerate() {
let full_path = format!("{}[{}]", path_prefix, i);
collect_leaf_paths(val, &full_path, source, sources);
}
}
_ => {
if !path_prefix.is_empty() {
sources.insert(path_prefix.to_string(), source.to_string());
}
}
}
}
fn remove_sources_with_prefix(
sources: &mut std::collections::HashMap<String, String>,
prefix: &str,
) {
if prefix.is_empty() {
sources.clear();
return;
}
sources.retain(|path, _| {
path != prefix
&& !path.starts_with(&format!("{}.", prefix))
&& !path.starts_with(&format!("{}[", prefix))
});
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, "null"),
Value::Bool(b) => write!(f, "{}", b),
Value::Integer(i) => write!(f, "{}", i),
Value::Float(n) => write!(f, "{}", n),
Value::String(s) => write!(f, "{}", s),
Value::Bytes(bytes) => write!(f, "<bytes: {} bytes>", bytes.len()),
Value::Stream(_) => write!(f, "<stream>"),
Value::Sequence(seq) => {
write!(f, "[")?;
for (i, v) in seq.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, "]")
}
Value::Mapping(map) => {
write!(f, "{{")?;
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}: {}", k, v)?;
}
write!(f, "}}")
}
}
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Bool(b)
}
}
impl From<i64> for Value {
fn from(i: i64) -> Self {
Value::Integer(i)
}
}
impl From<i32> for Value {
fn from(i: i32) -> Self {
Value::Integer(i as i64)
}
}
impl From<f64> for Value {
fn from(f: f64) -> Self {
Value::Float(f)
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(s)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::String(s.to_string())
}
}
impl<T: Into<Value>> From<Vec<T>> for Value {
fn from(v: Vec<T>) -> Self {
Value::Sequence(v.into_iter().map(Into::into).collect())
}
}
impl From<IndexMap<String, Value>> for Value {
fn from(m: IndexMap<String, Value>) -> Self {
Value::Mapping(m)
}
}
impl From<Vec<u8>> for Value {
fn from(bytes: Vec<u8>) -> Self {
Value::Bytes(bytes)
}
}
#[derive(Debug, Clone, PartialEq)]
enum PathSegment {
Key(String),
Index(usize),
}
fn parse_path(path: &str) -> Result<Vec<PathSegment>> {
let mut segments = Vec::new();
let mut current_key = String::new();
let mut chars = path.chars().peekable();
while let Some(c) = chars.next() {
match c {
'.' => {
if !current_key.is_empty() {
segments.push(PathSegment::Key(current_key.clone()));
current_key.clear();
}
}
'[' => {
if !current_key.is_empty() {
segments.push(PathSegment::Key(current_key.clone()));
current_key.clear();
}
let mut index_str = String::new();
while let Some(&c) = chars.peek() {
if c == ']' {
chars.next();
break;
}
index_str.push(chars.next().unwrap());
}
let idx: usize = index_str.parse().map_err(|_| {
Error::parse(format!("Invalid array index in path: {}", index_str))
})?;
segments.push(PathSegment::Index(idx));
}
']' => {
return Err(Error::parse("Unexpected ']' in path"));
}
_ => {
current_key.push(c);
}
}
}
if !current_key.is_empty() {
segments.push(PathSegment::Key(current_key));
}
Ok(segments)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_path() {
let segments = parse_path("database").unwrap();
assert_eq!(segments, vec![PathSegment::Key("database".into())]);
}
#[test]
fn test_parse_dotted_path() {
let segments = parse_path("database.host").unwrap();
assert_eq!(
segments,
vec![
PathSegment::Key("database".into()),
PathSegment::Key("host".into())
]
);
}
#[test]
fn test_parse_array_path() {
let segments = parse_path("servers[0]").unwrap();
assert_eq!(
segments,
vec![PathSegment::Key("servers".into()), PathSegment::Index(0)]
);
}
#[test]
fn test_parse_complex_path() {
let segments = parse_path("servers[0].host").unwrap();
assert_eq!(
segments,
vec![
PathSegment::Key("servers".into()),
PathSegment::Index(0),
PathSegment::Key("host".into())
]
);
}
#[test]
fn test_value_get_path() {
let mut map = IndexMap::new();
let mut db = IndexMap::new();
db.insert("host".into(), Value::String("localhost".into()));
db.insert("port".into(), Value::Integer(5432));
map.insert("database".into(), Value::Mapping(db));
let value = Value::Mapping(map);
assert_eq!(
value.get_path("database.host").unwrap().as_str(),
Some("localhost")
);
assert_eq!(
value.get_path("database.port").unwrap().as_i64(),
Some(5432)
);
}
#[test]
fn test_value_get_path_array() {
let mut map = IndexMap::new();
map.insert(
"servers".into(),
Value::Sequence(vec![
Value::String("server1".into()),
Value::String("server2".into()),
]),
);
let value = Value::Mapping(map);
assert_eq!(
value.get_path("servers[0]").unwrap().as_str(),
Some("server1")
);
assert_eq!(
value.get_path("servers[1]").unwrap().as_str(),
Some("server2")
);
}
#[test]
fn test_value_get_path_not_found() {
let map = IndexMap::new();
let value = Value::Mapping(map);
assert!(value.get_path("nonexistent").is_err());
}
#[test]
fn test_value_type_checks() {
assert!(Value::Null.is_null());
assert!(Value::Bool(true).is_bool());
assert!(Value::Integer(42).is_integer());
assert!(Value::Float(2.5).is_float());
assert!(Value::String("hello".into()).is_string());
assert!(Value::Sequence(vec![]).is_sequence());
assert!(Value::Mapping(IndexMap::new()).is_mapping());
}
#[test]
fn test_value_conversions() {
assert_eq!(Value::Bool(true).as_bool(), Some(true));
assert_eq!(Value::Integer(42).as_i64(), Some(42));
assert_eq!(Value::Float(2.5).as_f64(), Some(2.5));
assert_eq!(Value::Integer(42).as_f64(), Some(42.0));
assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
}
#[test]
fn test_merge_scalars() {
let mut base = Value::String("base".into());
base.merge(Value::String("overlay".into()));
assert_eq!(base.as_str(), Some("overlay"));
}
#[test]
fn test_merge_deep() {
let mut db_base = IndexMap::new();
db_base.insert("host".into(), Value::String("localhost".into()));
db_base.insert("port".into(), Value::Integer(5432));
let mut base = IndexMap::new();
base.insert("database".into(), Value::Mapping(db_base));
let mut base = Value::Mapping(base);
let mut db_overlay = IndexMap::new();
db_overlay.insert("host".into(), Value::String("prod-db".into()));
let mut overlay = IndexMap::new();
overlay.insert("database".into(), Value::Mapping(db_overlay));
let overlay = Value::Mapping(overlay);
base.merge(overlay);
assert_eq!(
base.get_path("database.host").unwrap().as_str(),
Some("prod-db")
);
assert_eq!(base.get_path("database.port").unwrap().as_i64(), Some(5432));
}
#[test]
fn test_merge_null_removes_key() {
let mut feature = IndexMap::new();
feature.insert("enabled".into(), Value::Bool(true));
feature.insert("config".into(), Value::String("value".into()));
let mut base = IndexMap::new();
base.insert("feature".into(), Value::Mapping(feature));
let mut base = Value::Mapping(base);
let mut feature_overlay = IndexMap::new();
feature_overlay.insert("config".into(), Value::Null);
let mut overlay = IndexMap::new();
overlay.insert("feature".into(), Value::Mapping(feature_overlay));
let overlay = Value::Mapping(overlay);
base.merge(overlay);
assert_eq!(
base.get_path("feature.enabled").unwrap().as_bool(),
Some(true)
);
assert!(base.get_path("feature.config").is_err());
}
#[test]
fn test_merge_array_replaces() {
let mut base = IndexMap::new();
base.insert(
"servers".into(),
Value::Sequence(vec![Value::String("a".into()), Value::String("b".into())]),
);
let mut base = Value::Mapping(base);
let mut overlay = IndexMap::new();
overlay.insert(
"servers".into(),
Value::Sequence(vec![Value::String("c".into())]),
);
let overlay = Value::Mapping(overlay);
base.merge(overlay);
let servers = base.get_path("servers").unwrap().as_sequence().unwrap();
assert_eq!(servers.len(), 1);
assert_eq!(servers[0].as_str(), Some("c"));
}
#[test]
fn test_merge_type_mismatch() {
let mut db = IndexMap::new();
db.insert("host".into(), Value::String("localhost".into()));
let mut base = IndexMap::new();
base.insert("database".into(), Value::Mapping(db));
let mut base = Value::Mapping(base);
let mut overlay = IndexMap::new();
overlay.insert("database".into(), Value::String("connection-string".into()));
let overlay = Value::Mapping(overlay);
base.merge(overlay);
assert_eq!(
base.get_path("database").unwrap().as_str(),
Some("connection-string")
);
}
#[test]
fn test_merge_adds_new_keys() {
let mut base = IndexMap::new();
base.insert("a".into(), Value::Integer(1));
let mut base = Value::Mapping(base);
let mut overlay = IndexMap::new();
overlay.insert("b".into(), Value::Integer(2));
let overlay = Value::Mapping(overlay);
base.merge(overlay);
assert_eq!(base.get_path("a").unwrap().as_i64(), Some(1));
assert_eq!(base.get_path("b").unwrap().as_i64(), Some(2));
}
#[test]
fn test_from_i32() {
let v: Value = 42i32.into();
assert_eq!(v, Value::Integer(42));
}
#[test]
fn test_from_vec_values() {
let vec: Vec<Value> = vec![Value::Integer(1), Value::Integer(2)];
let v: Value = vec.into();
assert!(v.is_sequence());
assert_eq!(v.as_sequence().unwrap().len(), 2);
}
#[test]
fn test_from_vec_strings() {
let vec: Vec<&str> = vec!["a", "b", "c"];
let v: Value = vec.into();
assert!(v.is_sequence());
assert_eq!(v.as_sequence().unwrap().len(), 3);
}
#[test]
fn test_from_indexmap() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let v: Value = map.into();
assert!(v.is_mapping());
}
#[test]
fn test_display_null() {
assert_eq!(format!("{}", Value::Null), "null");
}
#[test]
fn test_display_bool() {
assert_eq!(format!("{}", Value::Bool(true)), "true");
assert_eq!(format!("{}", Value::Bool(false)), "false");
}
#[test]
fn test_display_integer() {
assert_eq!(format!("{}", Value::Integer(42)), "42");
assert_eq!(format!("{}", Value::Integer(-123)), "-123");
}
#[test]
fn test_display_float() {
let display = format!("{}", Value::Float(1.5));
assert!(display.starts_with("1.5"));
}
#[test]
fn test_display_string() {
assert_eq!(format!("{}", Value::String("hello".into())), "hello");
}
#[test]
fn test_display_sequence() {
let seq = Value::Sequence(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
]);
assert_eq!(format!("{}", seq), "[1, 2, 3]");
}
#[test]
fn test_display_empty_sequence() {
let seq = Value::Sequence(vec![]);
assert_eq!(format!("{}", seq), "[]");
}
#[test]
fn test_display_mapping() {
let mut map = IndexMap::new();
map.insert("a".to_string(), Value::Integer(1));
let mapping = Value::Mapping(map);
assert_eq!(format!("{}", mapping), "{a: 1}");
}
#[test]
fn test_display_empty_mapping() {
let mapping = Value::Mapping(IndexMap::new());
assert_eq!(format!("{}", mapping), "{}");
}
#[test]
fn test_as_str_non_string() {
assert!(Value::Integer(42).as_str().is_none());
assert!(Value::Bool(true).as_str().is_none());
assert!(Value::Null.as_str().is_none());
}
#[test]
fn test_as_bool_non_bool() {
assert!(Value::Integer(42).as_bool().is_none());
assert!(Value::String("true".into()).as_bool().is_none());
}
#[test]
fn test_as_i64_non_integer() {
assert!(Value::String("42".into()).as_i64().is_none());
assert!(Value::Float(42.0).as_i64().is_none());
}
#[test]
fn test_as_f64_non_numeric() {
assert!(Value::String("3.14".into()).as_f64().is_none());
assert!(Value::Bool(true).as_f64().is_none());
}
#[test]
fn test_as_sequence_non_sequence() {
assert!(Value::Integer(42).as_sequence().is_none());
assert!(Value::Mapping(IndexMap::new()).as_sequence().is_none());
}
#[test]
fn test_as_mapping_non_mapping() {
assert!(Value::Integer(42).as_mapping().is_none());
assert!(Value::Sequence(vec![]).as_mapping().is_none());
}
#[test]
fn test_type_name() {
assert_eq!(Value::Null.type_name(), "null");
assert_eq!(Value::Bool(true).type_name(), "boolean");
assert_eq!(Value::Integer(42).type_name(), "integer");
assert_eq!(Value::Float(1.23).type_name(), "float");
assert_eq!(Value::String("s".into()).type_name(), "string");
assert_eq!(Value::Sequence(vec![]).type_name(), "sequence");
assert_eq!(Value::Mapping(IndexMap::new()).type_name(), "mapping");
}
#[test]
fn test_default() {
let v: Value = Default::default();
assert!(v.is_null());
}
#[test]
fn test_get_path_empty() {
let v = Value::Integer(42);
assert_eq!(v.get_path("").unwrap(), &Value::Integer(42));
}
#[test]
fn test_get_path_on_non_mapping() {
let v = Value::Integer(42);
assert!(v.get_path("key").is_err());
}
#[test]
fn test_get_path_array_out_of_bounds() {
let mut map = IndexMap::new();
map.insert(
"items".to_string(),
Value::Sequence(vec![Value::Integer(1)]),
);
let v = Value::Mapping(map);
assert!(v.get_path("items[99]").is_err());
}
#[test]
fn test_get_path_array_on_non_sequence() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let v = Value::Mapping(map);
assert!(v.get_path("key[0]").is_err());
}
#[test]
fn test_get_path_mut_empty() {
let mut v = Value::Integer(42);
let result = v.get_path_mut("").unwrap();
*result = Value::Integer(100);
assert_eq!(v, Value::Integer(100));
}
#[test]
fn test_get_path_mut_modify() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let mut v = Value::Mapping(map);
*v.get_path_mut("key").unwrap() = Value::Integer(100);
assert_eq!(v.get_path("key").unwrap().as_i64(), Some(100));
}
#[test]
fn test_get_path_mut_not_found() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let mut v = Value::Mapping(map);
assert!(v.get_path_mut("nonexistent").is_err());
}
#[test]
fn test_get_path_mut_on_non_mapping() {
let mut v = Value::Integer(42);
assert!(v.get_path_mut("key").is_err());
}
#[test]
fn test_get_path_mut_array() {
let mut map = IndexMap::new();
map.insert(
"items".to_string(),
Value::Sequence(vec![Value::Integer(1), Value::Integer(2)]),
);
let mut v = Value::Mapping(map);
*v.get_path_mut("items[1]").unwrap() = Value::Integer(99);
assert_eq!(v.get_path("items[1]").unwrap().as_i64(), Some(99));
}
#[test]
fn test_get_path_mut_array_out_of_bounds() {
let mut map = IndexMap::new();
map.insert(
"items".to_string(),
Value::Sequence(vec![Value::Integer(1)]),
);
let mut v = Value::Mapping(map);
assert!(v.get_path_mut("items[99]").is_err());
}
#[test]
fn test_get_path_mut_array_on_non_sequence() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let mut v = Value::Mapping(map);
assert!(v.get_path_mut("key[0]").is_err());
}
#[test]
fn test_set_path_empty() {
let mut v = Value::Integer(42);
v.set_path("", Value::Integer(100)).unwrap();
assert_eq!(v, Value::Integer(100));
}
#[test]
fn test_set_path_simple() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let mut v = Value::Mapping(map);
v.set_path("key", Value::Integer(100)).unwrap();
assert_eq!(v.get_path("key").unwrap().as_i64(), Some(100));
}
#[test]
fn test_set_path_new_key() {
let mut map = IndexMap::new();
map.insert("existing".to_string(), Value::Integer(1));
let mut v = Value::Mapping(map);
v.set_path("new_key", Value::Integer(42)).unwrap();
assert_eq!(v.get_path("new_key").unwrap().as_i64(), Some(42));
}
#[test]
fn test_set_path_creates_intermediate_mappings() {
let mut v = Value::Mapping(IndexMap::new());
v.set_path("a.b.c", Value::Integer(42)).unwrap();
assert_eq!(v.get_path("a.b.c").unwrap().as_i64(), Some(42));
}
#[test]
fn test_set_path_array_element() {
let mut map = IndexMap::new();
map.insert(
"items".to_string(),
Value::Sequence(vec![Value::Integer(1), Value::Integer(2)]),
);
let mut v = Value::Mapping(map);
v.set_path("items[0]", Value::Integer(99)).unwrap();
assert_eq!(v.get_path("items[0]").unwrap().as_i64(), Some(99));
}
#[test]
fn test_set_path_array_out_of_bounds() {
let mut map = IndexMap::new();
map.insert(
"items".to_string(),
Value::Sequence(vec![Value::Integer(1)]),
);
let mut v = Value::Mapping(map);
assert!(v.set_path("items[99]", Value::Integer(42)).is_err());
}
#[test]
fn test_set_path_on_non_mapping() {
let mut v = Value::Integer(42);
assert!(v.set_path("key", Value::Integer(1)).is_err());
}
#[test]
fn test_set_path_key_on_non_mapping_intermediate() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let mut v = Value::Mapping(map);
assert!(v.set_path("key.subkey", Value::Integer(1)).is_err());
}
#[test]
fn test_set_path_index_on_non_sequence() {
let mut map = IndexMap::new();
map.insert("key".to_string(), Value::Integer(42));
let mut v = Value::Mapping(map);
assert!(v.set_path("key[0]", Value::Integer(1)).is_err());
}
#[test]
fn test_set_path_intermediate_index_out_of_bounds() {
let mut map = IndexMap::new();
map.insert(
"items".to_string(),
Value::Sequence(vec![Value::Mapping(IndexMap::new())]),
);
let mut v = Value::Mapping(map);
assert!(v.set_path("items[99].key", Value::Integer(1)).is_err());
}
#[test]
fn test_merged_non_mutating() {
let mut base_map = IndexMap::new();
base_map.insert("a".to_string(), Value::Integer(1));
let base = Value::Mapping(base_map);
let mut overlay_map = IndexMap::new();
overlay_map.insert("b".to_string(), Value::Integer(2));
let overlay = Value::Mapping(overlay_map);
let result = base.merged(overlay);
assert_eq!(result.get_path("a").unwrap().as_i64(), Some(1));
assert_eq!(result.get_path("b").unwrap().as_i64(), Some(2));
}
#[test]
fn test_parse_path_invalid_index() {
assert!(parse_path("items[abc]").is_err());
}
#[test]
fn test_parse_path_unexpected_bracket() {
assert!(parse_path("items]").is_err());
}
#[test]
fn test_is_type_negative_cases() {
let integer = Value::Integer(42);
assert!(!integer.is_null());
assert!(!integer.is_bool());
assert!(!integer.is_float());
assert!(!integer.is_string());
assert!(!integer.is_sequence());
assert!(!integer.is_mapping());
assert!(!integer.is_bytes());
}
#[test]
fn test_bytes_is_bytes() {
let bytes = Value::Bytes(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]); assert!(bytes.is_bytes());
assert!(!bytes.is_string());
assert!(!bytes.is_null());
}
#[test]
fn test_bytes_as_bytes() {
let bytes = Value::Bytes(vec![1, 2, 3, 4, 5]);
assert_eq!(bytes.as_bytes(), Some(&[1u8, 2, 3, 4, 5][..]));
}
#[test]
fn test_bytes_as_bytes_non_bytes() {
assert!(Value::String("hello".into()).as_bytes().is_none());
assert!(Value::Integer(42).as_bytes().is_none());
}
#[test]
fn test_bytes_type_name() {
assert_eq!(Value::Bytes(vec![]).type_name(), "bytes");
}
#[test]
fn test_bytes_display() {
let bytes = Value::Bytes(vec![1, 2, 3, 4, 5]);
assert_eq!(format!("{}", bytes), "<bytes: 5 bytes>");
let empty = Value::Bytes(vec![]);
assert_eq!(format!("{}", empty), "<bytes: 0 bytes>");
}
#[test]
fn test_bytes_from_vec() {
let v: Value = vec![0x48u8, 0x65, 0x6c, 0x6c, 0x6f].into();
assert!(v.is_bytes());
assert_eq!(v.as_bytes(), Some(&[0x48u8, 0x65, 0x6c, 0x6c, 0x6f][..]));
}
#[test]
fn test_bytes_serialize_to_base64() {
let bytes = Value::Bytes(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]); let yaml = serde_yaml::to_string(&bytes).unwrap();
assert!(yaml.contains("SGVsbG8="));
}
#[test]
fn test_bytes_serialize_empty() {
let bytes = Value::Bytes(vec![]);
let json = serde_json::to_string(&bytes).unwrap();
assert_eq!(json, "\"\"");
}
#[test]
fn test_bytes_equality() {
let a = Value::Bytes(vec![1, 2, 3]);
let b = Value::Bytes(vec![1, 2, 3]);
let c = Value::Bytes(vec![1, 2, 4]);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_bytes_clone() {
let original = Value::Bytes(vec![1, 2, 3, 4, 5]);
let cloned = original.clone();
assert_eq!(original, cloned);
}
}