nushell/crates/nu-protocol/src/value/into_value.rs
Piepmatz ebe42241fe
Add #[nu_value(rename = "...")] as helper attribute on members for derive macros (#13761)
# 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.
2024-09-04 11:27:21 +02:00

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))
}
}