mirror of
https://github.com/nushell/nushell.git
synced 2025-05-18 17:00:46 +02:00
# Description This PR allows the helper attribute `nu_value(rename = "...")` to be used on struct fields and enum variants. This allows renaming keys and variants just like [`#[serde(rename = "name")]`](https://serde.rs/field-attrs.html#rename). This has no singular variants for `IntoValue` or `FromValue`, both need to use the same (but I think this shouldn't be an issue for now). # User-Facing Changes Users of the derive macros `IntoValue` and `FromValue` may now use `#[nu_value(rename = "...")]` to rename single fields, but no already existing code will break.
300 lines
8.9 KiB
Rust
300 lines
8.9 KiB
Rust
use crate::{ast::CellPath, engine::Closure, Range, Record, ShellError, Span, Value};
|
|
use chrono::{DateTime, FixedOffset};
|
|
use std::{borrow::Borrow, collections::HashMap};
|
|
|
|
/// A trait for converting a value into a [`Value`].
|
|
///
|
|
/// This conversion is infallible, for fallible conversions use [`TryIntoValue`].
|
|
///
|
|
/// # Derivable
|
|
/// This trait can be used with `#[derive]`.
|
|
/// When derived on structs with named fields, the resulting value representation will use
|
|
/// [`Value::Record`], where each field of the record corresponds to a field of the struct.
|
|
///
|
|
/// By default, field names will be used as-is unless specified otherwise:
|
|
/// - If `#[nu_value(rename = "...")]` is applied to a specific field, that name is used.
|
|
/// - If `#[nu_value(rename_all = "...")]` is applied to the struct, field names will be
|
|
/// case-converted accordingly.
|
|
/// - If neither attribute is used, the original field name will be retained.
|
|
///
|
|
/// For structs with unnamed fields, the value representation will be [`Value::List`], with all
|
|
/// fields inserted into a list.
|
|
/// Unit structs will be represented as [`Value::Nothing`] since they contain no data.
|
|
///
|
|
/// For enums, the resulting value representation depends on the variant name:
|
|
/// - If `#[nu_value(rename = "...")]` is applied to a specific variant, that name is used.
|
|
/// - If `#[nu_value(rename_all = "...")]` is applied to the enum, variant names will be
|
|
/// case-converted accordingly.
|
|
/// - If neither attribute is used, variant names will default to snake_case.
|
|
///
|
|
/// Only enums with no fields may derive this trait.
|
|
/// The resulting value will be the name of the variant as a [`Value::String`].
|
|
///
|
|
/// All case options from [`heck`] are supported, as well as the values allowed by
|
|
/// [`#[serde(rename_all)]`](https://serde.rs/container-attrs.html#rename_all).
|
|
///
|
|
/// # Enum Example
|
|
/// ```
|
|
/// # use nu_protocol::{IntoValue, Value, Span, record};
|
|
/// #
|
|
/// # let span = Span::unknown();
|
|
/// #
|
|
/// #[derive(IntoValue)]
|
|
/// #[nu_value(rename_all = "COBOL-CASE")]
|
|
/// enum Bird {
|
|
/// MountainEagle,
|
|
/// ForestOwl,
|
|
/// #[nu_value(rename = "RIVER-QUACK")]
|
|
/// RiverDuck,
|
|
/// }
|
|
///
|
|
/// assert_eq!(
|
|
/// Bird::ForestOwl.into_value(span),
|
|
/// Value::string("FOREST-OWL", span)
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// Bird::RiverDuck.into_value(span),
|
|
/// Value::string("RIVER-QUACK", span)
|
|
/// );
|
|
/// ```
|
|
///
|
|
/// # Struct Example
|
|
/// ```
|
|
/// # use nu_protocol::{IntoValue, Value, Span, record};
|
|
/// #
|
|
/// # let span = Span::unknown();
|
|
/// #
|
|
/// #[derive(IntoValue)]
|
|
/// #[nu_value(rename_all = "kebab-case")]
|
|
/// struct Person {
|
|
/// first_name: String,
|
|
/// last_name: String,
|
|
/// #[nu_value(rename = "age")]
|
|
/// age_years: u32,
|
|
/// }
|
|
///
|
|
/// assert_eq!(
|
|
/// Person {
|
|
/// first_name: "John".into(),
|
|
/// last_name: "Doe".into(),
|
|
/// age_years: 42,
|
|
/// }.into_value(span),
|
|
/// Value::record(record! {
|
|
/// "first-name" => Value::string("John", span),
|
|
/// "last-name" => Value::string("Doe", span),
|
|
/// "age" => Value::int(42, span),
|
|
/// }, span)
|
|
/// );
|
|
/// ```
|
|
pub trait IntoValue: Sized {
|
|
/// Converts the given value to a [`Value`].
|
|
fn into_value(self, span: Span) -> Value;
|
|
}
|
|
|
|
// Primitive Types
|
|
|
|
impl<T, const N: usize> IntoValue for [T; N]
|
|
where
|
|
T: IntoValue,
|
|
{
|
|
fn into_value(self, span: Span) -> Value {
|
|
Vec::from(self).into_value(span)
|
|
}
|
|
}
|
|
|
|
macro_rules! primitive_into_value {
|
|
($type:ty, $method:ident) => {
|
|
primitive_into_value!($type => $type, $method);
|
|
};
|
|
|
|
($type:ty => $as_type:ty, $method:ident) => {
|
|
impl IntoValue for $type {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::$method(<$as_type>::from(self), span)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
primitive_into_value!(bool, bool);
|
|
primitive_into_value!(char, string);
|
|
primitive_into_value!(f32 => f64, float);
|
|
primitive_into_value!(f64, float);
|
|
primitive_into_value!(i8 => i64, int);
|
|
primitive_into_value!(i16 => i64, int);
|
|
primitive_into_value!(i32 => i64, int);
|
|
primitive_into_value!(i64, int);
|
|
primitive_into_value!(u8 => i64, int);
|
|
primitive_into_value!(u16 => i64, int);
|
|
primitive_into_value!(u32 => i64, int);
|
|
// u64 and usize may be truncated as Value only supports i64.
|
|
|
|
impl IntoValue for isize {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::int(self as i64, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for () {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::nothing(span)
|
|
}
|
|
}
|
|
|
|
macro_rules! tuple_into_value {
|
|
($($t:ident:$n:tt),+) => {
|
|
impl<$($t),+> IntoValue for ($($t,)+) where $($t: IntoValue,)+ {
|
|
fn into_value(self, span: Span) -> Value {
|
|
let vals = vec![$(self.$n.into_value(span)),+];
|
|
Value::list(vals, span)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tuples in std are implemented for up to 12 elements, so we do it here too.
|
|
tuple_into_value!(T0:0);
|
|
tuple_into_value!(T0:0, T1:1);
|
|
tuple_into_value!(T0:0, T1:1, T2:2);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9);
|
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10);
|
|
tuple_into_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 IntoValue for String {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::string(self, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for &str {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::string(self, span)
|
|
}
|
|
}
|
|
|
|
impl<T> IntoValue for Vec<T>
|
|
where
|
|
T: IntoValue,
|
|
{
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::list(self.into_iter().map(|v| v.into_value(span)).collect(), span)
|
|
}
|
|
}
|
|
|
|
impl<T> IntoValue for Option<T>
|
|
where
|
|
T: IntoValue,
|
|
{
|
|
fn into_value(self, span: Span) -> Value {
|
|
match self {
|
|
Some(v) => v.into_value(span),
|
|
None => Value::nothing(span),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<K, V> IntoValue for HashMap<K, V>
|
|
where
|
|
K: Borrow<str> + Into<String>,
|
|
V: IntoValue,
|
|
{
|
|
fn into_value(self, span: Span) -> Value {
|
|
// The `Borrow<str>` constraint is to ensure uniqueness, as implementations of `Borrow`
|
|
// must uphold by certain properties (e.g., `(x == y) == (x.borrow() == y.borrow())`.
|
|
//
|
|
// The `Into<String>` constraint is necessary for us to convert the key into a `String`.
|
|
// Most types that implement `Borrow<str>` also implement `Into<String>`.
|
|
// Implementations of `Into` must also be lossless and value-preserving conversions.
|
|
// So, when combined with the `Borrow` constraint, this means that the converted
|
|
// `String` keys should be unique.
|
|
self.into_iter()
|
|
.map(|(k, v)| (k.into(), v.into_value(span)))
|
|
.collect::<Record>()
|
|
.into_value(span)
|
|
}
|
|
}
|
|
|
|
// Nu Types
|
|
|
|
impl IntoValue for Range {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::range(self, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for Record {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::record(self, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for Closure {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::closure(self, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for ShellError {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::error(self, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for CellPath {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::cell_path(self, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for Value {
|
|
fn into_value(self, span: Span) -> Value {
|
|
self.with_span(span)
|
|
}
|
|
}
|
|
|
|
// Foreign Types
|
|
|
|
impl IntoValue for DateTime<FixedOffset> {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::date(self, span)
|
|
}
|
|
}
|
|
|
|
impl IntoValue for bytes::Bytes {
|
|
fn into_value(self, span: Span) -> Value {
|
|
Value::binary(self.to_vec(), span)
|
|
}
|
|
}
|
|
|
|
// TODO: use this type for all the `into_value` methods that types implement but return a Result
|
|
/// A trait for trying to convert a value into a `Value`.
|
|
///
|
|
/// Types like streams may fail while collecting the `Value`,
|
|
/// for these types it is useful to implement a fallible variant.
|
|
///
|
|
/// This conversion is fallible, for infallible conversions use [`IntoValue`].
|
|
/// All types that implement `IntoValue` will automatically implement this trait.
|
|
pub trait TryIntoValue: Sized {
|
|
// TODO: instead of ShellError, maybe we could have a IntoValueError that implements Into<ShellError>
|
|
/// Tries to convert the given value into a `Value`.
|
|
fn try_into_value(self, span: Span) -> Result<Value, ShellError>;
|
|
}
|
|
|
|
impl<T> TryIntoValue for T
|
|
where
|
|
T: IntoValue,
|
|
{
|
|
fn try_into_value(self, span: Span) -> Result<Value, ShellError> {
|
|
Ok(self.into_value(span))
|
|
}
|
|
}
|