Crate nix_compat_derive

Source
Expand description

§Using derive

  1. Overview
  2. Attributes
    1. Container attributes
      1. #[nix(from_str)]
      2. #[nix(from = "FromType")]
      3. #[nix(try_from = "FromType")]
      4. #[nix(into = "IntoType")]
      5. #[nix(try_into = "IntoType")]
      6. #[nix(display)]
      7. #[nix(display = "path")]
      8. #[nix(crate = "...")]
    2. Variant attributes
      1. #[nix(version = "range")]
    3. Field attributes
      1. #[nix(version = "range")]
      2. #[nix(default)]
      3. #[nix(default = "path")]

§Overview

This crate contains derive macros and function-like macros for implementing NixDeserialize and NixSerialize with less boilerplate.

§Examples

#[derive(NixDeserialize, NixSerialize)]
struct Unnamed(u64, String);
#[derive(NixDeserialize, NixSerialize)]
struct Fields {
    number: u64,
    message: String,
};
#[derive(NixDeserialize, NixSerialize)]
struct Ignored;

§Attributes

To customize the derived trait implementations you can add attributes to containers, fields and variants.

#[derive(NixDeserialize)]
#[nix(crate="nix_compat")] // <-- This is a container attribute
struct Fields {
    number: u64,
    #[nix(version="..20")] // <-- This is a field attribute
    message: String,
};

#[derive(NixDeserialize)]
#[nix(crate="nix_compat")] // <-- This is also a container attribute
enum E {
    #[nix(version="..10")] // <-- This is a variant attribute
    A(u64),
    #[nix(version="10..")] // <-- This is also a variant attribute
    B(String),
}

§Container attributes

§#[nix(from_str)]

When from_str is specified the fields are all ignored and instead a String is first deserialized and then FromStr::from_str is used to convert this String to the container type.

This means that the container must implement FromStr and the error returned from the from_str must implement Display.

§Example
#[derive(NixDeserialize)]
#[nix(from_str)]
struct MyString(String);
impl std::str::FromStr for MyString {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s != "bad string" {
            Ok(MyString(s.to_string()))
        } else {
            Err("Got a bad string".to_string())
        }
    }
}
§#[nix(from = "FromType")]

When from is specified the fields are all ignored and instead a value of FromType is first deserialized and then From::from is used to convert from this value to the container type.

This means that the container must implement From<FromType> and FromType must implement NixDeserialize.

§Example
#[derive(NixDeserialize)]
#[nix(from="usize")]
struct MyValue(usize);
impl From<usize> for MyValue {
    fn from(val: usize) -> Self {
        MyValue(val)
    }
}
§#[nix(try_from = "FromType")]

With try_from a value of FromType is first deserialized and then TryFrom::try_from is used to convert from this value to the container type.

This means that the container must implement TryFrom<FromType> and FromType must implement NixDeserialize. The error returned from try_from also needs to implement Display.

§Example
#[derive(NixDeserialize)]
#[nix(try_from="usize")]
struct WrongAnswer(usize);
impl TryFrom<usize> for WrongAnswer {
    type Error = String;
    fn try_from(val: usize) -> Result<Self, Self::Error> {
        if val != 42 {
            Ok(WrongAnswer(val))
        } else {
            Err("Got the answer to life the universe and everything".to_string())
        }
    }
}
§#[nix(into = "IntoType")]

When into is specified the fields are all ignored and instead the container type is converted to IntoType using Into::into and IntoType is then serialized. Before converting Clone::clone is called.

This means that the container must implement Into<IntoType> and Clone and IntoType must implement NixSerialize.

§Example
#[derive(Clone, NixSerialize)]
#[nix(into="usize")]
struct MyValue(usize);
impl From<MyValue> for usize {
    fn from(val: MyValue) -> Self {
        val.0
    }
}
§#[nix(try_into = "IntoType")]

When try_into is specified the fields are all ignored and instead the container type is converted to IntoType using TryInto::try_into and IntoType is then serialized. Before converting Clone::clone is called.

This means that the container must implement TryInto<IntoType> and Clone and IntoType must implement NixSerialize. The error returned from try_into also needs to implement Display.

