mirror of
https://github.com/nushell/nushell.git
synced 2025-03-13 15:08:43 +01:00
820 lines
26 KiB
Rust
820 lines
26 KiB
Rust
use crate::{
|
|
ast::{CellPath, PathMember},
|
|
engine::Closure,
|
|
NuGlob, Range, Record, ShellError, Span, Spanned, Type, Value,
|
|
};
|
|
use chrono::{DateTime, FixedOffset};
|
|
use std::{
|
|
any,
|
|
cmp::Ordering,
|
|
collections::{HashMap, VecDeque},
|
|
fmt,
|
|
path::PathBuf,
|
|
str::FromStr,
|
|
};
|
|
|
|
/// A trait for loading a value from a [`Value`].
|
|
///
|
|
/// # Derivable
|
|
/// This trait can be used with `#[derive]`.
|
|
///
|
|
/// When derived on structs with named fields, it expects a [`Value::Record`] where each field of
|
|
/// the struct maps to a corresponding field in the record.
|
|
///
|
|
/// - If `#[nu_value(rename = "...")]` is applied to a field, that name will be used as the key in
|
|
/// the record.
|
|
/// - If `#[nu_value(rename_all = "...")]` is applied on the container (struct) the key of the
|
|
/// field will be case-converted accordingly.
|
|
/// - If neither attribute is applied, the field name is used as is.
|
|
///
|
|
/// Supported case conversions include those provided by [`heck`], such as
|
|
/// "snake_case", "kebab-case", "PascalCase", and others.
|
|
/// Additionally, all values accepted by
|
|
/// [`#[serde(rename_all = "...")]`](https://serde.rs/container-attrs.html#rename_all) are valid here.
|
|
///
|
|
/// For structs with unnamed fields, it expects a [`Value::List`], and the fields are populated in
|
|
/// the order they appear in the list.
|
|
/// Unit structs expect a [`Value::Nothing`], as they contain no data.
|
|
/// Attempting to convert from a non-matching `Value` type will result in an error.
|
|
///
|
|
/// Only enums with no fields may derive this trait.
|
|
/// The expected value representation will be the name of the variant as a [`Value::String`].
|
|
///
|
|
/// - If `#[nu_value(rename = "...")]` is applied to a variant, that name will be used.
|
|
/// - If `#[nu_value(rename_all = "...")]` is applied on the enum container, the name of variant
|
|
/// will be case-converted accordingly.
|
|
/// - If neither attribute is applied, the variant name will default to
|
|
/// ["snake_case"](heck::ToSnakeCase).
|
|
///
|
|
/// Additionally, you can use `#[nu_value(type_name = "...")]` in the derive macro to set a custom type name
|
|
/// for `FromValue::expected_type`. This will result in a `Type::Custom` with the specified type name.
|
|
/// This can be useful in situations where the default type name is not desired.
|
|
///
|
|
/// # Enum Example
|
|
/// ```
|
|
/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
|
|
/// #
|
|
/// # let span = Span::unknown();
|
|
/// #
|
|
/// #[derive(FromValue, Debug, PartialEq)]
|
|
/// #[nu_value(rename_all = "COBOL-CASE", type_name = "birb")]
|
|
/// enum Bird {
|
|
/// MountainEagle,
|
|
/// ForestOwl,
|
|
/// #[nu_value(rename = "RIVER-QUACK")]
|
|
/// RiverDuck,
|
|
/// }
|
|
///
|
|
/// assert_eq!(
|
|
/// Bird::from_value(Value::string("FOREST-OWL", span)).unwrap(),
|
|
/// Bird::ForestOwl
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// Bird::from_value(Value::string("RIVER-QUACK", span)).unwrap(),
|
|
/// Bird::RiverDuck
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// &Bird::expected_type().to_string(),
|
|
/// "birb"
|
|
/// );
|
|
/// ```
|
|
///
|
|
/// # Struct Example
|
|
/// ```
|
|
/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
|
|
/// #
|
|
/// # let span = Span::unknown();
|
|
/// #
|
|
/// #[derive(FromValue, PartialEq, Eq, Debug)]
|
|
/// #[nu_value(rename_all = "kebab-case")]
|
|
/// struct Person {
|
|
/// first_name: String,
|
|
/// last_name: String,
|
|
/// #[nu_value(rename = "age")]
|
|
/// age_years: u32,
|
|
/// }
|
|
///
|
|
/// let value = Value::record(record! {
|
|
/// "first-name" => Value::string("John", span),
|
|
/// "last-name" => Value::string("Doe", span),
|
|
/// "age" => Value::int(42, span),
|
|
/// }, span);
|
|
///
|
|
/// assert_eq!(
|
|
/// Person::from_value(value).unwrap(),
|
|
/// Person {
|
|
/// first_name: "John".into(),
|
|
/// last_name: "Doe".into(),
|
|
/// age_years: 42,
|
|
/// }
|
|
/// );
|
|
/// ```
|
|
pub trait FromValue: Sized {
|
|
// TODO: instead of ShellError, maybe we could have a FromValueError that implements Into<ShellError>
|
|
/// Loads a value from a [`Value`].
|
|
///
|
|
/// This method retrieves a value similarly to how strings are parsed using [`FromStr`].
|
|
/// The operation might fail if the `Value` contains unexpected types or structures.
|
|
fn from_value(v: Value) -> Result<Self, ShellError>;
|
|
|
|
/// Expected `Value` type.
|
|
///
|
|
/// This is used to print out errors of what type of value is expected for conversion.
|
|
/// Even if not used in [`from_value`](FromValue::from_value) this should still be implemented
|
|
/// so that other implementations like `Option` or `Vec` can make use of it.
|
|
/// It is advised to call this method in `from_value` to ensure that expected type in the error
|
|
/// is consistent.
|
|
///
|
|
/// Unlike the default implementation, derived implementations explicitly reveal the concrete
|
|
/// type, such as [`Type::Record`] or [`Type::List`], instead of an opaque type.
|
|
fn expected_type() -> Type {
|
|
Type::Custom(
|
|
any::type_name::<Self>()
|
|
.split(':')
|
|
.last()
|
|
.expect("str::split returns an iterator with at least one element")
|
|
.to_string()
|
|
.into_boxed_str(),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Primitive Types
|
|
|
|
impl<T, const N: usize> FromValue for [T; N]
|
|
where
|
|
T: FromValue,
|
|
{
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let span = v.span();
|
|
let v_ty = v.get_type();
|
|
let vec = Vec::<T>::from_value(v)?;
|
|
vec.try_into()
|
|
.map_err(|err_vec: Vec<T>| ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v_ty.to_string(),
|
|
span,
|
|
help: Some(match err_vec.len().cmp(&N) {
|
|
Ordering::Less => format!(
|
|
"input list too short ({}), expected length of {N}, add missing values",
|
|
err_vec.len()
|
|
),
|
|
Ordering::Equal => {
|
|
unreachable!("conversion would have worked if the length would be the same")
|
|
}
|
|
Ordering::Greater => format!(
|
|
"input list too long ({}), expected length of {N}, remove trailing values",
|
|
err_vec.len()
|
|
),
|
|
}),
|
|
})
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Custom(format!("list<{};{N}>", T::expected_type()).into_boxed_str())
|
|
}
|
|
}
|
|
|
|
impl FromValue for bool {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Bool { val, .. } => Ok(val),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Bool
|
|
}
|
|
}
|
|
|
|
impl FromValue for char {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let span = v.span();
|
|
let v_ty = v.get_type();
|
|
match v {
|
|
Value::String { ref val, .. } => match char::from_str(val) {
|
|
Ok(c) => Ok(c),
|
|
Err(_) => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v_ty.to_string(),
|
|
span,
|
|
help: Some("make the string only one char long".to_string()),
|
|
}),
|
|
},
|
|
_ => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v_ty.to_string(),
|
|
span,
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::String
|
|
}
|
|
}
|
|
|
|
impl FromValue for f32 {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
f64::from_value(v).map(|float| float as f32)
|
|
}
|
|
}
|
|
|
|
impl FromValue for f64 {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Float { val, .. } => Ok(val),
|
|
Value::Int { val, .. } => Ok(val as f64),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Float
|
|
}
|
|
}
|
|
|
|
impl FromValue for i64 {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Int { val, .. } => Ok(val),
|
|
Value::Duration { val, .. } => Ok(val),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Int
|
|
}
|
|
}
|
|
|
|
macro_rules! impl_from_value_for_int {
|
|
($type:ty) => {
|
|
impl FromValue for $type {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let span = v.span();
|
|
let int = i64::from_value(v)?;
|
|
const MIN: i64 = <$type>::MIN as i64;
|
|
const MAX: i64 = <$type>::MAX as i64;
|
|
#[allow(overlapping_range_endpoints)] // calculating MIN-1 is not possible for i64::MIN
|
|
#[allow(unreachable_patterns)] // isize might max out i64 number range
|
|
<$type>::try_from(int).map_err(|_| match int {
|
|
MIN..=MAX => unreachable!(
|
|
"int should be within the valid range for {}",
|
|
stringify!($type)
|
|
),
|
|
i64::MIN..=MIN => int_too_small_error(int, <$type>::MIN, span),
|
|
MAX..=i64::MAX => int_too_large_error(int, <$type>::MAX, span),
|
|
})
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
i64::expected_type()
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl_from_value_for_int!(i8);
|
|
impl_from_value_for_int!(i16);
|
|
impl_from_value_for_int!(i32);
|
|
impl_from_value_for_int!(isize);
|
|
|
|
macro_rules! impl_from_value_for_uint {
|
|
($type:ty, $max:expr) => {
|
|
impl FromValue for $type {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let span = v.span();
|
|
const MAX: i64 = $max;
|
|
match v {
|
|
Value::Int { val, .. } | Value::Duration { val, .. } => {
|
|
match val {
|
|
i64::MIN..=-1 => Err(ShellError::NeedsPositiveValue { span }),
|
|
0..=MAX => Ok(val as $type),
|
|
#[allow(unreachable_patterns)] // u64 will max out the i64 number range
|
|
n => Err(ShellError::GenericError {
|
|
error: "Integer too large".to_string(),
|
|
msg: format!("{n} is larger than {MAX}"),
|
|
span: Some(span),
|
|
help: None,
|
|
inner: vec![],
|
|
}),
|
|
}
|
|
}
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Custom("non-negative int".to_string().into_boxed_str())
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Sadly we cannot implement FromValue for u8 without losing the impl of Vec<u8>,
|
|
// Rust would find two possible implementations then, Vec<u8> and Vec<T = u8>,
|
|
// and wouldn't compile.
|
|
// The blanket implementation for Vec<T> is probably more useful than
|
|
// implementing FromValue for u8.
|
|
|
|
impl_from_value_for_uint!(u16, u16::MAX as i64);
|
|
impl_from_value_for_uint!(u32, u32::MAX as i64);
|
|
impl_from_value_for_uint!(u64, i64::MAX); // u64::Max would be -1 as i64
|
|
#[cfg(target_pointer_width = "64")]
|
|
impl_from_value_for_uint!(usize, i64::MAX);
|
|
#[cfg(target_pointer_width = "32")]
|
|
impl_from_value_for_uint!(usize, usize::MAX as i64);
|
|
|
|
impl FromValue for () {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Nothing { .. } => Ok(()),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Nothing
|
|
}
|
|
}
|
|
|
|
macro_rules! tuple_from_value {
|
|
($template:literal, $($t:ident:$n:tt),+) => {
|
|
impl<$($t),+> FromValue for ($($t,)+) where $($t: FromValue,)+ {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let span = v.span();
|
|
match v {
|
|
Value::List { vals, .. } => {
|
|
let mut deque = VecDeque::from(vals);
|
|
|
|
Ok(($(
|
|
{
|
|
let v = deque.pop_front().ok_or_else(|| ShellError::CantFindColumn {
|
|
col_name: $n.to_string(),
|
|
span: None,
|
|
src_span: span
|
|
})?;
|
|
$t::from_value(v)?
|
|
},
|
|
)*))
|
|
},
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Custom(
|
|
format!(
|
|
$template,
|
|
$($t::expected_type()),*
|
|
)
|
|
.into_boxed_str(),
|
|
)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Tuples in std are implemented for up to 12 elements, so we do it here too.
|
|
tuple_from_value!("[{}]", T0:0);
|
|
tuple_from_value!("[{}, {}]", T0:0, T1:1);
|
|
tuple_from_value!("[{}, {}, {}]", T0:0, T1:1, T2:2);
|
|
tuple_from_value!("[{}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10);
|
|
tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10, T11:11);
|
|
|
|
// Other std Types
|
|
|
|
impl FromValue for PathBuf {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::String { val, .. } => Ok(val.into()),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::String
|
|
}
|
|
}
|
|
|
|
impl FromValue for String {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
// FIXME: we may want to fail a little nicer here
|
|
match v {
|
|
Value::CellPath { val, .. } => Ok(val.to_string()),
|
|
Value::String { val, .. } => Ok(val),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::String
|
|
}
|
|
}
|
|
|
|
// This impl is different from Vec<T> as it allows reading from Value::Binary and Value::String too.
|
|
// This also denies implementing FromValue for u8 as it would be in conflict with the Vec<T> impl.
|
|
impl FromValue for Vec<u8> {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Binary { val, .. } => Ok(val),
|
|
Value::String { val, .. } => Ok(val.into_bytes()),
|
|
Value::List { vals, .. } => {
|
|
const U8MIN: i64 = u8::MIN as i64;
|
|
const U8MAX: i64 = u8::MAX as i64;
|
|
let mut this = Vec::with_capacity(vals.len());
|
|
for val in vals {
|
|
let span = val.span();
|
|
let int = i64::from_value(val)?;
|
|
// calculating -1 on these ranges would be less readable
|
|
#[allow(overlapping_range_endpoints)]
|
|
#[allow(clippy::match_overlapping_arm)]
|
|
match int {
|
|
U8MIN..=U8MAX => this.push(int as u8),
|
|
i64::MIN..=U8MIN => return Err(int_too_small_error(int, U8MIN, span)),
|
|
U8MAX..=i64::MAX => return Err(int_too_large_error(int, U8MAX, span)),
|
|
};
|
|
}
|
|
Ok(this)
|
|
}
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Binary
|
|
}
|
|
}
|
|
|
|
// Blanket std Implementations
|
|
|
|
impl<T> FromValue for Option<T>
|
|
where
|
|
T: FromValue,
|
|
{
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Nothing { .. } => Ok(None),
|
|
v => T::from_value(v).map(Option::Some),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
T::expected_type()
|
|
}
|
|
}
|
|
|
|
impl<V> FromValue for HashMap<String, V>
|
|
where
|
|
V: FromValue,
|
|
{
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let record = v.into_record()?;
|
|
let items: Result<Vec<(String, V)>, ShellError> = record
|
|
.into_iter()
|
|
.map(|(k, v)| Ok((k, V::from_value(v)?)))
|
|
.collect();
|
|
Ok(HashMap::from_iter(items?))
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Record(vec![].into_boxed_slice())
|
|
}
|
|
}
|
|
|
|
impl<T> FromValue for Vec<T>
|
|
where
|
|
T: FromValue,
|
|
{
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::List { vals, .. } => vals
|
|
.into_iter()
|
|
.map(|v| T::from_value(v))
|
|
.collect::<Result<Vec<T>, ShellError>>(),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::List(Box::new(T::expected_type()))
|
|
}
|
|
}
|
|
|
|
// Nu Types
|
|
|
|
impl FromValue for Value {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
Ok(v)
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Any
|
|
}
|
|
}
|
|
|
|
impl FromValue for CellPath {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let span = v.span();
|
|
match v {
|
|
Value::CellPath { val, .. } => Ok(val),
|
|
Value::String { val, .. } => Ok(CellPath {
|
|
members: vec![PathMember::String {
|
|
val,
|
|
span,
|
|
optional: false,
|
|
}],
|
|
}),
|
|
Value::Int { val, .. } => {
|
|
if val.is_negative() {
|
|
Err(ShellError::NeedsPositiveValue { span })
|
|
} else {
|
|
Ok(CellPath {
|
|
members: vec![PathMember::Int {
|
|
val: val as usize,
|
|
span,
|
|
optional: false,
|
|
}],
|
|
})
|
|
}
|
|
}
|
|
x => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: x.get_type().to_string(),
|
|
span,
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::CellPath
|
|
}
|
|
}
|
|
|
|
impl FromValue for Closure {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Closure { val, .. } => Ok(*val),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromValue for DateTime<FixedOffset> {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Date { val, .. } => Ok(val),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Date
|
|
}
|
|
}
|
|
|
|
impl FromValue for NuGlob {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
// FIXME: we may want to fail a little nicer here
|
|
match v {
|
|
Value::CellPath { val, .. } => Ok(NuGlob::Expand(val.to_string())),
|
|
Value::String { val, .. } => Ok(NuGlob::DoNotExpand(val)),
|
|
Value::Glob {
|
|
val,
|
|
no_expand: quoted,
|
|
..
|
|
} => {
|
|
if quoted {
|
|
Ok(NuGlob::DoNotExpand(val))
|
|
} else {
|
|
Ok(NuGlob::Expand(val))
|
|
}
|
|
}
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::String
|
|
}
|
|
}
|
|
|
|
impl FromValue for Range {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Range { val, .. } => Ok(*val),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Range
|
|
}
|
|
}
|
|
|
|
impl FromValue for Record {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Record { val, .. } => Ok(val),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Blanket Nu Implementations
|
|
|
|
impl<T> FromValue for Spanned<T>
|
|
where
|
|
T: FromValue,
|
|
{
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
let span = v.span();
|
|
Ok(Spanned {
|
|
item: T::from_value(v)?,
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
T::expected_type()
|
|
}
|
|
}
|
|
|
|
// Foreign Types
|
|
|
|
impl FromValue for bytes::Bytes {
|
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
|
match v {
|
|
Value::Binary { val, .. } => Ok(val.into()),
|
|
v => Err(ShellError::CantConvert {
|
|
to_type: Self::expected_type().to_string(),
|
|
from_type: v.get_type().to_string(),
|
|
span: v.span(),
|
|
help: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn expected_type() -> Type {
|
|
Type::Binary
|
|
}
|
|
}
|
|
|
|
// Use generics with `fmt::Display` to allow passing different kinds of integer
|
|
fn int_too_small_error(int: impl fmt::Display, min: impl fmt::Display, span: Span) -> ShellError {
|
|
ShellError::GenericError {
|
|
error: "Integer too small".to_string(),
|
|
msg: format!("{int} is smaller than {min}"),
|
|
span: Some(span),
|
|
help: None,
|
|
inner: vec![],
|
|
}
|
|
}
|
|
|
|
fn int_too_large_error(int: impl fmt::Display, max: impl fmt::Display, span: Span) -> ShellError {
|
|
ShellError::GenericError {
|
|
error: "Integer too large".to_string(),
|
|
msg: format!("{int} is larger than {max}"),
|
|
span: Some(span),
|
|
help: None,
|
|
inner: vec![],
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{engine::Closure, FromValue, IntoValue, Record, Span, Type, Value};
|
|
use std::ops::Deref;
|
|
|
|
#[test]
|
|
fn expected_type_default_impl() {
|
|
assert_eq!(
|
|
Record::expected_type(),
|
|
Type::Custom("Record".to_string().into_boxed_str())
|
|
);
|
|
|
|
assert_eq!(
|
|
Closure::expected_type(),
|
|
Type::Custom("Closure".to_string().into_boxed_str())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn from_value_vec_u8() {
|
|
let vec: Vec<u8> = vec![1, 2, 3];
|
|
let span = Span::test_data();
|
|
let string = "Hello Vec<u8>!".to_string();
|
|
|
|
assert_eq!(
|
|
Vec::<u8>::from_value(vec.clone().into_value(span)).unwrap(),
|
|
vec.clone(),
|
|
"Vec<u8> roundtrip"
|
|
);
|
|
|
|
assert_eq!(
|
|
Vec::<u8>::from_value(Value::test_string(string.clone()))
|
|
.unwrap()
|
|
.deref(),
|
|
string.as_bytes(),
|
|
"Vec<u8> from String"
|
|
);
|
|
|
|
assert_eq!(
|
|
Vec::<u8>::from_value(Value::test_binary(vec.clone())).unwrap(),
|
|
vec,
|
|
"Vec<u8> from Binary"
|
|
);
|
|
|
|
assert!(Vec::<u8>::from_value(vec![u8::MIN as i32 - 1].into_value(span)).is_err());
|
|
assert!(Vec::<u8>::from_value(vec![u8::MAX as i32 + 1].into_value(span)).is_err());
|
|
}
|
|
}
|