nu_protocol/value/
from_value.rs

1use crate::{
2    ast::{CellPath, PathMember},
3    engine::Closure,
4    NuGlob, Range, Record, ShellError, Span, Spanned, Type, Value,
5};
6use chrono::{DateTime, FixedOffset};
7use std::{
8    any,
9    cmp::Ordering,
10    collections::{HashMap, VecDeque},
11    fmt,
12    path::PathBuf,
13    str::FromStr,
14};
15
16/// A trait for loading a value from a [`Value`].
17///
18/// # Derivable
19/// This trait can be used with `#[derive]`.
20///
21/// When derived on structs with named fields, it expects a [`Value::Record`] where each field of
22/// the struct maps to a corresponding field in the record.
23///
24/// - If `#[nu_value(rename = "...")]` is applied to a field, that name will be used as the key in
25///   the record.
26/// - If `#[nu_value(rename_all = "...")]` is applied on the container (struct) the key of the
27///   field will be case-converted accordingly.
28/// - If neither attribute is applied, the field name is used as is.
29///
30/// Supported case conversions include those provided by [`heck`], such as
31/// "snake_case", "kebab-case", "PascalCase", and others.
32/// Additionally, all values accepted by
33/// [`#[serde(rename_all = "...")]`](https://serde.rs/container-attrs.html#rename_all) are valid here.
34///
35/// For structs with unnamed fields, it expects a [`Value::List`], and the fields are populated in
36/// the order they appear in the list.
37/// Unit structs expect a [`Value::Nothing`], as they contain no data.
38/// Attempting to convert from a non-matching `Value` type will result in an error.
39///
40/// Only enums with no fields may derive this trait.
41/// The expected value representation will be the name of the variant as a [`Value::String`].
42///
43/// - If `#[nu_value(rename = "...")]` is applied to a variant, that name will be used.
44/// - If `#[nu_value(rename_all = "...")]` is applied on the enum container, the name of variant
45///   will be case-converted accordingly.
46/// - If neither attribute is applied, the variant name will default to
47///   ["snake_case"](heck::ToSnakeCase).
48///
49/// Additionally, you can use `#[nu_value(type_name = "...")]` in the derive macro to set a custom type name
50/// for `FromValue::expected_type`. This will result in a `Type::Custom` with the specified type name.
51/// This can be useful in situations where the default type name is not desired.
52///
53/// # Enum Example
54/// ```
55/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
56/// #
57/// # let span = Span::unknown();
58/// #
59/// #[derive(FromValue, Debug, PartialEq)]
60/// #[nu_value(rename_all = "COBOL-CASE", type_name = "birb")]
61/// enum Bird {
62///     MountainEagle,
63///     ForestOwl,
64///     #[nu_value(rename = "RIVER-QUACK")]
65///     RiverDuck,
66/// }
67///
68/// assert_eq!(
69///     Bird::from_value(Value::string("FOREST-OWL", span)).unwrap(),
70///     Bird::ForestOwl
71/// );
72///
73/// assert_eq!(
74///     Bird::from_value(Value::string("RIVER-QUACK", span)).unwrap(),
75///     Bird::RiverDuck
76/// );
77///
78/// assert_eq!(
79///     &Bird::expected_type().to_string(),
80///     "birb"
81/// );
82/// ```
83///
84/// # Struct Example
85/// ```
86/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
87/// #
88/// # let span = Span::unknown();
89/// #
90/// #[derive(FromValue, PartialEq, Eq, Debug)]
91/// #[nu_value(rename_all = "kebab-case")]
92/// struct Person {
93///     first_name: String,
94///     last_name: String,
95///     #[nu_value(rename = "age")]
96///     age_years: u32,
97/// }
98///
99/// let value = Value::record(record! {
100///     "first-name" => Value::string("John", span),
101///     "last-name" => Value::string("Doe", span),
102///     "age" => Value::int(42, span),
103/// }, span);
104///
105/// assert_eq!(
106///     Person::from_value(value).unwrap(),
107///     Person {
108///         first_name: "John".into(),
109///         last_name: "Doe".into(),
110///         age_years: 42,
111///     }
112/// );
113/// ```
114pub trait FromValue: Sized {
115    // TODO: instead of ShellError, maybe we could have a FromValueError that implements Into<ShellError>
116    /// Loads a value from a [`Value`].
117    ///
118    /// This method retrieves a value similarly to how strings are parsed using [`FromStr`].
119    /// The operation might fail if the `Value` contains unexpected types or structures.
120    fn from_value(v: Value) -> Result<Self, ShellError>;
121
122    /// Expected `Value` type.
123    ///
124    /// This is used to print out errors of what type of value is expected for conversion.
125    /// Even if not used in [`from_value`](FromValue::from_value) this should still be implemented
126    /// so that other implementations like `Option` or `Vec` can make use of it.
127    /// It is advised to call this method in `from_value` to ensure that expected type in the error
128    /// is consistent.
129    ///
130    /// Unlike the default implementation, derived implementations explicitly reveal the concrete
131    /// type, such as [`Type::Record`] or [`Type::List`], instead of an opaque type.
132    fn expected_type() -> Type {
133        Type::Custom(
134            any::type_name::<Self>()
135                .split(':')
136                .last()
137                .expect("str::split returns an iterator with at least one element")
138                .to_string()
139                .into_boxed_str(),
140        )
141    }
142}
143
144// Primitive Types
145
146impl<T, const N: usize> FromValue for [T; N]
147where
148    T: FromValue,
149{
150    fn from_value(v: Value) -> Result<Self, ShellError> {
151        let span = v.span();
152        let v_ty = v.get_type();
153        let vec = Vec::<T>::from_value(v)?;
154        vec.try_into()
155            .map_err(|err_vec: Vec<T>| ShellError::CantConvert {
156                to_type: Self::expected_type().to_string(),
157                from_type: v_ty.to_string(),
158                span,
159                help: Some(match err_vec.len().cmp(&N) {
160                    Ordering::Less => format!(
161                        "input list too short ({}), expected length of {N}, add missing values",
162                        err_vec.len()
163                    ),
164                    Ordering::Equal => {
165                        unreachable!("conversion would have worked if the length would be the same")
166                    }
167                    Ordering::Greater => format!(
168                        "input list too long ({}), expected length of {N}, remove trailing values",
169                        err_vec.len()
170                    ),
171                }),
172            })
173    }
174
175    fn expected_type() -> Type {
176        Type::Custom(format!("list<{};{N}>", T::expected_type()).into_boxed_str())
177    }
178}
179
180impl FromValue for bool {
181    fn from_value(v: Value) -> Result<Self, ShellError> {
182        match v {
183            Value::Bool { val, .. } => Ok(val),
184            v => Err(ShellError::CantConvert {
185                to_type: Self::expected_type().to_string(),
186                from_type: v.get_type().to_string(),
187                span: v.span(),
188                help: None,
189            }),
190        }
191    }
192
193    fn expected_type() -> Type {
194        Type::Bool
195    }
196}
197
198impl FromValue for char {
199    fn from_value(v: Value) -> Result<Self, ShellError> {
200        let span = v.span();
201        let v_ty = v.get_type();
202        match v {
203            Value::String { ref val, .. } => match char::from_str(val) {
204                Ok(c) => Ok(c),
205                Err(_) => Err(ShellError::CantConvert {
206                    to_type: Self::expected_type().to_string(),
207                    from_type: v_ty.to_string(),
208                    span,
209                    help: Some("make the string only one char long".to_string()),
210                }),
211            },
212            _ => Err(ShellError::CantConvert {
213                to_type: Self::expected_type().to_string(),
214                from_type: v_ty.to_string(),
215                span,
216                help: None,
217            }),
218        }
219    }
220
221    fn expected_type() -> Type {
222        Type::String
223    }
224}
225
226impl FromValue for f32 {
227    fn from_value(v: Value) -> Result<Self, ShellError> {
228        f64::from_value(v).map(|float| float as f32)
229    }
230}
231
232impl FromValue for f64 {
233    fn from_value(v: Value) -> Result<Self, ShellError> {
234        match v {
235            Value::Float { val, .. } => Ok(val),
236            Value::Int { val, .. } => Ok(val as f64),
237            v => Err(ShellError::CantConvert {
238                to_type: Self::expected_type().to_string(),
239                from_type: v.get_type().to_string(),
240                span: v.span(),
241                help: None,
242            }),
243        }
244    }
245
246    fn expected_type() -> Type {
247        Type::Float
248    }
249}
250
251impl FromValue for i64 {
252    fn from_value(v: Value) -> Result<Self, ShellError> {
253        match v {
254            Value::Int { val, .. } => Ok(val),
255            Value::Duration { val, .. } => Ok(val),
256            v => Err(ShellError::CantConvert {
257                to_type: Self::expected_type().to_string(),
258                from_type: v.get_type().to_string(),
259                span: v.span(),
260                help: None,
261            }),
262        }
263    }
264
265    fn expected_type() -> Type {
266        Type::Int
267    }
268}
269
270macro_rules! impl_from_value_for_int {
271    ($type:ty) => {
272        impl FromValue for $type {
273            fn from_value(v: Value) -> Result<Self, ShellError> {
274                let span = v.span();
275                let int = i64::from_value(v)?;
276                const MIN: i64 = <$type>::MIN as i64;
277                const MAX: i64 = <$type>::MAX as i64;
278                #[allow(overlapping_range_endpoints)] // calculating MIN-1 is not possible for i64::MIN
279                #[allow(unreachable_patterns)] // isize might max out i64 number range
280                <$type>::try_from(int).map_err(|_| match int {
281                    MIN..=MAX => unreachable!(
282                        "int should be within the valid range for {}",
283                        stringify!($type)
284                    ),
285                    i64::MIN..=MIN => int_too_small_error(int, <$type>::MIN, span),
286                    MAX..=i64::MAX => int_too_large_error(int, <$type>::MAX, span),
287                })
288            }
289
290            fn expected_type() -> Type {
291                i64::expected_type()
292            }
293        }
294    };
295}
296
297impl_from_value_for_int!(i8);
298impl_from_value_for_int!(i16);
299impl_from_value_for_int!(i32);
300impl_from_value_for_int!(isize);
301
302macro_rules! impl_from_value_for_uint {
303    ($type:ty, $max:expr) => {
304        impl FromValue for $type {
305            fn from_value(v: Value) -> Result<Self, ShellError> {
306                let span = v.span();
307                const MAX: i64 = $max;
308                match v {
309                    Value::Int { val, .. } | Value::Duration { val, .. } => {
310                        match val {
311                            i64::MIN..=-1 => Err(ShellError::NeedsPositiveValue { span }),
312                            0..=MAX => Ok(val as $type),
313                            #[allow(unreachable_patterns)] // u64 will max out the i64 number range
314                            n => Err(ShellError::GenericError {
315                                error: "Integer too large".to_string(),
316                                msg: format!("{n} is larger than {MAX}"),
317                                span: Some(span),
318                                help: None,
319                                inner: vec![],
320                            }),
321                        }
322                    }
323                    v => Err(ShellError::CantConvert {
324                        to_type: Self::expected_type().to_string(),
325                        from_type: v.get_type().to_string(),
326                        span: v.span(),
327                        help: None,
328                    }),
329                }
330            }
331
332            fn expected_type() -> Type {
333                Type::Custom("non-negative int".to_string().into_boxed_str())
334            }
335        }
336    };
337}
338
339// Sadly we cannot implement FromValue for u8 without losing the impl of Vec<u8>,
340// Rust would find two possible implementations then, Vec<u8> and Vec<T = u8>,
341// and wouldn't compile.
342// The blanket implementation for Vec<T> is probably more useful than
343// implementing FromValue for u8.
344
345impl_from_value_for_uint!(u16, u16::MAX as i64);
346impl_from_value_for_uint!(u32, u32::MAX as i64);
347impl_from_value_for_uint!(u64, i64::MAX); // u64::Max would be -1 as i64
348#[cfg(target_pointer_width = "64")]
349impl_from_value_for_uint!(usize, i64::MAX);
350#[cfg(target_pointer_width = "32")]
351impl_from_value_for_uint!(usize, usize::MAX as i64);
352
353impl FromValue for () {
354    fn from_value(v: Value) -> Result<Self, ShellError> {
355        match v {
356            Value::Nothing { .. } => Ok(()),
357            v => Err(ShellError::CantConvert {
358                to_type: Self::expected_type().to_string(),
359                from_type: v.get_type().to_string(),
360                span: v.span(),
361                help: None,
362            }),
363        }
364    }
365
366    fn expected_type() -> Type {
367        Type::Nothing
368    }
369}
370
371macro_rules! tuple_from_value {
372    ($template:literal, $($t:ident:$n:tt),+) => {
373        impl<$($t),+> FromValue for ($($t,)+) where $($t: FromValue,)+ {
374            fn from_value(v: Value) -> Result<Self, ShellError> {
375                let span = v.span();
376                match v {
377                    Value::List { vals, .. } => {
378                        let mut deque = VecDeque::from(vals);
379
380                        Ok(($(
381                            {
382                                let v = deque.pop_front().ok_or_else(|| ShellError::CantFindColumn {
383                                    col_name: $n.to_string(),
384                                    span: None,
385                                    src_span: span
386                                })?;
387                                $t::from_value(v)?
388                            },
389                        )*))
390                    },
391                    v => Err(ShellError::CantConvert {
392                        to_type: Self::expected_type().to_string(),
393                        from_type: v.get_type().to_string(),
394                        span: v.span(),
395                        help: None,
396                    }),
397                }
398            }
399
400            fn expected_type() -> Type {
401                Type::Custom(
402                    format!(
403                        $template,
404                        $($t::expected_type()),*
405                    )
406                    .into_boxed_str(),
407                )
408            }
409        }
410    };
411}
412
413// Tuples in std are implemented for up to 12 elements, so we do it here too.
414tuple_from_value!("[{}]", T0:0);
415tuple_from_value!("[{}, {}]", T0:0, T1:1);
416tuple_from_value!("[{}, {}, {}]", T0:0, T1:1, T2:2);
417tuple_from_value!("[{}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3);
418tuple_from_value!("[{}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4);
419tuple_from_value!("[{}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5);
420tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6);
421tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7);
422tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8);
423tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9);
424tuple_from_value!("[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]", T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10);
425tuple_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);
426
427// Other std Types
428
429impl FromValue for PathBuf {
430    fn from_value(v: Value) -> Result<Self, ShellError> {
431        match v {
432            Value::String { val, .. } => Ok(val.into()),
433            v => Err(ShellError::CantConvert {
434                to_type: Self::expected_type().to_string(),
435                from_type: v.get_type().to_string(),
436                span: v.span(),
437                help: None,
438            }),
439        }
440    }
441
442    fn expected_type() -> Type {
443        Type::String
444    }
445}
446
447impl FromValue for String {
448    fn from_value(v: Value) -> Result<Self, ShellError> {
449        // FIXME: we may want to fail a little nicer here
450        match v {
451            Value::CellPath { val, .. } => Ok(val.to_string()),
452            Value::String { val, .. } => Ok(val),
453            v => Err(ShellError::CantConvert {
454                to_type: Self::expected_type().to_string(),
455                from_type: v.get_type().to_string(),
456                span: v.span(),
457                help: None,
458            }),
459        }
460    }
461
462    fn expected_type() -> Type {
463        Type::String
464    }
465}
466
467// This impl is different from Vec<T> as it allows reading from Value::Binary and Value::String too.
468// This also denies implementing FromValue for u8 as it would be in conflict with the Vec<T> impl.
469impl FromValue for Vec<u8> {
470    fn from_value(v: Value) -> Result<Self, ShellError> {
471        match v {
472            Value::Binary { val, .. } => Ok(val),
473            Value::String { val, .. } => Ok(val.into_bytes()),
474            Value::List { vals, .. } => {
475                const U8MIN: i64 = u8::MIN as i64;
476                const U8MAX: i64 = u8::MAX as i64;
477                let mut this = Vec::with_capacity(vals.len());
478                for val in vals {
479                    let span = val.span();
480                    let int = i64::from_value(val)?;
481                    // calculating -1 on these ranges would be less readable
482                    #[allow(overlapping_range_endpoints)]
483                    #[allow(clippy::match_overlapping_arm)]
484                    match int {
485                        U8MIN..=U8MAX => this.push(int as u8),
486                        i64::MIN..=U8MIN => return Err(int_too_small_error(int, U8MIN, span)),
487                        U8MAX..=i64::MAX => return Err(int_too_large_error(int, U8MAX, span)),
488                    };
489                }
490                Ok(this)
491            }
492            v => Err(ShellError::CantConvert {
493                to_type: Self::expected_type().to_string(),
494                from_type: v.get_type().to_string(),
495                span: v.span(),
496                help: None,
497            }),
498        }
499    }
500
501    fn expected_type() -> Type {
502        Type::Binary
503    }
504}
505
506// Blanket std Implementations
507
508impl<T> FromValue for Option<T>
509where
510    T: FromValue,
511{
512    fn from_value(v: Value) -> Result<Self, ShellError> {
513        match v {
514            Value::Nothing { .. } => Ok(None),
515            v => T::from_value(v).map(Option::Some),
516        }
517    }
518
519    fn expected_type() -> Type {
520        T::expected_type()
521    }
522}
523
524impl<V> FromValue for HashMap<String, V>
525where
526    V: FromValue,
527{
528    fn from_value(v: Value) -> Result<Self, ShellError> {
529        let record = v.into_record()?;
530        let items: Result<Vec<(String, V)>, ShellError> = record
531            .into_iter()
532            .map(|(k, v)| Ok((k, V::from_value(v)?)))
533            .collect();
534        Ok(HashMap::from_iter(items?))
535    }
536
537    fn expected_type() -> Type {
538        Type::Record(vec![].into_boxed_slice())
539    }
540}
541
542impl<T> FromValue for Vec<T>
543where
544    T: FromValue,
545{
546    fn from_value(v: Value) -> Result<Self, ShellError> {
547        match v {
548            Value::List { vals, .. } => vals
549                .into_iter()
550                .map(|v| T::from_value(v))
551                .collect::<Result<Vec<T>, ShellError>>(),
552            v => Err(ShellError::CantConvert {
553                to_type: Self::expected_type().to_string(),
554                from_type: v.get_type().to_string(),
555                span: v.span(),
556                help: None,
557            }),
558        }
559    }
560
561    fn expected_type() -> Type {
562        Type::List(Box::new(T::expected_type()))
563    }
564}
565
566// Nu Types
567
568impl FromValue for Value {
569    fn from_value(v: Value) -> Result<Self, ShellError> {
570        Ok(v)
571    }
572
573    fn expected_type() -> Type {
574        Type::Any
575    }
576}
577
578impl FromValue for CellPath {
579    fn from_value(v: Value) -> Result<Self, ShellError> {
580        let span = v.span();
581        match v {
582            Value::CellPath { val, .. } => Ok(val),
583            Value::String { val, .. } => Ok(CellPath {
584                members: vec![PathMember::String {
585                    val,
586                    span,
587                    optional: false,
588                }],
589            }),
590            Value::Int { val, .. } => {
591                if val.is_negative() {
592                    Err(ShellError::NeedsPositiveValue { span })
593                } else {
594                    Ok(CellPath {
595                        members: vec![PathMember::Int {
596                            val: val as usize,
597                            span,
598                            optional: false,
599                        }],
600                    })
601                }
602            }
603            x => Err(ShellError::CantConvert {
604                to_type: Self::expected_type().to_string(),
605                from_type: x.get_type().to_string(),
606                span,
607                help: None,
608            }),
609        }
610    }
611
612    fn expected_type() -> Type {
613        Type::CellPath
614    }
615}
616
617impl FromValue for Closure {
618    fn from_value(v: Value) -> Result<Self, ShellError> {
619        match v {
620            Value::Closure { val, .. } => Ok(*val),
621            v => Err(ShellError::CantConvert {
622                to_type: Self::expected_type().to_string(),
623                from_type: v.get_type().to_string(),
624                span: v.span(),
625                help: None,
626            }),
627        }
628    }
629}
630
631impl FromValue for DateTime<FixedOffset> {
632    fn from_value(v: Value) -> Result<Self, ShellError> {
633        match v {
634            Value::Date { val, .. } => Ok(val),
635            v => Err(ShellError::CantConvert {
636                to_type: Self::expected_type().to_string(),
637                from_type: v.get_type().to_string(),
638                span: v.span(),
639                help: None,
640            }),
641        }
642    }
643
644    fn expected_type() -> Type {
645        Type::Date
646    }
647}
648
649impl FromValue for NuGlob {
650    fn from_value(v: Value) -> Result<Self, ShellError> {
651        // FIXME: we may want to fail a little nicer here
652        match v {
653            Value::CellPath { val, .. } => Ok(NuGlob::Expand(val.to_string())),
654            Value::String { val, .. } => Ok(NuGlob::DoNotExpand(val)),
655            Value::Glob {
656                val,
657                no_expand: quoted,
658                ..
659            } => {
660                if quoted {
661                    Ok(NuGlob::DoNotExpand(val))
662                } else {
663                    Ok(NuGlob::Expand(val))
664                }
665            }
666            v => Err(ShellError::CantConvert {
667                to_type: Self::expected_type().to_string(),
668                from_type: v.get_type().to_string(),
669                span: v.span(),
670                help: None,
671            }),
672        }
673    }
674
675    fn expected_type() -> Type {
676        Type::String
677    }
678}
679
680impl FromValue for Range {
681    fn from_value(v: Value) -> Result<Self, ShellError> {
682        match v {
683            Value::Range { val, .. } => Ok(*val),
684            v => Err(ShellError::CantConvert {
685                to_type: Self::expected_type().to_string(),
686                from_type: v.get_type().to_string(),
687                span: v.span(),
688                help: None,
689            }),
690        }
691    }
692
693    fn expected_type() -> Type {
694        Type::Range
695    }
696}
697
698impl FromValue for Record {
699    fn from_value(v: Value) -> Result<Self, ShellError> {
700        match v {
701            Value::Record { val, .. } => Ok(val.into_owned()),
702            v => Err(ShellError::CantConvert {
703                to_type: Self::expected_type().to_string(),
704                from_type: v.get_type().to_string(),
705                span: v.span(),
706                help: None,
707            }),
708        }
709    }
710}
711
712// Blanket Nu Implementations
713
714impl<T> FromValue for Spanned<T>
715where
716    T: FromValue,
717{
718    fn from_value(v: Value) -> Result<Self, ShellError> {
719        let span = v.span();
720        Ok(Spanned {
721            item: T::from_value(v)?,
722            span,
723        })
724    }
725
726    fn expected_type() -> Type {
727        T::expected_type()
728    }
729}
730
731// Foreign Types
732
733impl FromValue for bytes::Bytes {
734    fn from_value(v: Value) -> Result<Self, ShellError> {
735        match v {
736            Value::Binary { val, .. } => Ok(val.into()),
737            v => Err(ShellError::CantConvert {
738                to_type: Self::expected_type().to_string(),
739                from_type: v.get_type().to_string(),
740                span: v.span(),
741                help: None,
742            }),
743        }
744    }
745
746    fn expected_type() -> Type {
747        Type::Binary
748    }
749}
750
751// Use generics with `fmt::Display` to allow passing different kinds of integer
752fn int_too_small_error(int: impl fmt::Display, min: impl fmt::Display, span: Span) -> ShellError {
753    ShellError::GenericError {
754        error: "Integer too small".to_string(),
755        msg: format!("{int} is smaller than {min}"),
756        span: Some(span),
757        help: None,
758        inner: vec![],
759    }
760}
761
762fn int_too_large_error(int: impl fmt::Display, max: impl fmt::Display, span: Span) -> ShellError {
763    ShellError::GenericError {
764        error: "Integer too large".to_string(),
765        msg: format!("{int} is larger than {max}"),
766        span: Some(span),
767        help: None,
768        inner: vec![],
769    }
770}
771
772#[cfg(test)]
773mod tests {
774    use crate::{engine::Closure, FromValue, IntoValue, Record, Span, Type, Value};
775    use std::ops::Deref;
776
777    #[test]
778    fn expected_type_default_impl() {
779        assert_eq!(
780            Record::expected_type(),
781            Type::Custom("Record".to_string().into_boxed_str())
782        );
783
784        assert_eq!(
785            Closure::expected_type(),
786            Type::Custom("Closure".to_string().into_boxed_str())
787        );
788    }
789
790    #[test]
791    fn from_value_vec_u8() {
792        let vec: Vec<u8> = vec![1, 2, 3];
793        let span = Span::test_data();
794        let string = "Hello Vec<u8>!".to_string();
795
796        assert_eq!(
797            Vec::<u8>::from_value(vec.clone().into_value(span)).unwrap(),
798            vec.clone(),
799            "Vec<u8> roundtrip"
800        );
801
802        assert_eq!(
803            Vec::<u8>::from_value(Value::test_string(string.clone()))
804                .unwrap()
805                .deref(),
806            string.as_bytes(),
807            "Vec<u8> from String"
808        );
809
810        assert_eq!(
811            Vec::<u8>::from_value(Value::test_binary(vec.clone())).unwrap(),
812            vec,
813            "Vec<u8> from Binary"
814        );
815
816        assert!(Vec::<u8>::from_value(vec![u8::MIN as i32 - 1].into_value(span)).is_err());
817        assert!(Vec::<u8>::from_value(vec![u8::MAX as i32 + 1].into_value(span)).is_err());
818    }
819}