diff --git a/Cargo.lock b/Cargo.lock index 0f48bb3fdf..911030bcfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3229,6 +3229,7 @@ dependencies = [ "byte-unit", "chrono", "chrono-humanize", + "convert_case", "fancy-regex", "indexmap", "lru", diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 99089edddf..849a968eb8 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -22,6 +22,7 @@ brotli = { workspace = true, optional = true } byte-unit = { version = "5.1", features = [ "serde" ] } chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false } chrono-humanize = { workspace = true } +convert_case = { workspace = true } fancy-regex = { workspace = true } indexmap = { workspace = true } lru = { workspace = true } diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index 2a384d33c4..d96938adfd 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -16,37 +16,53 @@ use std::{ /// /// # 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. -/// For structs with unnamed fields, it expects a [`Value::List`], and the -/// fields are populated in the order they appear in the list. +/// 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. +/// 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. -// TODO: explain derive for enums +/// 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`]. +/// By default, variant names will be expected in ["snake_case"](convert_case::Case::Snake). +/// You can customize the case conversion using `#[nu_value(rename_all = "kebab-case")]` on the enum. +/// All deterministic case conversions provided by [`convert_case::Case`] are supported by +/// specifying the case name followed by "case". +/// +/// ``` +/// # use nu_protocol::{FromValue, Value, ShellError}; +/// #[derive(FromValue, Debug, PartialEq)] +/// #[nu_value(rename_all = "COBOL-CASE")] +/// enum Bird { +/// MountainEagle, +/// ForestOwl, +/// RiverDuck, +/// } +/// +/// assert_eq!( +/// Bird::from_value(Value::test_string("RIVER-DUCK")).unwrap(), +/// Bird::RiverDuck +/// ); +/// ``` pub trait FromValue: Sized { // TODO: instead of ShellError, maybe we could have a FromValueError that implements Into /// 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. + /// 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; /// 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. + /// 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. + /// 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::() diff --git a/crates/nu-protocol/src/value/into_value.rs b/crates/nu-protocol/src/value/into_value.rs index 118d3f9627..5b52a0b77c 100644 --- a/crates/nu-protocol/src/value/into_value.rs +++ b/crates/nu-protocol/src/value/into_value.rs @@ -8,14 +8,34 @@ use crate::{Record, ShellError, Span, Value}; /// /// # 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. -/// 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. -// TODO: explain derive for enums +/// 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. +/// 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. +/// +/// Only enums with no fields may derive this trait. +/// The resulting value representation will be the name of the variant as a [`Value::String`]. +/// By default, variant names will be converted to ["snake_case"](convert_case::Case::Snake). +/// You can customize the case conversion using `#[nu_value(rename_all = "kebab-case")]` on the enum. +/// All deterministic case conversions provided by [`convert_case::Case`] are supported by +/// specifying the case name followed by "case". +/// +/// ``` +/// # use nu_protocol::{IntoValue, Value}; +/// #[derive(IntoValue)] +/// #[nu_value(rename_all = "COBOL-CASE")] +/// enum Bird { +/// MountainEagle, +/// ForestOwl, +/// RiverDuck, +/// } +/// +/// assert_eq!( +/// Bird::RiverDuck.into_value_unknown(), +/// Value::test_string("RIVER-DUCK") +/// ); +/// ``` pub trait IntoValue: Sized { /// Converts the given value to a [`Value`]. fn into_value(self, span: Span) -> Value;