§Example
#[derive(Clone, NixSerialize)]
#[nix(try_into="usize")]
struct WrongAnswer(usize);
impl TryFrom<WrongAnswer> for usize {
    type Error = String;
    fn try_from(val: WrongAnswer) -> Result<Self, Self::Error> {
        if val.0 != 42 {
            Ok(val.0)
        } else {
            Err("Got the answer to life the universe and everything".to_string())
        }
    }
}
§#[nix(display)]

When display is specified the fields are all ignored and instead the container must implement Display and NixWrite::write_display is used to write the container.

§Example
#[derive(NixSerialize)]
#[nix(display)]
struct WrongAnswer(usize);
impl Display for WrongAnswer {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "Wrong Answer = {}", self.0)
    }
}
§#[nix(display = "path")]

When display is specified the fields are all ignored and instead the container the specified path must point to a function that is callable as fn(&T) -> impl Display. The result from this call is then written with NixWrite::write_display. For example default = "my_value" would call my_value(&self) and display = "AType::empty" would call AType::empty(&self).

§Example
#[derive(NixSerialize)]
#[nix(display = "format_it")]
struct WrongAnswer(usize);
struct WrongDisplay<'a>(&'a WrongAnswer);
impl<'a> Display for WrongDisplay<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "Wrong Answer = {}", self.0.0)
    }
}

fn format_it(value: &WrongAnswer) -> impl Display + '_ {
    WrongDisplay(value)
}
§#[nix(crate = "...")]

Specify the path to the nix-compat crate instance to use when referring to the API in the generated code. This is usually not needed.

§Variant attributes

§#[nix(version = "range")]

Specifies the protocol version range where this variant is used. When deriving an enum the version attribute is used to select which variant of the enum to deserialize. The range is for minor version and the version ranges of all variants combined must cover all versions without any overlap or the first variant that matches is selected.

§Example
#[derive(NixDeserialize)]
enum Testing {
    #[nix(version="..=18")]
    OldVersion(u64),
    #[nix(version="19..")]
    NewVersion(String),
}

§Field attributes

§#[nix(version = "range")]

Specifies the protocol version range where this field is included. The range is for minor version. For example version = "..20" includes the field in protocol versions 1.0 to 1.19 and skips it in version 1.20 and above.

§Example
#[derive(NixDeserialize)]
struct Field {
    number: u64,
    #[nix(version="..20")]
    messsage: String,
}
§#[nix(default)]

When a field is skipped because the active protocol version falls outside the range specified in #[nix(version = "range")] this attribute indicates that Default::default() should be used to get a value for the field. This is also the default when you only specify #[nix(version = "range")].

§Example
#[derive(NixDeserialize)]
struct Field {
    number: u64,
    #[nix(version="..20", default)]
    messsage: String,
}
§#[nix(default = "path")]

When a field is skipped because the active protocol version falls outside the range specified in #[nix(version = "range")] this attribute indicates that the function in path should be called to get a default value for the field. The given function must be callable as fn() -> T. For example default = "my_value" would call my_value() and default = "AType::empty" would call AType::empty().

§Example
#[derive(NixDeserialize)]
struct Field {
    number: u64,
    #[nix(version="..20", default="missing_string")]
    messsage: String,
}

fn missing_string() -> String {
    "missing string".to_string()
}

Modules§

Macros§

  • Macro to implement NixDeserialize on a type. Sometimes you can’t use the deriver to implement NixDeserialize (like when dealing with types in Rust standard library) but don’t want to implement it yourself. So this macro can be used for those situations where you would derive using #[nix(from_str)], #[nix(from = "FromType")] or #[nix(try_from = "FromType")] if you could.
  • Macro to implement NixSerialize on a type. Sometimes you can’t use the deriver to implement NixSerialize (like when dealing with types in Rust standard library) but don’t want to implement it yourself. So this macro can be used for those situations where you would derive using #[nix(display)], #[nix(display = "path")], #[nix(store_dir_display)], #[nix(into = "IntoType")] or #[nix(try_into = "IntoType")] if you could.

Derive Macros§