use crate::de::is_list_context;
use crate::error::{Error, Result};
use indexmap::IndexMap;
use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
use std::fmt;
use std::ops::Index;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Bool(bool),
Integer(i32),
Integer64(i64),
Float(f64),
String(String),
Array(Vec<Value>),
List(Vec<Value>),
Group(Map),
}
pub type Map = IndexMap<String, Value>;
#[derive(Debug, Clone, PartialEq)]
enum PathSegment<'a> {
Key(&'a str),
Index(usize),
}
fn parse_path(path: &str) -> Option<Vec<PathSegment<'_>>> {
if path.is_empty() {
return None;
}
let mut segments = Vec::new();
for part in path.split('.') {
if part.is_empty() {
return None;
}
if part.starts_with('[') && part.ends_with(']') {
let inner = &part[1..part.len() - 1];
let index: usize = inner.parse().ok()?;
segments.push(PathSegment::Index(index));
} else if let Ok(index) = part.parse::<usize>() {
segments.push(PathSegment::Index(index));
} else {
segments.push(PathSegment::Key(part));
}
}
if segments.is_empty() {
None
} else {
Some(segments)
}
}
impl Value {
pub fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
pub fn is_integer(&self) -> bool {
matches!(self, Value::Integer(_) | Value::Integer64(_))
}
pub fn is_i32(&self) -> bool {
matches!(self, Value::Integer(_))
}
pub fn is_i64(&self) -> bool {
matches!(self, Value::Integer64(_))
}
pub fn is_float(&self) -> bool {
matches!(self, Value::Float(_))
}
pub fn is_number(&self) -> bool {
matches!(
self,
Value::Integer(_) | Value::Integer64(_) | Value::Float(_)
)
}
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub fn is_array(&self) -> bool {
matches!(self, Value::Array(_))
}
pub fn is_list(&self) -> bool {
matches!(self, Value::List(_))
}
pub fn is_group(&self) -> bool {
matches!(self, Value::Group(_))
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_i32(&self) -> Option<i32> {
match self {
Value::Integer(i) => Some(*i),
Value::Integer64(i) if *i >= i32::MIN as i64 && *i <= i32::MAX as i64 => {
Some(*i as i32)
}
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Value::Integer(i) => Some(*i as i64),
Value::Integer64(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),
Value::Integer64(i) => Some(*i as f64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_array(&self) -> Option<&Vec<Value>> {
match self {
Value::Array(arr) | Value::List(arr) => Some(arr),
_ => None,
}
}
pub fn as_group(&self) -> Option<&Map> {
match self {
Value::Group(map) => Some(map),
_ => None,
}
}
pub fn get<S: AsRef<str>>(&self, key: S) -> Option<&Value> {
match self {
Value::Group(map) => map.get(key.as_ref()),
_ => None,
}
}
pub fn get_mut<S: AsRef<str>>(&mut self, key: S) -> Option<&mut Value> {
match self {
Value::Group(map) => map.get_mut(key.as_ref()),
_ => None,
}
}
pub fn get_index(&self, index: usize) -> Option<&Value> {
match self {
Value::Array(arr) | Value::List(arr) => arr.get(index),
_ => None,
}
}
pub fn lookup(&self, path: &str) -> Option<&Value> {
let segments = parse_path(path)?;
let mut current = self;
for segment in &segments {
match segment {
PathSegment::Key(key) => current = current.get(*key)?,
PathSegment::Index(index) => current = current.get_index(*index)?,
}
}
Some(current)
}
pub fn lookup_mut(&mut self, path: &str) -> Option<&mut Value> {
let segments = parse_path(path)?;
let mut current = self;
for segment in &segments {
match segment {
PathSegment::Key(key) => match current {
Value::Group(map) => current = map.get_mut(*key)?,
_ => return None,
},
PathSegment::Index(index) => match current {
Value::Array(arr) | Value::List(arr) => current = arr.get_mut(*index)?,
_ => return None,
},
}
}
Some(current)
}
pub fn set(&mut self, path: &str, value: Value) -> Option<()> {
let segments = parse_path(path)?;
if segments.is_empty() {
return None;
}
let mut current = self;
for segment in &segments[..segments.len() - 1] {
match segment {
PathSegment::Key(key) => match current {
Value::Group(map) => {
current = map
.entry(key.to_string())
.or_insert_with(|| Value::Group(IndexMap::new()));
}
_ => return None,
},
PathSegment::Index(index) => match current {
Value::Array(arr) | Value::List(arr) => {
current = arr.get_mut(*index)?;
}
_ => return None,
},
}
}
match segments.last().unwrap() {
PathSegment::Key(key) => match current {
Value::Group(map) => {
map.insert(key.to_string(), value);
Some(())
}
_ => None,
},
PathSegment::Index(index) => match current {
Value::Array(arr) | Value::List(arr) => {
if *index < arr.len() {
arr[*index] = value;
Some(())
} else {
None
}
}
_ => None,
},
}
}
pub fn remove(&mut self, path: &str) -> Option<Value> {
let segments = parse_path(path)?;
if segments.is_empty() {
return None;
}
let mut current = self;
for segment in &segments[..segments.len() - 1] {
match segment {
PathSegment::Key(key) => match current {
Value::Group(map) => current = map.get_mut(*key)?,
_ => return None,
},
PathSegment::Index(index) => match current {
Value::Array(arr) | Value::List(arr) => current = arr.get_mut(*index)?,
_ => return None,
},
}
}
match segments.last().unwrap() {
PathSegment::Key(key) => match current {
Value::Group(map) => map.shift_remove(*key),
_ => None,
},
PathSegment::Index(index) => match current {
Value::Array(arr) | Value::List(arr) => {
if *index < arr.len() {
Some(arr.remove(*index))
} else {
None
}
}
_ => None,
},
}
}
pub fn from_str(s: &str) -> Result<Value> {
crate::from_str(s)
}
pub fn to_string(&self) -> Result<String> {
crate::to_string(self)
}
}
impl Index<&str> for Value {
type Output = Value;
fn index(&self, key: &str) -> &Self::Output {
self.lookup(key).expect("key not found in group")
}
}
impl Index<usize> for Value {
type Output = Value;
fn index(&self, index: usize) -> &Self::Output {
self.get_index(index).expect("index out of bounds")
}
}
impl PartialEq<bool> for Value {
fn eq(&self, other: &bool) -> bool {
self.as_bool() == Some(*other)
}
}
impl PartialEq<i32> for Value {
fn eq(&self, other: &i32) -> bool {
self.as_i32() == Some(*other)
}
}
impl PartialEq<i64> for Value {
fn eq(&self, other: &i64) -> bool {
self.as_i64() == Some(*other)
}
}
impl PartialEq<f64> for Value {
fn eq(&self, other: &f64) -> bool {
self.as_f64() == Some(*other)
}
}
impl PartialEq<str> for Value {
fn eq(&self, other: &str) -> bool {
self.as_str() == Some(other)
}
}
impl PartialEq<&str> for Value {
fn eq(&self, other: &&str) -> bool {
self.as_str() == Some(*other)
}
}
impl PartialEq<String> for Value {
fn eq(&self, other: &String) -> bool {
self.as_str() == Some(other.as_str())
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::Bool(b) => write!(f, "{}", b),
Value::Integer(i) => write!(f, "{}", i),
Value::Integer64(i) => write!(f, "{}L", i),
Value::Float(fl) => write!(f, "{}", fl),
Value::String(s) => write!(f, "\"{}\"", s),
Value::Array(arr) => {
write!(f, "[")?;
for (i, v) in arr.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, "]")
}
Value::List(arr) => {
write!(f, "(")?;
for (i, v) in arr.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, ")")
}
Value::Group(map) => {
write!(f, "{{")?;
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}: {}", k, v)?;
}
write!(f, "}}")
}
}
}
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Bool(b) => serializer.serialize_bool(*b),
Value::Integer(i) => serializer.serialize_i32(*i),
Value::Integer64(i) => serializer.serialize_i64(*i),
Value::Float(f) => serializer.serialize_f64(*f),
Value::String(s) => serializer.serialize_str(s),
Value::Array(arr) => {
let mut seq = serializer.serialize_seq(Some(arr.len()))?;
for element in arr {
seq.serialize_element(element)?;
}
seq.end()
}
Value::List(arr) => {
use serde::ser::SerializeTuple;
let mut tup = serializer.serialize_tuple(arr.len())?;
for element in arr {
tup.serialize_element(element)?;
}
tup.end()
}
Value::Group(map) => {
let mut map_ser = serializer.serialize_map(Some(map.len()))?;
for (k, v) in map {
map_ser.serialize_entry(k, v)?;
}
map_ser.end()
}
}
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> std::result::Result<Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(ValueVisitor)
}
}
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("any valid libconfig value")
}
fn visit_bool<E>(self, value: bool) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Bool(value))
}
fn visit_i8<E>(self, value: i8) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Integer(value as i32))
}
fn visit_i16<E>(self, value: i16) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Integer(value as i32))
}
fn visit_i32<E>(self, value: i32) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Integer(value))
}
fn visit_i64<E>(self, value: i64) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Integer64(value))
}
fn visit_u8<E>(self, value: u8) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Integer(value as i32))
}
fn visit_u16<E>(self, value: u16) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Integer(value as i32))
}
fn visit_u32<E>(self, value: u32) -> std::result::Result<Value, E>
where
E: de::Error,
{
if value <= i32::MAX as u32 {
Ok(Value::Integer(value as i32))
} else {
Ok(Value::Integer64(value as i64))
}
}
fn visit_u64<E>(self, value: u64) -> std::result::Result<Value, E>
where
E: de::Error,
{
if value <= i32::MAX as u64 {
Ok(Value::Integer(value as i32))
} else {
Ok(Value::Integer64(value as i64))
}
}
fn visit_f32<E>(self, value: f32) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Float(value as f64))
}
fn visit_f64<E>(self, value: f64) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::Float(value))
}
fn visit_str<E>(self, value: &str) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::String(value.to_string()))
}
fn visit_borrowed_str<E>(self, value: &'de str) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::String(value.to_string()))
}
fn visit_string<E>(self, value: String) -> std::result::Result<Value, E>
where
E: de::Error,
{
Ok(Value::String(value))
}
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Value, A::Error>
where
A: SeqAccess<'de>,
{
let is_list = is_list_context();
let mut values = Vec::new();
while let Some(value) = seq.next_element()? {
values.push(value);
}
if is_list {
Ok(Value::List(values))
} else {
Ok(Value::Array(values))
}
}
fn visit_map<A>(self, mut map: A) -> std::result::Result<Value, A::Error>
where
A: MapAccess<'de>,
{
let mut values = Map::new();
while let Some((key, value)) = map.next_entry()? {
values.insert(key, value);
}
Ok(Value::Group(values))
}
}
pub fn from_value<T>(value: Value) -> Result<T>
where
T: de::DeserializeOwned,
{
T::deserialize(value).map_err(|e| Error::Message(format!("failed to deserialize: {}", e)))
}
fn to_string_format_impl(value: &Value, indent_level: usize, is_nested: bool) -> Result<String> {
const INDENT_SIZE: usize = 2;
match value {
Value::Bool(b) => Ok(if *b {
"true".to_string()
} else {
"false".to_string()
}),
Value::Integer(i) => Ok(i.to_string()),
Value::Integer64(i) => Ok(format!("{}L", i)),
Value::Float(f) => {
let s = f.to_string();
if !s.contains('.') && !s.contains('e') && !s.contains('E') {
Ok(format!("{}.0", s))
} else {
Ok(s)
}
}
Value::String(s) => {
let mut result = String::from("\"");
for ch in s.chars() {
match ch {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
'\x0C' => result.push_str("\\f"),
'\x08' => result.push_str("\\b"),
'\x0B' => result.push_str("\\v"),
'\x07' => result.push_str("\\a"),
_ => result.push(ch),
}
}
result.push('"');
Ok(result)
}
Value::Array(arr) => {
let mut result = String::from("[");
for (i, elem) in arr.iter().enumerate() {
if i > 0 {
result.push_str(", ");
}
result.push_str(&to_string_format_impl(elem, indent_level, true)?);
}
result.push(']');
Ok(result)
}
Value::List(arr) => {
let mut result = String::from("(");
for (i, elem) in arr.iter().enumerate() {
if i > 0 {
result.push_str(", ");
}
result.push_str(&to_string_format_impl(elem, indent_level, true)?);
}
result.push(')');
Ok(result)
}
Value::Group(map) => {
if !is_nested {
let mut output = String::new();
let mut first = true;
for (key, val) in map {
if !first {
output.push(';');
output.push('\n');
}
first = false;
output.push_str(key);
output.push_str(" = ");
let val_str = to_string_format_impl(val, indent_level, true)?;
output.push_str(&val_str);
}
if !output.is_empty() {
output.push(';');
}
Ok(output)
} else {
let mut output = String::from("{");
let next_indent = indent_level + 1;
let indent_str = " ".repeat(next_indent * INDENT_SIZE);
let mut first = true;
for (key, val) in map {
if !first {
output.push(';');
}
output.push('\n');
output.push_str(&indent_str);
first = false;
output.push_str(key);
output.push_str(" = ");
let val_str = to_string_format_impl(val, next_indent, true)?;
output.push_str(&val_str);
}
if !map.is_empty() {
output.push(';');
}
output.push('\n');
output.push_str(&" ".repeat(indent_level * INDENT_SIZE));
output.push('}');
Ok(output)
}
}
}
}
impl<'de> Deserializer<'de> for Value {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match self {
Value::Bool(b) => visitor.visit_bool(b),
Value::Integer(i) => visitor.visit_i32(i),
Value::Integer64(i) => visitor.visit_i64(i),
Value::Float(f) => visitor.visit_f64(f),
Value::String(s) => visitor.visit_string(s),
Value::Array(arr) | Value::List(arr) => {
visitor.visit_seq(SeqDeserializer::new(arr.into_iter()))
}
Value::Group(map) => visitor.visit_map(MapDeserializer::new(map.into_iter())),
}
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string
bytes byte_buf unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any
}
}
struct SeqDeserializer {
iter: std::vec::IntoIter<Value>,
}
impl SeqDeserializer {
fn new(iter: std::vec::IntoIter<Value>) -> Self {
SeqDeserializer { iter }
}
}
impl<'de> SeqAccess<'de> for SeqDeserializer {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.iter.next() {
Some(value) => seed.deserialize(value).map(Some),
None => Ok(None),
}
}
}
struct MapDeserializer {
iter: indexmap::map::IntoIter<String, Value>,
value: Option<Value>,
}
impl MapDeserializer {
fn new(iter: indexmap::map::IntoIter<String, Value>) -> Self {
MapDeserializer { iter, value: None }
}
}
impl<'de> MapAccess<'de> for MapDeserializer {
type Error = Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
where
K: de::DeserializeSeed<'de>,
{
match self.iter.next() {
Some((key, value)) => {
self.value = Some(value);
seed.deserialize(Value::String(key)).map(Some)
}
None => Ok(None),
}
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
where
V: de::DeserializeSeed<'de>,
{
match self.value.take() {
Some(value) => seed.deserialize(value),
None => Err(Error::Message("value is missing".to_string())),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub value: Value,
pub explicit_braces: bool,
}
impl Config {
pub fn new() -> Self {
Config {
value: Value::Group(Map::new()),
explicit_braces: false,
}
}
pub fn from_str(s: &str) -> Result<Config> {
let trimmed = s.trim_start();
let explicit_braces = trimmed.starts_with('{');
let value: Value = crate::from_str(s)?;
Ok(Config {
value,
explicit_braces,
})
}
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Config> {
let contents = std::fs::read_to_string(path)
.map_err(|e| Error::Message(format!("failed to read file: {}", e)))?;
Config::from_str(&contents)
}
pub fn to_string(&self) -> Result<String> {
to_string_format_impl(&self.value, 0, self.explicit_braces)
}
}
impl Default for Config {
fn default() -> Self {
Config::new()
}
}
impl From<Value> for Config {
fn from(value: Value) -> Self {
Config {
value,
explicit_braces: false,
}
}
}
impl std::ops::Deref for Config {
type Target = Value;
fn deref(&self) -> &Value {
&self.value
}
}
impl std::ops::DerefMut for Config {
fn deref_mut(&mut self) -> &mut Value {
&mut self.value
}
}
impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.to_string() {
Ok(s) => write!(f, "{}", s),
Err(e) => write!(f, "<error: {}>", e),
}
}
}
impl Serialize for Config {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
self.value.serialize(serializer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::from_str;
#[test]
fn test_value_types() {
let v = Value::Bool(true);
assert!(v.is_bool());
assert_eq!(v.as_bool(), Some(true));
let v = Value::Integer(42);
assert!(v.is_i32());
assert_eq!(v.as_i32(), Some(42));
let v = Value::Float(3.14);
assert!(v.is_float());
assert_eq!(v.as_f64(), Some(3.14));
let v = Value::String("hello".to_string());
assert!(v.is_string());
assert_eq!(v.as_str(), Some("hello"));
}
#[test]
fn test_value_indexing() {
let config = r#"
{
name = "Test";
count = 42;
items = [1, 2, 3];
}
"#;
let v: Value = from_str(config).unwrap();
assert_eq!(v["name"], "Test");
assert_eq!(v["count"], 42);
assert_eq!(v["items"][0], 1);
assert_eq!(v["items"][1], 2);
}
#[test]
fn test_value_lookup() {
let config = r#"
{
application = {
window = {
title = "My App";
size = { w = 640; h = 480; };
};
};
}
"#;
let v: Value = from_str(config).unwrap();
assert_eq!(
v.lookup("application.window.title")
.and_then(|v| v.as_str()),
Some("My App")
);
assert_eq!(
v.lookup("application.window.size")
.and_then(|v| v.get("w"))
.and_then(|v| v.as_i32()),
Some(640)
);
}
#[test]
fn test_value_lookup_with_array_index() {
let config = r#"
{
name = "Test";
items = [10, 20, 30];
nested = {
ports = [80, 443];
};
}
"#;
let v: Value = from_str(config).unwrap();
assert_eq!(v.lookup("items.0").and_then(|v| v.as_i32()), Some(10));
assert_eq!(v.lookup("items.1").and_then(|v| v.as_i32()), Some(20));
assert_eq!(v.lookup("items.2").and_then(|v| v.as_i32()), Some(30));
assert_eq!(
v.lookup("nested.ports.0").and_then(|v| v.as_i32()),
Some(80)
);
assert_eq!(
v.lookup("nested.ports.1").and_then(|v| v.as_i32()),
Some(443)
);
assert!(v.lookup("items.5").is_none());
assert!(v.lookup("name.0").is_none());
assert_eq!(v["items.0"], 10);
assert_eq!(v["nested.ports.1"], 443);
}
#[test]
fn test_value_comparison() {
let v = Value::Bool(true);
assert!(v == true);
let v = Value::Integer(42);
assert!(v == 42);
let v = Value::String("hello".to_string());
assert!(v == "hello");
}
#[test]
fn test_value_round_trip() {
let config = r#"
{
name = "Test";
version = 1;
enabled = true;
pi = 3.14;
items = [1, 2, 3];
}
"#;
let v: Value = from_str(config).unwrap();
let serialized = crate::to_string(&v).unwrap();
let v2: Value = from_str(&serialized).unwrap();
assert_eq!(v["name"], v2["name"]);
assert_eq!(v["version"], v2["version"]);
assert_eq!(v["enabled"], v2["enabled"]);
}
#[test]
fn test_parse_path_dotted_keys() {
let segs = parse_path("a.b.c").unwrap();
assert_eq!(
segs,
vec![
PathSegment::Key("a"),
PathSegment::Key("b"),
PathSegment::Key("c"),
]
);
}
#[test]
fn test_parse_path_bracket_index() {
let segs = parse_path("items.[0].title").unwrap();
assert_eq!(
segs,
vec![
PathSegment::Key("items"),
PathSegment::Index(0),
PathSegment::Key("title"),
]
);
}
#[test]
fn test_parse_path_bare_numeric() {
let segs = parse_path("items.0").unwrap();
assert_eq!(segs, vec![PathSegment::Key("items"), PathSegment::Index(0)]);
}
#[test]
fn test_parse_path_single_segment() {
assert_eq!(
parse_path("version").unwrap(),
vec![PathSegment::Key("version")]
);
}
#[test]
fn test_parse_path_invalid() {
assert!(parse_path("").is_none());
assert!(parse_path("a..b").is_none());
assert!(parse_path(".a").is_none());
assert!(parse_path("a.").is_none());
assert!(parse_path("items.[abc]").is_none());
assert!(parse_path("items.[-1]").is_none());
assert!(parse_path("items.[]").is_none());
}
#[test]
fn test_lookup_bracket_notation() {
let config = r#"
{
books = (
{ title = "Treasure Island"; price = 29; },
{ title = "Snow Crash"; price = 9; }
);
}
"#;
let v: Value = from_str(config).unwrap();
assert_eq!(
v.lookup("books.[0].title").and_then(|v| v.as_str()),
Some("Treasure Island")
);
assert_eq!(
v.lookup("books.[1].price").and_then(|v| v.as_i32()),
Some(9)
);
assert_eq!(
v.lookup("books.0.title").and_then(|v| v.as_str()),
Some("Treasure Island")
);
}
#[test]
fn test_lookup_mut_bracket_notation() {
let config = r#"{ items = [10, 20, 30]; }"#;
let mut v: Value = from_str(config).unwrap();
*v.lookup_mut("items.[1]").unwrap() = Value::Integer(99);
assert_eq!(v.lookup("items.[1]").unwrap().as_i32(), Some(99));
assert_eq!(v.lookup("items.1").unwrap().as_i32(), Some(99));
}
#[test]
fn test_set_existing_key() {
let mut v: Value = from_str(r#"{ name = "old"; }"#).unwrap();
assert_eq!(v.set("name", Value::String("new".to_string())), Some(()));
assert_eq!(v.lookup("name").unwrap().as_str(), Some("new"));
}
#[test]
fn test_set_new_key() {
let mut v: Value = from_str(r#"{ a = 1; }"#).unwrap();
assert_eq!(v.set("b", Value::Integer(2)), Some(()));
assert_eq!(v.lookup("b").unwrap().as_i32(), Some(2));
}
#[test]
fn test_set_creates_intermediate_groups() {
let mut v = Value::Group(IndexMap::new());
assert_eq!(v.set("a.b.c", Value::Integer(42)), Some(()));
assert_eq!(v.lookup("a.b.c").unwrap().as_i32(), Some(42));
assert!(v.lookup("a").unwrap().is_group());
assert!(v.lookup("a.b").unwrap().is_group());
}
#[test]
fn test_set_array_element() {
let mut v: Value = from_str(r#"{ items = [1, 2, 3]; }"#).unwrap();
assert_eq!(v.set("items.[0]", Value::Integer(99)), Some(()));
assert_eq!(v.lookup("items.[0]").unwrap().as_i32(), Some(99));
}
#[test]
fn test_set_array_out_of_bounds() {
let mut v: Value = from_str(r#"{ items = [1, 2]; }"#).unwrap();
assert_eq!(v.set("items.[5]", Value::Integer(99)), None);
}
#[test]
fn test_set_on_scalar_fails() {
let mut v: Value = from_str(r#"{ name = "hello"; }"#).unwrap();
assert_eq!(v.set("name.child", Value::Integer(1)), None);
}
#[test]
fn test_set_deeply_nested() {
let mut v = Value::Group(IndexMap::new());
assert_eq!(
v.set("level1.level2.level3.level4", Value::Bool(true)),
Some(())
);
assert_eq!(
v.lookup("level1.level2.level3.level4").unwrap().as_bool(),
Some(true)
);
}
#[test]
fn test_set_into_list_element_group() {
let config = r#"
{
books = (
{ title = "Old Title"; }
);
}
"#;
let mut v: Value = from_str(config).unwrap();
assert_eq!(
v.set("books.[0].title", Value::String("New Title".to_string())),
Some(())
);
assert_eq!(
v.lookup("books.[0].title").unwrap().as_str(),
Some("New Title")
);
}
#[test]
fn test_remove_group_key() {
let mut v: Value = from_str(r#"{ a = 1; b = 2; c = 3; }"#).unwrap();
let removed = v.remove("b");
assert_eq!(removed, Some(Value::Integer(2)));
assert!(v.lookup("b").is_none());
assert_eq!(v.lookup("a").unwrap().as_i32(), Some(1));
assert_eq!(v.lookup("c").unwrap().as_i32(), Some(3));
}
#[test]
fn test_remove_array_element() {
let mut v: Value = from_str(r#"{ items = [10, 20, 30]; }"#).unwrap();
let removed = v.remove("items.[1]");
assert_eq!(removed, Some(Value::Integer(20)));
assert_eq!(v.lookup("items.[0]").unwrap().as_i32(), Some(10));
assert_eq!(v.lookup("items.[1]").unwrap().as_i32(), Some(30));
assert!(v.lookup("items.[2]").is_none());
}
#[test]
fn test_remove_nested() {
let config = r#"
{
database = {
host = "localhost";
port = 5432;
};
}
"#;
let mut v: Value = from_str(config).unwrap();
let removed = v.remove("database.host");
assert_eq!(removed, Some(Value::String("localhost".to_string())));
assert!(v.lookup("database.host").is_none());
assert_eq!(v.lookup("database.port").unwrap().as_i32(), Some(5432));
}
#[test]
fn test_remove_nonexistent() {
let mut v: Value = from_str(r#"{ a = 1; }"#).unwrap();
assert_eq!(v.remove("z"), None);
assert_eq!(v.remove("a.b"), None);
}
#[test]
fn test_remove_index_out_of_bounds() {
let mut v: Value = from_str(r#"{ items = [1, 2]; }"#).unwrap();
assert_eq!(v.remove("items.[5]"), None);
}
#[test]
fn test_remove_entire_subtree() {
let config = r#"
{
app = {
window = { title = "hi"; };
debug = false;
};
}
"#;
let mut v: Value = from_str(config).unwrap();
let removed = v.remove("app.window");
assert!(removed.unwrap().is_group());
assert!(v.lookup("app.window").is_none());
assert_eq!(v.lookup("app.debug").unwrap().as_bool(), Some(false));
}
#[test]
fn test_remove_bare_numeric_index() {
let mut v: Value = from_str(r#"{ items = [10, 20, 30]; }"#).unwrap();
let removed = v.remove("items.0");
assert_eq!(removed, Some(Value::Integer(10)));
assert_eq!(v.lookup("items.0").unwrap().as_i32(), Some(20));
}
#[test]
fn test_ordering_preserved_in_round_trip() {
let input = r#"{
first = 1;
second = 2;
third = 3;
fourth = 4;
}"#;
let v: Value = from_str(input).unwrap();
let output = Config::from(v.clone()).to_string().unwrap();
let v_output: Value = from_str(&output).unwrap();
if let Value::Group(map) = v {
if let Value::Group(map_output) = v_output {
let keys1: Vec<&String> = map.keys().collect();
let keys2: Vec<&String> = map_output.keys().collect();
assert_eq!(keys1, keys2);
assert_eq!(keys1, vec!["first", "second", "third", "fourth"]);
}
}
}
#[test]
fn test_config_implicit_no_outer_braces() {
let input = "a = 1;\nb = 2;\nc = 3;";
let v: Value = from_str(input).unwrap();
let output = Config::from(v).to_string().unwrap();
assert!(!output.trim_start().starts_with('{'));
assert!(!output.trim_end().ends_with('}'));
assert!(output.contains("a = 1"));
assert!(output.contains("b = 2"));
assert!(output.contains("c = 3"));
}
#[test]
fn test_config_explicit_outer_braces() {
let input = "a = 1;\nb = 2;\nc = 3;";
let v: Value = from_str(input).unwrap();
let mut config = Config::from(v);
config.explicit_braces = true;
let output = config.to_string().unwrap();
assert!(output.trim_start().starts_with('{'));
assert!(output.trim_end().ends_with('}'));
assert!(output.contains("a = 1"));
assert!(output.contains("b = 2"));
assert!(output.contains("c = 3"));
}
#[test]
fn test_config_implicit_vs_explicit() {
let input = "{\n a = 1;\n b = 2;\n c = 3;\n}";
let v: Value = from_str(input).unwrap();
let output = Config::from(v.clone()).to_string().unwrap();
assert!(!output.trim_start().starts_with('{'));
assert!(!output.trim_end().ends_with('}'));
let mut config = Config::from(v);
config.explicit_braces = true;
let output_braces = config.to_string().unwrap();
assert!(output_braces.trim_start().starts_with('{'));
assert!(output_braces.trim_end().ends_with('}'));
}
#[test]
fn test_nested_groups_have_braces() {
let input = r#"{
outer = 1;
nested = {
inner = 2;
};
}"#;
let v: Value = from_str(input).unwrap();
let output = Config::from(v).to_string().unwrap();
assert!(!output.trim_start().starts_with('{'));
assert!(output.contains("nested = {"));
let v2: Value = from_str(&output).unwrap();
assert_eq!(v2["outer"], 1);
assert_eq!(v2["nested.inner"], 2);
}
#[test]
fn test_ordering_with_mixed_types() {
let input = r#"{
string_first = "hello";
int_second = 42;
bool_third = true;
float_fourth = 3.14;
array_fifth = [1, 2, 3];
}"#;
let v: Value = from_str(input).unwrap();
if let Value::Group(map) = &v {
let keys: Vec<&String> = map.keys().collect();
assert_eq!(
keys,
vec![
"string_first",
"int_second",
"bool_third",
"float_fourth",
"array_fifth"
]
);
}
let output = Config::from(v).to_string().unwrap();
let v2: Value = from_str(&output).unwrap();
if let Value::Group(map) = &v2 {
let keys: Vec<&String> = map.keys().collect();
assert_eq!(
keys,
vec![
"string_first",
"int_second",
"bool_third",
"float_fourth",
"array_fifth"
]
);
}
}
#[test]
fn test_adding_new_keys_appends_to_end() {
let input = "a = 1;\nb = 2;";
let mut v: Value = from_str(input).unwrap();
v.set("c", Value::Integer(3)).unwrap();
if let Value::Group(map) = &v {
let keys: Vec<&String> = map.keys().collect();
assert_eq!(keys, vec!["a", "b", "c"]);
}
let output = Config::from(v).to_string().unwrap();
let a_pos = output.find("a = 1").unwrap();
let b_pos = output.find("b = 2").unwrap();
let c_pos = output.find("c = 3").unwrap();
assert!(a_pos < b_pos);
assert!(b_pos < c_pos);
}
#[test]
fn test_implicit_group_round_trip_exact() {
let input = "name = \"test\";\nversion = 42;";
let v: Value = from_str(input).unwrap();
let output = Config::from(v).to_string().unwrap();
assert_eq!(output, input);
let v2: Value = from_str(&output).unwrap();
let output2 = Config::from(v2).to_string().unwrap();
assert_eq!(output, output2);
}
#[test]
fn test_nested_indentation() {
let input = r#"{
outer = 1;
nested = {
inner = 2;
deep = {
value = 3;
};
};
another = 4;
}"#;
let v: Value = from_str(input).unwrap();
let output = Config::from(v).to_string().unwrap();
assert!(output.contains("outer = 1"));
assert!(output.contains("nested = {"));
assert!(output.contains(" inner = 2"));
assert!(output.contains(" deep = {"));
assert!(output.contains(" value = 3"));
let v2: Value = from_str(&output).unwrap();
assert_eq!(v2["outer"], 1);
assert_eq!(v2["nested.inner"], 2);
assert_eq!(v2["nested.deep.value"], 3);
assert_eq!(v2["another"], 4);
}
#[test]
fn test_implicit_group_with_nested_explicit_groups() {
let input = r#"top_level = 1;
nested = {
inner = 2;
};
also_top = 3;"#;
let v: Value = from_str(input).unwrap();
let output = Config::from(v).to_string().unwrap();
assert!(!output.trim().starts_with('{'));
assert!(output.contains("nested = {"));
assert!(output.contains(" inner = 2"));
let v2: Value = from_str(&output).unwrap();
assert_eq!(v2["top_level"], 1);
assert_eq!(v2["nested.inner"], 2);
assert_eq!(v2["also_top"], 3);
}
#[test]
fn test_deeply_nested_groups() {
let input = r#"{
level1 = {
level2 = {
level3 = {
value = 42;
};
};
};
}"#;
let v: Value = from_str(input).unwrap();
let output = Config::from(v).to_string().unwrap();
assert!(output.contains("level1 = {"));
assert!(output.contains(" level2 = {"));
assert!(output.contains(" level3 = {"));
assert!(output.contains(" value = 42"));
}
#[test]
fn test_config_implicit_round_trip() {
let input = "name = \"test\";\nversion = 42;";
let config = Config::from_str(input).unwrap();
assert!(!config.explicit_braces);
assert_eq!(config["name"], "test");
assert_eq!(config["version"], 42);
let output = config.to_string().unwrap();
assert_eq!(output, input);
}
#[test]
fn test_config_explicit_round_trip() {
let input = "{\n name = \"test\";\n version = 42;\n}";
let config = Config::from_str(input).unwrap();
assert!(config.explicit_braces);
assert_eq!(config["name"], "test");
let output = config.to_string().unwrap();
assert!(output.starts_with('{'));
assert!(output.ends_with('}'));
}
#[test]
fn test_config_deref_lookup() {
let config = Config::from_str("a = 1; b = { c = 2; };").unwrap();
assert_eq!(config.lookup("b.c").unwrap().as_i32(), Some(2));
assert!(config.is_group());
}
#[test]
fn test_config_deref_mut() {
let mut config = Config::from_str("a = 1; b = 2;").unwrap();
config.set("c", Value::Integer(3)).unwrap();
assert_eq!(config["c"], 3);
let output = config.to_string().unwrap();
assert!(output.contains("c = 3"));
}
#[test]
fn test_config_display() {
let config = Config::from_str("x = 10; y = 20;").unwrap();
let displayed = format!("{}", config);
assert!(displayed.contains("x = 10"));
assert!(displayed.contains("y = 20"));
assert!(!displayed.starts_with('{'));
}
#[test]
fn test_config_new_empty() {
let mut config = Config::new();
assert!(!config.explicit_braces);
assert!(config.is_group());
config.set("key", Value::String("val".to_string())).unwrap();
let output = config.to_string().unwrap();
assert_eq!(output, "key = \"val\";");
}
#[test]
fn test_config_from_value() {
let mut map = Map::new();
map.insert("key".to_string(), Value::String("val".to_string()));
let config = Config::from(Value::Group(map));
assert!(!config.explicit_braces);
let output = config.to_string().unwrap();
assert_eq!(output, "key = \"val\";");
}
#[test]
fn test_config_from_value_explicit() {
let mut map = Map::new();
map.insert("key".to_string(), Value::String("val".to_string()));
let mut config = Config::from(Value::Group(map));
config.explicit_braces = true;
assert!(config.explicit_braces);
let output = config.to_string().unwrap();
assert!(output.starts_with('{'));
assert!(output.contains("key = \"val\""));
}
#[test]
fn test_config_default() {
let config = Config::default();
assert!(!config.explicit_braces);
assert!(config.is_group());
assert_eq!(config.as_group().unwrap().len(), 0);
}
#[test]
fn test_config_serialize() {
let config = Config::from_str("a = 1; b = 2;").unwrap();
let serde_output = crate::to_string(&config).unwrap();
assert!(serde_output.starts_with('{'));
let format_output = config.to_string().unwrap();
assert!(!format_output.starts_with('{'));
}
}