mod de;
mod debug;
mod from;
mod index;
mod partial_eq;
mod resolve_aliases;
mod ser;
pub mod tagged;
use crate::error::{self, Error, ErrorImpl};
use serde::de::{Deserialize, DeserializeOwned, IntoDeserializer};
use serde::Serialize;
use std::hash::{Hash, Hasher};
use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering};
pub use self::index::Index;
pub use self::ser::Serializer;
pub use self::tagged::{Tag, TaggedValue};
#[doc(inline)]
pub use crate::mapping::Mapping;
pub use crate::number::Number;
static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
pub(crate) fn next_id() -> usize {
NEXT_ID.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone, PartialEq, PartialOrd)]
pub enum Value {
Null(Option<String>),
Bool(bool, Option<String>),
Number(Number, Option<String>),
String(String, Option<String>),
Sequence(Sequence),
Mapping(Mapping),
Alias(String),
Tagged(Box<TaggedValue>),
}
impl Default for Value {
fn default() -> Value {
Value::Null(None)
}
}
#[derive(Clone, Debug, Default)]
pub struct Sequence {
pub anchor: Option<String>,
pub elements: Vec<Value>,
}
impl PartialEq for Sequence {
fn eq(&self, other: &Self) -> bool {
self.anchor == other.anchor && self.elements == other.elements
}
}
impl Eq for Sequence {}
impl PartialOrd for Sequence {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match self.anchor.partial_cmp(&other.anchor) {
Some(std::cmp::Ordering::Equal) => self.elements.partial_cmp(&other.elements),
non_eq => non_eq,
}
}
}
impl std::hash::Hash for Sequence {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.anchor.hash(state);
self.elements.hash(state);
}
}
impl serde::Serialize for Sequence {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.elements.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Sequence {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let elements = Vec::<Value>::deserialize(deserializer)?;
Ok(Sequence {
anchor: None,
elements,
})
}
}
impl<'a> IntoIterator for &'a Sequence {
type Item = &'a Value;
type IntoIter = std::slice::Iter<'a, Value>;
fn into_iter(self) -> Self::IntoIter {
self.elements.iter()
}
}
impl<'a> IntoIterator for &'a mut Sequence {
type Item = &'a mut Value;
type IntoIter = std::slice::IterMut<'a, Value>;
fn into_iter(self) -> Self::IntoIter {
self.elements.iter_mut()
}
}
impl IntoIterator for Sequence {
type Item = Value;
type IntoIter = std::vec::IntoIter<Value>;
fn into_iter(self) -> Self::IntoIter {
self.elements.into_iter()
}
}
impl std::ops::Deref for Sequence {
type Target = Vec<Value>;
fn deref(&self) -> &Self::Target {
&self.elements
}
}
impl std::ops::DerefMut for Sequence {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.elements
}
}
impl Sequence {
#[inline]
pub fn new() -> Self {
Sequence {
anchor: None,
elements: Vec::new(),
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Sequence {
anchor: None,
elements: Vec::with_capacity(capacity),
}
}
#[inline]
pub fn with_anchor(anchor: impl Into<String>) -> Self {
Sequence {
anchor: Some(anchor.into()),
elements: Vec::new(),
}
}
pub const fn const_new() -> Self {
Sequence {
anchor: None,
elements: Vec::new(),
}
}
}
pub fn to_value<T>(value: T) -> Result<Value, Error>
where
T: Serialize,
{
value.serialize(Serializer)
}
pub fn from_value<T>(value: Value) -> Result<T, Error>
where
T: DeserializeOwned,
{
Deserialize::deserialize(value)
}
impl Value {
pub fn get<I: Index>(&self, index: I) -> Option<&Value> {
index.index_into(self)
}
pub fn is_null(&self) -> bool {
matches!(self.untag_ref(), Value::Null(_))
}
pub fn as_null(&self) -> Option<()> {
match self.untag_ref() {
Value::Null(_) => Some(()),
_ => None,
}
}
pub fn is_bool(&self) -> bool {
matches!(self.untag_ref(), Value::Bool(_, _))
}
pub fn as_bool(&self) -> Option<bool> {
match self.untag_ref() {
Value::Bool(b, _) => Some(*b),
_ => None,
}
}
pub fn is_number(&self) -> bool {
matches!(self.untag_ref(), Value::Number(_, _))
}
pub fn is_i64(&self) -> bool {
matches!(self.untag_ref(), Value::Number(n, _) if n.is_i64())
}
pub fn as_i64(&self) -> Option<i64> {
match self.untag_ref() {
Value::Number(n, _) => n.as_i64(),
_ => None,
}
}
pub fn is_u64(&self) -> bool {
matches!(self.untag_ref(), Value::Number(n, _) if n.is_u64())
}
pub fn as_u64(&self) -> Option<u64> {
match self.untag_ref() {
Value::Number(n, _) => n.as_u64(),
_ => None,
}
}
pub fn is_f64(&self) -> bool {
matches!(self.untag_ref(), Value::Number(n, _) if n.is_f64())
}
pub fn as_f64(&self) -> Option<f64> {
match self.untag_ref() {
Value::Number(i, _) => i.as_f64(),
_ => None,
}
}
pub fn is_string(&self) -> bool {
matches!(self.untag_ref(), Value::String(_, _))
}
pub fn as_str(&self) -> Option<&str> {
match self.untag_ref() {
Value::String(s, _) => Some(s),
_ => None,
}
}
pub fn is_sequence(&self) -> bool {
matches!(self.untag_ref(), Value::Sequence(_))
}
pub fn as_sequence(&self) -> Option<&Sequence> {
match self.untag_ref() {
Value::Sequence(seq) => Some(seq),
_ => None,
}
}
pub fn as_sequence_mut(&mut self) -> Option<&mut Sequence> {
match self.untag_mut() {
Value::Sequence(seq) => Some(seq),
_ => None,
}
}
pub fn is_mapping(&self) -> bool {
matches!(self.untag_ref(), Value::Mapping(_))
}
pub fn as_mapping(&self) -> Option<&Mapping> {
match self.untag_ref() {
Value::Mapping(map) => Some(map),
_ => None,
}
}
pub fn as_mapping_mut(&mut self) -> Option<&mut Mapping> {
match self.untag_mut() {
Value::Mapping(map) => Some(map),
_ => None,
}
}
pub fn apply_merge(&mut self) -> Result<(), Error> {
use std::collections::HashSet;
let mut stack = Vec::new();
let mut visited = HashSet::new();
stack.push(self);
while let Some(node) = stack.pop() {
match node {
Value::Mapping(mapping) => {
let id = mapping.id;
if id != 0 && !visited.insert(id) {
return Err(error::new(ErrorImpl::MergeRecursion));
}
match mapping.remove("<<") {
Some(Value::Mapping(merge)) => {
for (k, v) in merge {
mapping.entry(k).or_insert(v);
}
}
Some(Value::Sequence(sequence)) => {
for value in sequence.into_iter().rev() {
match value {
Value::Mapping(merge) => {
for (k, v) in merge {
mapping.entry(k).or_insert(v);
}
}
Value::Sequence(_) => {
return Err(error::new(ErrorImpl::SequenceInMergeElement));
}
Value::Tagged(_) => {
return Err(error::new(ErrorImpl::TaggedInMerge));
}
_unexpected => {
return Err(error::new(ErrorImpl::ScalarInMergeElement));
}
}
}
}
None => {}
Some(Value::Tagged(_)) => return Err(error::new(ErrorImpl::TaggedInMerge)),
Some(_unexpected) => return Err(error::new(ErrorImpl::ScalarInMerge)),
}
stack.extend(mapping.values_mut());
}
Value::Sequence(sequence) => stack.extend(sequence),
Value::Tagged(tagged) => stack.push(&mut tagged.value),
_ => {}
}
}
Ok(())
}
}
impl Eq for Value {}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
mem::discriminant(self).hash(state);
match self {
Value::Null(_) => {}
Value::Bool(v, _) => v.hash(state),
Value::Number(v, _) => v.hash(state),
Value::String(v, _) => v.hash(state),
Value::Sequence(v) => v.hash(state),
Value::Mapping(v) => v.hash(state),
Value::Alias(v) => v.hash(state),
Value::Tagged(v) => v.hash(state),
}
}
}
impl IntoDeserializer<'_, Error> for Value {
type Deserializer = Self;
fn into_deserializer(self) -> Self::Deserializer {
self
}
}
impl Value {
pub fn from_vector(values: Vec<Value>) -> Self {
Value::Sequence(Sequence {
anchor: None,
elements: values
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::de::from_str_value_preserve;
use crate::value::{Tag, TaggedValue};
use indoc::indoc;
#[test]
fn test_apply_merge_example() {
let config = indoc! {r#"
tasks:
build: &webpack_shared
command: webpack
args: build
inputs:
- 'src/**/*'
start:
<<: *webpack_shared
args: start
"#};
let mut value: Value = crate::from_str(config).unwrap();
value.apply_merge().unwrap();
value.resolve_aliases().unwrap();
assert_eq!(value["tasks"]["start"]["command"], "webpack");
assert_eq!(value["tasks"]["start"]["args"], "start");
}
#[test]
fn test_scalar_in_merge() {
let yaml = indoc!(
r#"
<<: 1
a: 2
"#
);
let mut value: Value = from_str_value_preserve(yaml).unwrap();
let err = value.apply_merge().unwrap_err();
assert_eq!(
err.to_string(),
"expected a mapping or list of mappings for merging, but found scalar"
);
}
#[test]
fn test_tagged_in_merge() {
let yaml = indoc!(
r#"
<<: {}
a: 2
"#
);
let mut value: Value = from_str_value_preserve(yaml).unwrap();
if let Value::Mapping(ref mut map) = value {
let merge = map.get_mut("<<").unwrap();
let inner = std::mem::take(merge);
let tag = Tag::new("foo").unwrap();
*merge = Value::Tagged(Box::new(TaggedValue { tag, value: inner }));
} else {
panic!("expected mapping");
}
let err = value.apply_merge().unwrap_err();
assert_eq!(err.to_string(), "unexpected tagged value in merge");
}
#[test]
fn test_scalar_in_merge_element() {
let yaml = indoc!(
r#"
<<: [1]
a: 2
"#
);
let mut value: Value = from_str_value_preserve(yaml).unwrap();
let err = value.apply_merge().unwrap_err();
assert_eq!(
err.to_string(),
"expected a mapping for merging, but found scalar"
);
}
#[test]
fn test_sequence_in_merge_element() {
let yaml = indoc!(
r#"
<<:
- [1, 2]
a: 2
"#
);
let mut value: Value = from_str_value_preserve(yaml).unwrap();
let err = value.apply_merge().unwrap_err();
assert_eq!(
err.to_string(),
"expected a mapping for merging, but found sequence"
);
}
#[test]
fn test_merge_recursion() {
let yaml = indoc!(
r#"
a: &a
b: 1
"#
);
let mut value: Value = from_str_value_preserve(yaml).unwrap();
if let Value::Mapping(map) = &mut value {
if let Some(Value::Mapping(a_map)) = map.get_mut("a") {
let mut clone = a_map.clone();
clone.id = a_map.id;
a_map.insert("self".into(), Value::Mapping(clone));
}
}
let err = value.apply_merge().unwrap_err();
assert_eq!(err.to_string(), "encountered recursive merge alias");
}
#[test]
fn unresolved_alias_error() {
let yaml = "anchor: &id 1\nalias: *id";
let mut value: Value = from_str_value_preserve(yaml).unwrap();
if let Some(Value::Number(_, anchor)) = value.as_mapping_mut().unwrap().get_mut("anchor") {
*anchor = None;
}
let err = value.resolve_aliases().unwrap_err();
assert_eq!(err.to_string(), "unresolved alias");
}
#[test]
fn cyclic_aliases_error() {
let yaml = "a: &a\n ref: *a\n";
let mut value: Value = from_str_value_preserve(yaml).unwrap();
let err = value.resolve_aliases().unwrap_err();
assert_eq!(err.to_string(), "encountered recursive merge alias");
}
#[test]
fn test_field_inheritance() {
let yaml_input = r#"
defaults: &defaults
adapter: postgres
host: localhost
development:
<<: *defaults
database: dev_db
production:
<<: *defaults
database: prod_db
"#;
let parsed: Value = from_str_value_preserve(yaml_input).unwrap();
let serialized = crate::to_string(&parsed).unwrap();
if serialized.matches("adapter: postgres").count() != 1 {
panic!("Anchors and aliases were not correctly preserved; duplication detected. Serialized output {serialized}");
}
}
fn assert_same_entries(a: &Value, b: &Value) {
let a = a.as_mapping().expect("a: expected a mapping");
let b = b.as_mapping().expect("b: expected a mapping");
assert_eq!(a.len(), b.len());
for a_key in a.keys() {
assert!(b.contains_key(a_key));
let a_value = a
.get(a_key)
.unwrap_or_else(|| panic!("key not present in a: {a_key:?}"))
.as_str();
let b_value = b
.get(a_key)
.unwrap_or_else(|| panic!("key not present in b: {a_key:?}"))
.as_str();
assert_eq!(
a_value,
b_value,
"key {:?} has different values: a {:?}, b {:?}",
a_key.as_str(),
a_value,
b_value
);
}
}
#[test]
fn test_merge_key_example() {
let yaml = r#"
- &CENTER { x: 1, y: 2 }
- &LEFT { x: 0, y: 2 }
- &BIG { r: 10 }
- &SMALL { r: 1 }
- x: 1
y: 2
r: 10
label: center/big
- <<: *CENTER
r: 10
label: center/big
- <<: [ *CENTER, *BIG ]
label: center/big
# And here we have it all:
- <<: [ *BIG, *LEFT, { x: 1, y: 2 } ]
label: center/big
"#;
let value: Value = crate::from_str(yaml).unwrap();
let seq = value.as_sequence().expect("root should be a sequence");
assert_eq!(seq.len(), 8);
let base = &seq[4];
assert_same_entries(base, &seq[5]);
assert_same_entries(base, &seq[6]);
assert_same_entries(base, &seq[7]);
}
#[test]
fn test_self_referential_merge() {
let yaml = "a: &a\n b: 1\n <<: *a";
let mut value: Value = from_str_value_preserve(yaml).unwrap();
assert!(value.apply_merge().is_err());
}
#[test]
fn test_self_referential_after_reallocation() {
let yaml = "a: &a\n b: 1\n <<: *a";
let value: Value = from_str_value_preserve(yaml).unwrap();
let mut vec = Vec::new();
vec.push(Value::Null(None));
vec.push(value);
for _ in 0..100 {
vec.push(Value::Null(None));
}
let mut moved = vec.remove(1);
assert!(moved.apply_merge().is_err());
}
}