use crate::prelude::*;
use core::cmp::Ordering;
use core::hash::{Hash, Hasher};
mod convert;
mod mapping;
mod number;
mod serde_impl;
pub use mapping::{Mapping, MappingAny};
pub use number::{Number, ParseNumberError};
pub type Sequence = Vec<Value>;
mod tag;
pub use tag::{MaybeTag, Tag, TaggedValue, check_for_tag, nobang};
pub(crate) use tag::{TAGGED_VALUE_FIELD_TAG, TAGGED_VALUE_FIELD_VALUE, TagPreservingMapAccess};
#[derive(Debug, Clone, Default)]
pub enum Value {
#[default]
Null,
Bool(bool),
Number(Number),
String(String),
Sequence(Sequence),
Mapping(Mapping),
Tagged(Box<TaggedValue>),
}
impl Value {
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
#[must_use]
pub fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
#[must_use]
pub fn is_number(&self) -> bool {
matches!(self, Value::Number(_))
}
#[must_use]
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
#[must_use]
pub fn is_sequence(&self) -> bool {
matches!(self, Value::Sequence(_))
}
#[must_use]
pub fn is_mapping(&self) -> bool {
matches!(self, Value::Mapping(_))
}
#[must_use]
pub fn is_tagged(&self) -> bool {
matches!(self, Value::Tagged(_))
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
#[must_use]
pub fn as_null(&self) -> Option<()> {
match self {
Value::Null => Some(()),
_ => None,
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
match self {
Value::Number(n) => n.as_i64(),
_ => None,
}
}
#[must_use]
pub fn as_u64(&self) -> Option<u64> {
match self {
Value::Number(n) => n.as_u64(),
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(n.as_f64()),
_ => None,
}
}
#[must_use]
pub fn is_i64(&self) -> bool {
match self {
Value::Number(n) => n.is_i64(),
_ => false,
}
}
#[must_use]
pub fn is_u64(&self) -> bool {
match self {
Value::Number(n) => n.is_u64(),
_ => false,
}
}
#[must_use]
pub fn is_f64(&self) -> bool {
matches!(self, Value::Number(_))
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_sequence(&self) -> Option<&Sequence> {
match self {
Value::Sequence(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_sequence_mut(&mut self) -> Option<&mut Sequence> {
match self {
Value::Sequence(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_mapping(&self) -> Option<&Mapping> {
match self {
Value::Mapping(m) => Some(m),
_ => None,
}
}
#[must_use]
pub fn as_mapping_mut(&mut self) -> Option<&mut Mapping> {
match self {
Value::Mapping(m) => Some(m),
_ => None,
}
}
#[must_use]
pub fn as_tagged(&self) -> Option<&TaggedValue> {
match self {
Value::Tagged(t) => Some(t),
_ => None,
}
}
#[must_use]
pub fn as_tagged_mut(&mut self) -> Option<&mut TaggedValue> {
match self {
Value::Tagged(t) => Some(t),
_ => None,
}
}
#[must_use]
pub fn get<I: ValueIndex>(&self, index: I) -> Option<&Value> {
index.index_into(self)
}
#[must_use]
pub fn get_mut<I: ValueIndex>(&mut self, index: I) -> Option<&mut Value> {
index.index_into_mut(self)
}
#[must_use]
pub fn get_path(&self, path: &str) -> Option<&Value> {
let segments = parse_path(path);
let mut current = self;
for segment in segments {
current = match segment {
QuerySegment::Key(key) => current.get(key.as_str())?,
QuerySegment::Index(idx) => current.get(idx)?,
QuerySegment::Wildcard | QuerySegment::RecursiveDescent => {
return self.query(path).into_iter().next();
}
};
}
Some(current)
}
#[must_use]
pub fn query(&self, path: &str) -> Vec<&Value> {
let segments = parse_path(path);
let mut results = Vec::new();
query_recursive(self, &segments, 0, &mut results);
results
}
#[must_use]
pub fn get_path_mut(&mut self, path: &str) -> Option<&mut Value> {
let segments = parse_path(path);
let mut current = self;
for segment in segments {
current = match segment {
QuerySegment::Key(key) => current.get_mut(key.as_str())?,
QuerySegment::Index(idx) => current.get_mut(idx)?,
QuerySegment::Wildcard | QuerySegment::RecursiveDescent => return None,
};
}
Some(current)
}
pub fn merge(&mut self, other: Value) {
match (self, other) {
(Value::Mapping(base), Value::Mapping(other)) => {
for (key, other_value) in other {
match base.get_mut(&key) {
Some(base_value) => {
base_value.merge(other_value);
}
None => {
let _ = base.insert(key, other_value);
}
}
}
}
(this, other) => {
*this = other;
}
}
}
pub fn merge_concat(&mut self, other: Value) {
match (self, other) {
(Value::Mapping(base), Value::Mapping(other)) => {
for (key, other_value) in other {
match base.get_mut(&key) {
Some(base_value) => {
base_value.merge_concat(other_value);
}
None => {
let _ = base.insert(key, other_value);
}
}
}
}
(Value::Sequence(base), Value::Sequence(other)) => {
base.extend(other);
}
(this, other) => {
*this = other;
}
}
}
pub fn remove(&mut self, key: &str) -> Option<Value> {
match self {
Value::Mapping(map) => map.shift_remove(key),
_ => None,
}
}
pub fn insert(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
match self {
Value::Mapping(map) => map.insert(key.into(), value),
_ => None,
}
}
pub fn apply_merge(&mut self) -> crate::Result<()> {
match self {
Value::Mapping(mapping) => {
for value in mapping.values_mut() {
value.apply_merge()?;
}
let merge_value = mapping.remove("<<");
let merge_sequence = match merge_value {
Some(Value::Sequence(seq)) => seq,
Some(value) => vec![value],
None => vec![],
};
for value in merge_sequence {
match value {
Value::Mapping(merge_map) => {
for (k, v) in merge_map {
let _ = mapping.entry(k).or_insert(v);
}
}
Value::Sequence(_) => {
return Err(crate::Error::SequenceInMergeElement);
}
Value::Tagged(_) => {
return Err(crate::Error::TaggedInMerge);
}
_ => {
return Err(crate::Error::ScalarInMergeElement);
}
}
}
}
Value::Sequence(seq) => {
for value in seq {
value.apply_merge()?;
}
}
Value::Tagged(tagged) => {
tagged.value_mut().apply_merge()?;
}
_ => {}
}
Ok(())
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn interpolate_properties<S>(
&mut self,
properties: &std::collections::HashMap<String, S>,
) -> crate::Result<()>
where
S: AsRef<str>,
{
self.interpolate_inner(
&|name| match properties.get(name) {
Some(v) => ResolveOutcome::Found(v.as_ref().to_owned()),
None => ResolveOutcome::Missing,
},
MissingAction::Error(false),
)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn interpolate_properties_redacted<S>(
&mut self,
properties: &std::collections::HashMap<String, S>,
) -> crate::Result<()>
where
S: AsRef<str>,
{
self.interpolate_inner(
&|name| match properties.get(name) {
Some(v) => ResolveOutcome::Found(v.as_ref().to_owned()),
None => ResolveOutcome::Missing,
},
MissingAction::Error(true),
)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn interpolate_properties_lossy<S>(
&mut self,
properties: &std::collections::HashMap<String, S>,
) where
S: AsRef<str>,
{
let _ = self.interpolate_inner(
&|name| match properties.get(name) {
Some(v) => ResolveOutcome::Found(v.as_ref().to_owned()),
None => ResolveOutcome::Missing,
},
MissingAction::Empty,
);
}
#[cfg(feature = "std")]
pub(crate) fn interpolate_inner(
&mut self,
resolve: &dyn Fn(&str) -> ResolveOutcome,
missing_action: MissingAction,
) -> crate::Result<()> {
match self {
Value::String(s) => {
if let Some(updated) = expand_placeholders(s, resolve, missing_action)? {
*s = updated;
}
}
Value::Sequence(seq) => {
for v in seq {
v.interpolate_inner(resolve, missing_action)?;
}
}
Value::Mapping(map) => {
for v in map.values_mut() {
v.interpolate_inner(resolve, missing_action)?;
}
}
Value::Tagged(tagged) => {
tagged
.value_mut()
.interpolate_inner(resolve, missing_action)?;
}
Value::Null | Value::Bool(_) | Value::Number(_) => {}
}
Ok(())
}
#[must_use]
pub fn untag(self) -> Self {
match self {
Value::Tagged(tagged) => tagged.value.untag(),
Value::Sequence(seq) => Value::Sequence(seq.into_iter().map(Value::untag).collect()),
Value::Mapping(map) => {
let untagged: Mapping = map.into_iter().map(|(k, v)| (k, v.untag())).collect();
Value::Mapping(untagged)
}
other => other,
}
}
#[must_use]
pub fn untag_ref(&self) -> &Self {
match self {
Value::Tagged(tagged) => tagged.value.untag_ref(),
other => other,
}
}
#[must_use]
pub fn untag_mut(&mut self) -> &mut Self {
match self {
Value::Tagged(tagged) => tagged.value.untag_mut(),
other => other,
}
}
}
use crate::path::{QuerySegment, parse_query_path};
fn parse_path(path: &str) -> Vec<QuerySegment> {
parse_query_path(path)
}
fn query_recursive<'a>(
value: &'a Value,
segments: &[QuerySegment],
depth: usize,
results: &mut Vec<&'a Value>,
) {
if depth >= segments.len() {
results.push(value);
return;
}
match &segments[depth] {
QuerySegment::Key(key) => {
if let Some(child) = value.get(key.as_str()) {
query_recursive(child, segments, depth + 1, results);
}
}
QuerySegment::Index(idx) => {
if let Some(child) = value.get(*idx) {
query_recursive(child, segments, depth + 1, results);
}
}
QuerySegment::Wildcard => match value {
Value::Sequence(seq) => {
for item in seq {
query_recursive(item, segments, depth + 1, results);
}
}
Value::Mapping(map) => {
for (_, v) in map.iter() {
query_recursive(v, segments, depth + 1, results);
}
}
_ => {}
},
QuerySegment::RecursiveDescent => {
let remaining = &segments[depth + 1..];
if !remaining.is_empty() {
query_recursive(value, segments, depth + 1, results);
match value {
Value::Sequence(seq) => {
for item in seq {
query_recursive(item, segments, depth, results);
}
}
Value::Mapping(map) => {
for (_, v) in map.iter() {
query_recursive(v, segments, depth, results);
}
}
Value::Tagged(t) => {
query_recursive(t.value(), segments, depth, results);
}
_ => {}
}
}
}
}
}
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::Number(a), Value::Number(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Sequence(a), Value::Sequence(b)) => a == b,
(Value::Mapping(a), Value::Mapping(b)) => a == b,
(Value::Tagged(a), Value::Tagged(b)) => a == b,
_ => false,
}
}
}
impl Eq for Value {}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Value::Null => {}
Value::Bool(b) => b.hash(state),
Value::Number(n) => n.hash(state),
Value::String(s) => s.hash(state),
Value::Sequence(seq) => {
seq.len().hash(state);
for v in seq {
v.hash(state);
}
}
Value::Mapping(map) => {
map.len().hash(state);
for (k, v) in map {
k.hash(state);
v.hash(state);
}
}
Value::Tagged(tagged) => {
tagged.tag().hash(state);
tagged.value().hash(state);
}
}
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Value {
fn cmp(&self, other: &Self) -> Ordering {
fn type_order(v: &Value) -> u8 {
match v {
Value::Null => 0,
Value::Bool(_) => 1,
Value::Number(_) => 2,
Value::String(_) => 3,
Value::Sequence(_) => 4,
Value::Mapping(_) => 5,
Value::Tagged(_) => 6,
}
}
match type_order(self).cmp(&type_order(other)) {
Ordering::Equal => {}
ord => return ord,
}
match (self, other) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
(Value::Number(a), Value::Number(b)) => a.cmp(b),
(Value::String(a), Value::String(b)) => a.cmp(b),
(Value::Sequence(a), Value::Sequence(b)) => a.len().cmp(&b.len()).then_with(|| {
for (av, bv) in a.iter().zip(b.iter()) {
match av.cmp(bv) {
Ordering::Equal => continue,
ord => return ord,
}
}
Ordering::Equal
}),
(Value::Mapping(a), Value::Mapping(b)) => a.len().cmp(&b.len()).then_with(|| {
for ((ak, av), (bk, bv)) in a.iter().zip(b.iter()) {
match ak.cmp(bk) {
Ordering::Equal => {}
ord => return ord,
}
match av.cmp(bv) {
Ordering::Equal => continue,
ord => return ord,
}
}
Ordering::Equal
}),
(Value::Tagged(a), Value::Tagged(b)) => a
.tag()
.as_str()
.cmp(b.tag().as_str())
.then_with(|| a.value().cmp(b.value())),
_ => unreachable!("type_order check ensures same variants"),
}
}
}
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::Number(n) => write!(f, "{n}"),
Value::String(s) => write!(f, "{s}"),
Value::Sequence(s) => {
write!(f, "[")?;
for (i, v) in s.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{v}")?;
}
write!(f, "]")
}
Value::Mapping(m) => {
write!(f, "{{")?;
for (i, (k, v)) in m.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
Value::Tagged(t) => write!(f, "{t}"),
}
}
}
pub trait ValueIndex {
fn index_into(self, value: &Value) -> Option<&Value>;
fn index_into_mut(self, value: &mut Value) -> Option<&mut Value>;
fn index_or_insert(self, value: &mut Value) -> &mut Value;
}
impl ValueIndex for usize {
fn index_into(self, value: &Value) -> Option<&Value> {
match value {
Value::Sequence(s) => s.get(self),
Value::Tagged(t) => self.index_into(t.value()),
_ => None,
}
}
fn index_into_mut(self, value: &mut Value) -> Option<&mut Value> {
match value {
Value::Sequence(s) => s.get_mut(self),
Value::Tagged(t) => self.index_into_mut(t.value_mut()),
_ => None,
}
}
#[track_caller]
fn index_or_insert(self, value: &mut Value) -> &mut Value {
match value {
Value::Sequence(s) => {
let len = s.len();
s.get_mut(self).unwrap_or_else(|| {
panic!(
"cannot access index {} of YAML sequence of length {}",
self, len
)
})
}
Value::Tagged(t) => self.index_or_insert(t.value_mut()),
_ => panic!(
"cannot access index {} of YAML {}",
self,
value_type_name(value)
),
}
}
}
impl ValueIndex for &str {
fn index_into(self, value: &Value) -> Option<&Value> {
match value {
Value::Mapping(m) => m.get(self),
Value::Tagged(t) => self.index_into(t.value()),
_ => None,
}
}
fn index_into_mut(self, value: &mut Value) -> Option<&mut Value> {
match value {
Value::Mapping(m) => m.get_mut(self),
Value::Tagged(t) => self.index_into_mut(t.value_mut()),
_ => None,
}
}
#[track_caller]
fn index_or_insert(self, value: &mut Value) -> &mut Value {
if let Value::Null = value {
*value = Value::Mapping(Mapping::new());
}
match value {
Value::Mapping(m) => {
let _ = m.entry(self.to_owned()).or_insert(Value::Null);
m.get_mut(self).unwrap()
}
Value::Tagged(t) => self.index_or_insert(t.value_mut()),
_ => panic!(
"cannot access key {:?} in YAML {}",
self,
value_type_name(value)
),
}
}
}
impl ValueIndex for String {
fn index_into(self, value: &Value) -> Option<&Value> {
self.as_str().index_into(value)
}
fn index_into_mut(self, value: &mut Value) -> Option<&mut Value> {
self.as_str().index_into_mut(value)
}
#[track_caller]
fn index_or_insert(self, value: &mut Value) -> &mut Value {
self.as_str().index_or_insert(value)
}
}
impl ValueIndex for &String {
fn index_into(self, value: &Value) -> Option<&Value> {
self.as_str().index_into(value)
}
fn index_into_mut(self, value: &mut Value) -> Option<&mut Value> {
self.as_str().index_into_mut(value)
}
#[track_caller]
fn index_or_insert(self, value: &mut Value) -> &mut Value {
self.as_str().index_or_insert(value)
}
}
impl ValueIndex for &Value {
fn index_into(self, value: &Value) -> Option<&Value> {
match self {
Value::String(s) => s.as_str().index_into(value),
Value::Number(Number::Integer(n)) if *n >= 0 => {
usize::try_from(*n).ok()?.index_into(value)
}
_ => None,
}
}
fn index_into_mut(self, value: &mut Value) -> Option<&mut Value> {
match self {
Value::String(s) => s.as_str().index_into_mut(value),
Value::Number(Number::Integer(n)) if *n >= 0 => {
usize::try_from(*n).ok()?.index_into_mut(value)
}
_ => None,
}
}
#[track_caller]
fn index_or_insert(self, value: &mut Value) -> &mut Value {
match self {
Value::String(s) => s.as_str().index_or_insert(value),
Value::Number(Number::Integer(n)) if *n >= 0 => {
let idx =
usize::try_from(*n).unwrap_or_else(|_| panic!("index {} overflows usize", n));
idx.index_or_insert(value)
}
_ => panic!("cannot index with {:?}", self),
}
}
}
#[cfg(feature = "std")]
pub(crate) enum ResolveOutcome {
Found(String),
Missing,
#[allow(dead_code)]
Error(crate::Error),
}
#[cfg(feature = "std")]
#[derive(Debug, Clone, Copy)]
pub(crate) enum MissingAction {
Empty,
Error(bool),
}
#[cfg(feature = "std")]
fn expand_placeholders(
s: &str,
resolve: &dyn Fn(&str) -> ResolveOutcome,
missing_action: MissingAction,
) -> crate::Result<Option<String>> {
let bytes = s.as_bytes();
if !bytes.contains(&b'$') && !bytes.contains(&b'}') {
return Ok(None);
}
let mut out = String::with_capacity(s.len());
let mut i = 0;
let mut touched = false;
while i < bytes.len() {
let b = bytes[i];
if b == b'$' && i + 1 < bytes.len() && bytes[i + 1] == b'$' {
out.push('$');
i += 2;
touched = true;
continue;
}
if b == b'$' && i + 1 < bytes.len() && bytes[i + 1] == b'{' {
if i + 2 < bytes.len() && bytes[i + 2] == b'{' {
out.push_str("${");
i += 3;
touched = true;
continue;
}
let name_start = i + 2;
let mut j = name_start;
while j < bytes.len() && bytes[j] != b'}' && bytes[j] != b':' {
let c = bytes[j];
let ok = c.is_ascii_alphanumeric() || c == b'_' || c == b'.';
if !ok {
return Err(crate::Error::Custom(format!(
"interpolate_properties: invalid character {:?} in placeholder",
c as char
)));
}
j += 1;
}
if j >= bytes.len() {
return Err(crate::Error::Custom(
"interpolate_properties: unterminated `${...}` placeholder".into(),
));
}
if name_start == j {
return Err(crate::Error::Custom(
"interpolate_properties: empty placeholder `${}`".into(),
));
}
let name = &s[name_start..j];
let mut default: Option<&str> = None;
let mut close = j;
if bytes[j] == b':' {
if j + 1 >= bytes.len() || bytes[j + 1] != b'-' {
return Err(crate::Error::Custom(
"interpolate_properties: expected `:-default` after `${name:`".into(),
));
}
let default_start = j + 2;
let mut k = default_start;
while k < bytes.len() && bytes[k] != b'}' {
k += 1;
}
if k >= bytes.len() {
return Err(crate::Error::Custom(
"interpolate_properties: unterminated `${name:-default}`".into(),
));
}
default = Some(&s[default_start..k]);
close = k;
}
let value = match resolve(name) {
ResolveOutcome::Found(v) => v,
ResolveOutcome::Missing => match default {
Some(d) => d.to_owned(),
None => match missing_action {
MissingAction::Empty => String::new(),
MissingAction::Error(redact) => {
return Err(crate::Error::Custom(if redact {
"interpolate_properties: unknown placeholder `${<redacted>}`".into()
} else {
format!("interpolate_properties: unknown placeholder `${{{name}}}`")
}));
}
},
},
ResolveOutcome::Error(e) => return Err(e),
};
out.push_str(&value);
i = close + 1;
touched = true;
continue;
}
if b == b'}' && i + 1 < bytes.len() && bytes[i + 1] == b'}' {
out.push('}');
i += 2;
touched = true;
continue;
}
let c = s[i..].chars().next().expect("char at boundary");
out.push(c);
i += c.len_utf8();
}
if touched { Ok(Some(out)) } else { Ok(None) }
}
fn value_type_name(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Sequence(_) => "sequence",
Value::Mapping(_) => "mapping",
Value::Tagged(_) => "tagged value",
}
}