snix/tvix/nix-compat-derive/src/lib.rs
Brian Olsen b88579ade4 feat(tvix/nix-compat): Add nix serialization support
This change implements the serialization part that is needed to
implement the nix daemon protocol. Previously was add deserialization
and derivers for that and this then adds the other part of that equation
so that you can write types that can then be read using deserialization.

Change-Id: I2917de634980a93822a4f5a8ad38897b9ce16d89
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12729
Autosubmit: Brian Olsen <me@griff.name>
Reviewed-by: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
2024-11-04 20:02:54 +00:00

460 lines
14 KiB
Rust

//! # Using derive
//!
//! 1. [Overview](#overview)
//! 3. [Attributes](#attributes)
//! 1. [Container attributes](#container-attributes)
//! 1. [`#[nix(from_str)]`](#nixfrom_str)
//! 2. [`#[nix(from = "FromType")]`](#nixfrom--fromtype)
//! 3. [`#[nix(try_from = "FromType")]`](#nixtry_from--fromtype)
//! 4. [`#[nix(into = "IntoType")]`](#nixinto--intotype)
//! 5. [`#[nix(try_into = "IntoType")]`](#nixtry_into--intotype)
//! 6. [`#[nix(display)]`](#nixdisplay)
//! 7. [`#[nix(display = "path")]`](#nixdisplay--path)
//! 8. [`#[nix(crate = "...")]`](#nixcrate--)
//! 2. [Variant attributes](#variant-attributes)
//! 1. [`#[nix(version = "range")]`](#nixversion--range)
//! 3. [Field attributes](#field-attributes)
//! 1. [`#[nix(version = "range")]`](#nixversion--range-1)
//! 2. [`#[nix(default)]`](#nixdefault)
//! 3. [`#[nix(default = "path")]`](#nixdefault--path)
//!
//! ## Overview
//!
//! This crate contains derive macros and function-like macros for implementing
//! `NixDeserialize` and `NixSerialize` with less boilerplate.
//!
//! ### Examples
//!
//! ```rust
//! # use nix_compat_derive::{NixDeserialize, NixSerialize};
//! #
//! #[derive(NixDeserialize, NixSerialize)]
//! struct Unnamed(u64, String);
//! ```
//!
//! ```rust
//! # use nix_compat_derive::{NixDeserialize, NixSerialize};
//! #
//! #[derive(NixDeserialize, NixSerialize)]
//! struct Fields {
//! number: u64,
//! message: String,
//! };
//! ```
//!
//! ```rust
//! # use nix_compat_derive::{NixDeserialize, NixSerialize};
//! #
//! #[derive(NixDeserialize, NixSerialize)]
//! struct Ignored;
//! ```
//!
//! ## Attributes
//!
//! To customize the derived trait implementations you can add
//! [attributes](https://doc.rust-lang.org/reference/attributes.html)
//! to containers, fields and variants.
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixSerialize;
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixSerialize;
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixSerialize;
//! # use std::fmt::{Display, Result, Formatter};
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixSerialize;
//! # use std::fmt::{Display, Result, Formatter};
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[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
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[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")]`](#nixversion--range-1)
//! 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")]`](#nixversion--range-1).
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[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")]`](#nixversion--range-1)
//! 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
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! struct Field {
//! number: u64,
//! #[nix(version="..20", default="missing_string")]
//! messsage: String,
//! }
//!
//! fn missing_string() -> String {
//! "missing string".to_string()
//! }
//! ```
use internal::inputs::RemoteInput;
use proc_macro::TokenStream;
use syn::{parse_quote, DeriveInput};
mod de;
mod internal;
mod ser;
#[proc_macro_derive(NixDeserialize, attributes(nix))]
pub fn derive_nix_deserialize(item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as DeriveInput);
let crate_path: syn::Path = parse_quote!(::nix_compat);
de::expand_nix_deserialize(crate_path, &mut input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro_derive(NixSerialize, attributes(nix))]
pub fn derive_nix_serialize(item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as DeriveInput);
let crate_path: syn::Path = parse_quote!(::nix_compat);
ser::expand_nix_serialize(crate_path, &mut input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
/// 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.
///
/// #### Example
///
/// ```rust
/// # use nix_compat_derive::nix_deserialize_remote;
/// #
/// struct MyU64(u64);
///
/// impl From<u64> for MyU64 {
/// fn from(value: u64) -> Self {
/// Self(value)
/// }
/// }
///
/// nix_deserialize_remote!(#[nix(from="u64")] MyU64);
/// ```
#[proc_macro]
pub fn nix_deserialize_remote(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as RemoteInput);
let crate_path = parse_quote!(::nix_compat);
de::expand_nix_deserialize_remote(crate_path, &input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
/// 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.
///
/// #### Example
///
/// ```rust
/// # use nix_compat_derive::nix_serialize_remote;
/// #
/// #[derive(Clone)]
/// struct MyU64(u64);
///
/// impl From<MyU64> for u64 {
/// fn from(value: MyU64) -> Self {
/// value.0
/// }
/// }
///
/// nix_serialize_remote!(#[nix(into="u64")] MyU64);
/// ```
#[proc_macro]
pub fn nix_serialize_remote(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as RemoteInput);
let crate_path = parse_quote!(::nix_compat);
ser::expand_nix_serialize_remote(crate_path, &input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}