declio/
derive.rs

1//! Derive macros.
2//!
3//! **Note:** The macros themselves are not contained in this module; they are at the top level of
4//! the crate. This module is used to document them.
5//!
6//! The `Encode` and `Decode` macros generate implementations of their respective traits.
7//! For structs, each field of the struct is encoded or decoded in the order they are listed.
8//! For enums, each variant is encoded and decoded as if it were a struct, but some additional
9//! attributes are required to know which variant to pick when decoding. Specifically, either
10//! `id_type` or `id_expr` must be specified outside the enum, and an `id` expression must be
11//! specified for each variant. See below for more information.
12//!
13//! # Attributes
14//!
15//! The implementation can be modified by attributes at several levels:
16//!
17//! - **Container** attributes are applied to the outside of the struct or enum.
18//! - **Variant** attributes are applied to the outside of an enum variant.
19//! - **Field** attributes are prepended to the field declarartion.
20//!
21//! Some attributes can be _asymmetric_, meaning different values may be specified for `Encode` and
22//! `Decode`. To do this, instead of providing a single value like `#[declio(name = "value")]`, the
23//! syntax `#[declio(name(encode = "value1", decode = "value2"))]` will also work. Either of the
24//! `encode` or `decode` values may be omitted, and the default will be used, as if the attribute
25//! is not present.
26//!
27//! Unless otherwise specified, the attributes listed are optional.
28//!
29//! ## Attribute Expressions
30//!
31//! Some attributes accept values in the form of _expressions_. These should be provided
32//! as a string literal (surrounded by quotes), and they have access to the context bindings
33//! defined in the container-level `ctx` attribute, as well as the values (by reference) of any of
34//! the fields declared _before_ the attribute. Field values can be accessed by the field's name,
35//! or by `field_0`, `field_1`, etc. if it is a tuple struct or variant. It may also use the try
36//! operator `?`, provided that the error type can be converted into a `declio::Error`.
37//!
38//! The classic example of this is using an integer field adjacent to a `Vec` to provide the length
39//! context required by the `Vec`:
40//!
41//! ```
42//! use declio::{Encode, Decode};
43//! use declio::ctx::{Len, Endian};
44//! use std::convert::TryInto;
45//!
46//! #[derive(Encode, Decode)]
47//! struct LengthPrefixedBytes {
48//!     #[declio(ctx = "Endian::Big")]
49//!     len: u16,
50//!     #[declio(ctx = "Len((*len).try_into()?)")]
51//!     bytes: Vec<u8>,
52//! }
53//! ```
54//!
55//! ## Container Attributes
56//!
57//! - **`crate_path`** - Specify a custom path to the `declio` crate. If you use the `declio` crate
58//! under a different name, this must be set to that path for the `derive` to successfully compile.
59//!
60//! - **`ctx`** (Asymmetric) - A comma-separated list of context fields, specified by `$ident:
61//! $type` (e.g. `tag: i32`). The `Ctx` type parameter of the resulting `Encode` or `Decode` impl
62//! will be a _n_-tuple of the given types if n > 1, or the given type itself if n = 1, and the
63//! context values will be bound to the given `ident`s to be used in attribute expressions.
64//! When not present, the context type is the unit type `()`. Example:
65//!
66//! ```
67//! use declio::{Encode, Decode};
68//! use declio::ctx::Len;
69//!
70//! /// Accepts a context
71//! #[derive(Encode, Decode)]
72//! #[declio(ctx = "len: usize")]
73//! struct UnknownLength<T: Encode + Decode> {
74//!     #[declio(ctx = "Len(len)")]
75//!     vec: Vec<T>,
76//! }
77//! ```
78//!
79//! - **`id_expr`** (Asymmetric, required for enums, conflicts with `id_type`) - Use the given expression as
80//! the variant ID when decoding. Unlike `id_type`, the variant ID is not encoded or decoded as
81//! part of the enum. Useful for specifying a variant ID via a `ctx` field.  
82//! When encoding, the given expression will also be checked against the variant to ensure it is
83//! correct, and an error will be raised if they do not match. If you want to suppress this
84//! behavior (ie if the value in `id_expr` is not available during encoding), you can pass it asymmetrically,
85//! like `id_expr(decode = "...")`.
86//!
87//! - **`id_type`** (Required for enums, conflicts with `id_expr`) - Encode or decode the variant ID
88//! as the given type before encoding/decoding the fields.
89//!
90//! - **`id_ctx`** (Asymmetric, conflicts with `id_expr`) - If encoding or decoding a variant ID
91//! with `id_type`, this attribute will set the context used by the ID encoder or decoder.
92//!
93//! ## Variant Attributes
94//!
95//! - **`id`** - An expression used to match the variant ID when decoding, and to encode the variant
96//! when `id_type` is being used.
97//!
98//! ## Field Attributes
99//!
100//! - **`ctx`** (Asymmetric) The context value to be passed to the field's encoder or decoder. When
101//! not present, the passed context is the unit context.
102//!
103//! - **`with`** (Conflicts with `encode_with` and `decode_with`) - Uses the given helper functions
104//! to encode or decode the field instead of the field type's `Encode` or `Decode` implementation.
105//! Should be a path to a module with these definitions:
106//!
107//! ```
108//! # type T = ();
109//! # type Ctx = ();
110//! fn encode<W>(val: &T, ctx: Ctx, writer: &mut W) -> Result<(), declio::Error>
111//! where
112//!     W: std::io::Write,
113//! {
114//! # todo!()
115//!     /* ... */
116//! }
117//!
118//! fn decode<R>(ctx: Ctx, reader: &mut R) -> Result<T, declio::Error>
119//! where
120//!     R: std::io::Read,
121//! {
122//! # todo!()
123//!     /* ... */
124//! }
125//! ```
126//!
127//! where `T` is the field type and `Ctx` is the type of the context provided by `ctx` (or the
128//! unit type `()` if not specified).
129//!
130//! - **`encode_with`** (Conflicts with `with`) - Uses the given helper function to encode the field
131//! instead of the field type's `Encode` implementation. Should be a path to a function with the
132//! signature `fn<W: std::io::Write>(&T, Ctx, &mut W) -> Result<(), declio::Error>`, where `T` is
133//! the field type and `Ctx` is the type of the context provided by `ctx` (or the unit type `()` if
134//! not specified).
135//!
136//! - **`decode_with`** (Conflicts with `with`) - Uses the given helper function to decode the field
137//! instead of the field type's `Decode` implementation. Should be a path to a function with the
138//! signature `fn<R: std::io::Read>(Ctx, &mut R) -> Result<T, declio::Error>`, where `T` is the
139//! field type and `Ctx` is the type of the context provided by `ctx` (or the unit type `()` if not
140//! specified).
141//!
142//! - **`skip_if`** - If the given expression evaluates true, the field will not be encoded or
143//! decoded. When decoding, the field will be given the value of `Default::default()` instead.
144//!
145//!   For example, this is useful for optionally encoding or decoding a field based on the value of
146//!   a previous field. In particular, it is impossible to get `None` from `Option::decode` without
147//!   using `skip_if`, since it assumes that the inner value is present:
148//!
149//! ```
150//! use declio::{Encode, Decode};
151//! use declio::ctx::Endian;
152//!
153//! #[derive(Encode, Decode)]
154//! struct OptionalExtraData {
155//!     tag: u8,
156//!
157//!     #[declio(ctx = "Endian::Big", skip_if = "*tag != 2")]
158//!     extra_data: Option<u32>,
159//! }
160//! ```