use std::collections::{BTreeMap, HashMap};
use crate::error::Error;
use crate::value::Value;
pub trait ToValue {
fn to_value(&self) -> Value;
}
pub trait FromValue: Sized {
fn from_value(value: &Value) -> Result<Self, Error>;
}
fn kind_of(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Int(_) | Value::Uint(_) => "integer",
Value::Float(_) => "float",
Value::Str(_) => "string",
Value::Extended { .. } => "extended scalar",
Value::Seq(_) => "sequence",
Value::Map(_) => "mapping",
}
}
#[cold]
#[inline(never)]
fn type_err(expected: &'static str, found: &Value) -> Error {
Error::type_mismatch(expected, kind_of(found))
}
#[doc(hidden)]
pub fn map_get<'a>(entries: &'a [(Value, Value)], name: &str) -> Option<&'a Value> {
entries.iter().rev().find_map(|(k, v)| match k {
Value::Str(s) if s == name => Some(v),
_ => None,
})
}
#[doc(hidden)]
pub fn field<T: FromValue>(
entries: &[(Value, Value)],
key: &'static str,
ty: &'static str,
) -> Result<T, Error> {
match map_get(entries, key) {
Some(v) => T::from_value(v),
None => Err(Error::missing_field(key, ty)),
}
}
#[doc(hidden)]
pub fn field_or_default<T: FromValue + Default>(
entries: &[(Value, Value)],
key: &str,
) -> Result<T, Error> {
match map_get(entries, key) {
Some(v) => T::from_value(v),
None => Ok(T::default()),
}
}
impl ToValue for Value {
fn to_value(&self) -> Value {
self.clone()
}
}
impl FromValue for Value {
fn from_value(value: &Value) -> Result<Self, Error> {
Ok(value.clone())
}
}
impl<T: ToValue + ?Sized> ToValue for &T {
fn to_value(&self) -> Value {
(**self).to_value()
}
}
impl<T: ToValue + ?Sized> ToValue for Box<T> {
fn to_value(&self) -> Value {
(**self).to_value()
}
}
impl<T: FromValue> FromValue for Box<T> {
fn from_value(value: &Value) -> Result<Self, Error> {
T::from_value(value).map(Box::new)
}
}
impl ToValue for bool {
fn to_value(&self) -> Value {
Value::Bool(*self)
}
}
impl FromValue for bool {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Bool(b) => Ok(*b),
other => Err(type_err("bool", other)),
}
}
}
macro_rules! signed_to_value {
($($t:ty),*) => {$(
impl ToValue for $t {
fn to_value(&self) -> Value { Value::Int(*self as i64) }
}
)*};
}
macro_rules! unsigned_to_value {
($($t:ty),*) => {$(
impl ToValue for $t {
fn to_value(&self) -> Value { Value::Uint(*self as u64) }
}
)*};
}
signed_to_value!(i8, i16, i32, i64, isize);
unsigned_to_value!(u8, u16, u32, u64, usize);
fn as_i128(value: &Value) -> Option<i128> {
match value {
Value::Int(i) => Some(*i as i128),
Value::Uint(u) => Some(*u as i128),
_ => None,
}
}
macro_rules! int_from_value {
($($t:ty),*) => {$(
impl FromValue for $t {
fn from_value(value: &Value) -> Result<Self, Error> {
let n = as_i128(value).ok_or_else(|| type_err("integer", value))?;
<$t>::try_from(n).map_err(|_| Error::int_out_of_range(stringify!($t)))
}
}
)*};
}
int_from_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
impl ToValue for f64 {
fn to_value(&self) -> Value {
Value::Float(*self)
}
}
impl ToValue for f32 {
fn to_value(&self) -> Value {
Value::Float(*self as f64)
}
}
impl FromValue for f64 {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Float(f) => Ok(*f),
Value::Int(i) => Ok(*i as f64),
Value::Uint(u) => Ok(*u as f64),
other => Err(type_err("float", other)),
}
}
}
impl FromValue for f32 {
fn from_value(value: &Value) -> Result<Self, Error> {
f64::from_value(value).map(|f| f as f32)
}
}
impl ToValue for str {
fn to_value(&self) -> Value {
Value::Str(self.to_owned())
}
}
impl ToValue for String {
fn to_value(&self) -> Value {
Value::Str(self.clone())
}
}
impl FromValue for String {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Str(s) => Ok(s.clone()),
other => Err(type_err("string", other)),
}
}
}
impl ToValue for char {
fn to_value(&self) -> Value {
Value::Str(self.to_string())
}
}
impl FromValue for char {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Str(s) => {
let mut chars = s.chars();
match (chars.next(), chars.next()) {
(Some(c), None) => Ok(c),
_ => Err(Error::msg_static("expected a single-character string")),
}
}
other => Err(type_err("char (single-character string)", other)),
}
}
}
impl ToValue for std::path::Path {
fn to_value(&self) -> Value {
Value::Str(self.to_string_lossy().into_owned())
}
}
impl ToValue for std::path::PathBuf {
fn to_value(&self) -> Value {
self.as_path().to_value()
}
}
impl FromValue for std::path::PathBuf {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Str(s) => Ok(std::path::PathBuf::from(s)),
other => Err(type_err("string (path)", other)),
}
}
}
impl<T: ToValue> ToValue for Option<T> {
fn to_value(&self) -> Value {
match self {
Some(t) => t.to_value(),
None => Value::Null,
}
}
}
impl<T: FromValue> FromValue for Option<T> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Null => Ok(None),
other => Ok(Some(T::from_value(other)?)),
}
}
}
impl<T: ToValue> ToValue for [T] {
fn to_value(&self) -> Value {
Value::Seq(self.iter().map(ToValue::to_value).collect())
}
}
impl<T: ToValue> ToValue for Vec<T> {
fn to_value(&self) -> Value {
Value::Seq(self.iter().map(ToValue::to_value).collect())
}
}
impl<T: FromValue> FromValue for Vec<T> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Seq(items) => items.iter().map(T::from_value).collect(),
other => Err(type_err("sequence", other)),
}
}
}
macro_rules! string_map_impls {
($($map:ident),*) => {$(
impl<T: ToValue> ToValue for $map<String, T> {
fn to_value(&self) -> Value {
Value::Map(
self.iter()
.map(|(k, v)| (Value::Str(k.clone()), v.to_value()))
.collect(),
)
}
}
impl<T: FromValue> FromValue for $map<String, T> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Map(entries) => {
let mut out = $map::new();
for (k, v) in entries {
let key = match k {
Value::Str(s) => s.clone(),
other => {
return Err(type_err("string (map key)", other));
}
};
out.insert(key, T::from_value(v)?);
}
Ok(out)
}
other => Err(type_err("mapping", other)),
}
}
}
)*};
}
string_map_impls!(BTreeMap, HashMap);
#[cfg(feature = "indexmap")]
use indexmap::IndexMap;
#[cfg(feature = "indexmap")]
string_map_impls!(IndexMap);