├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── declio ├── Cargo.toml ├── src │ ├── ctx.rs │ ├── derive.rs │ ├── error.rs │ ├── lib.rs │ ├── macros.rs │ └── util.rs └── tests │ └── derive.rs └── declio_derive ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "declio", 4 | "declio_derive", 5 | ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Adam Gausmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # declio 2 | 3 | A declarative I/O serialization library. 4 | 5 | `declio` provides a pair of traits, [`Encode`] and [`Decode`], that facilitate bidirectional 6 | conversions between binary data streams (the `std::io` traits) and arbitrary data types. 7 | 8 | These traits are implemented for many of the types in `std`; integer and floating-point 9 | primitives can be encoded and decoded as their big- or little-endian binary representations, 10 | and collections and other container types encode and decode the data they contain. 11 | 12 | However, there are some notable exceptions; for example, there are no implementations for 13 | `bool` and `str`/`String`. This is because these types have several common representations, so 14 | to avoid accidental misuse, you are required to explicitly declare their representation. Some 15 | of the common representations are provided in the [`util`] module, implemented as both wrapper 16 | types and helper modules. 17 | 18 | This crate also provides a pair of derive macros, via the default feature `derive`, that can 19 | implement `Encode` and `Decode` for arbitrary compound data types. By default it will encode 20 | and decode all of its fields in order, but it is highly configurable, intended to target the 21 | many different patterns found in binary formats. 22 | 23 | Inspiration for this crate largely comes from [`deku`], but incorporating some changes based on 24 | my own opinions and preferences. For example, `declio` uses byte-wise data streams from 25 | `std::io` instead of the bit-wise `BitVec`s used by `deku`. 26 | 27 | ## Examples 28 | 29 | Let's start with a simple example - encoding a single integer into a byte buffer: 30 | 31 | ```rust 32 | use declio::Encode; 33 | use declio::ctx::Endian; 34 | 35 | let mut buf: Vec = Vec::new(); 36 | u32::encode(&0xdeadbeef, Endian::Big, &mut buf) 37 | .expect("encode failed"); 38 | 39 | assert_eq!(buf, [0xde, 0xad, 0xbe, 0xef]); 40 | ``` 41 | 42 | In this example, [`Endian::Big`] is a "context" value. `declio` provides these as an easy way to 43 | configure or alter an implementation of `Encode` or `Decode` at runtime, instead of using 44 | wrapper types or helper functions. In this case, it tells the implementation of `Encode` for 45 | `u32` to encode the bytes in big-endian order. It can instead be set to [`Endian::Little`] to 46 | reverse the byte order, and in general, [`Endian`] can be passed to any of the integer and 47 | floating-point primitive types for similar effects. 48 | 49 | Context can also be used to abstract some fields that are necessary to encode and decode some 50 | types. For example, containers with variable length like `Vec` accept a [`Len`] context value 51 | during decoding, which tells the `Decode` implementation how many values it should decode. 52 | This can be a compile-time constant like `Len(1024)`, or it can be created from another value 53 | at runtime, like this: 54 | 55 | ```rust 56 | use declio::Decode; 57 | use declio::ctx::{Len, Endian}; 58 | 59 | let mut bytes = &[ 60 | // len 61 | 0x00, 0x02, 62 | 63 | // words[0] 64 | 0xde, 0xad, 65 | 66 | // words[1] 67 | 0xbe, 0xef, 68 | ][..]; 69 | 70 | let len = u16::decode(Endian::Big, &mut bytes) 71 | .expect("decode len failed"); 72 | 73 | let words: Vec = Vec::decode((Len(len as usize), Endian::Big), &mut bytes) 74 | .expect("decode bytes failed"); 75 | 76 | assert!(bytes.is_empty()); // did we consume the whole buffer? 77 | assert_eq!(words, [0xdead, 0xbeef]); 78 | ``` 79 | 80 | The reason for this is that the `Decode` implementation for `Vec` does not know how to read the 81 | length value. It doesn't know what integer size or byte order the binary format uses to encode 82 | the length; it doesn't even know if the length is encoded at all! It might be some fixed length 83 | defined as part of the format. 84 | 85 | `Vec::decode` can also accept an additional context value to pass to the element decoder, using 86 | a 2-tuple like `(Len(len as usize), Endian::Big)`. However, in this example, only a `Len` is 87 | passed, which is also valid and will pass `()` as context to the element decoder. 88 | 89 | ### Deriving 90 | 91 | Here is an example which makes use of derive macros to encode and decode a 92 | user-defined data type. This is not a complete demonstration of the features of the derive 93 | macros; for a more complete reference, see the [`mod@derive`] module docs. 94 | 95 | ```rust 96 | use declio::{Encode, Decode}; 97 | use declio::ctx::{Endian, Len}; 98 | use std::convert::TryInto; 99 | 100 | #[derive(Debug, PartialEq, Encode, Decode)] 101 | struct WithLength { 102 | // Context can be passed to the field decoder with a `ctx` attribute. 103 | #[declio(ctx = "Endian::Little")] 104 | len: u16, 105 | 106 | // Context may be different for encode and decode, 107 | // though they should generally be as symmetric as possible. 108 | // For example, `Vec` requires a `Len` when decoding, but it is 109 | // optional for encoding. However, it should be provided anyway 110 | // because it will be used to check that the encoded length is 111 | // the same as the actual length. 112 | // 113 | // Fields declared before this one can be accessed by name 114 | // (or by `field_0`, `field_1`, etc for tuple structs): 115 | #[declio(ctx = "Len((*len).try_into()?)")] 116 | bytes: Vec, 117 | } 118 | 119 | let bytes: Vec = vec![0xde, 0xad, 0xbe, 0xef]; 120 | 121 | let with_length = WithLength { 122 | len: bytes.len().try_into().expect("length out of range"), 123 | bytes, 124 | }; 125 | 126 | let encoded: Vec = declio::to_bytes(&with_length) 127 | .expect("encode failed"); 128 | assert_eq!(encoded, [0x04, 0x00, 0xde, 0xad, 0xbe, 0xef]); 129 | 130 | let decoded: WithLength = declio::from_bytes(&encoded) 131 | .expect("decode failed"); 132 | 133 | assert_eq!(decoded, with_length); 134 | ``` 135 | 136 | [`deku`]: https://crates.io/crates/deku 137 | -------------------------------------------------------------------------------- /declio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "declio" 3 | version = "0.2.0" 4 | authors = ["Adam Gausmann "] 5 | edition = "2018" 6 | description = "A declarative I/O serialization library." 7 | readme = "../README.md" 8 | repository = "https://github.com/agausmann/declio" 9 | license = "MIT" 10 | keywords = ["binary", "io", "declarative", "bidirectional"] 11 | categories = ["encoding", "parsing"] 12 | 13 | [features] 14 | default = ["derive"] 15 | derive = ["declio_derive"] 16 | 17 | [dependencies] 18 | declio_derive = { path = "../declio_derive", version = "0.2", optional = true } 19 | -------------------------------------------------------------------------------- /declio/src/ctx.rs: -------------------------------------------------------------------------------- 1 | //! Types that give context to encoders and decoders. 2 | 3 | /// The endianness, or byte order, of primitive types. 4 | #[derive(Debug, Clone, Copy, PartialEq)] 5 | pub enum Endian { 6 | /// Big-endian (most-significant-byte first). 7 | Big, 8 | /// Little-endian (least-significant-byte first). 9 | Little, 10 | } 11 | 12 | impl Endian { 13 | /// The native endianness of the target architecture. 14 | /// 15 | /// **Warning** - This should not be used for cross-platform I/O in general. While dealing with 16 | /// native-endian bytes is marginally more efficient, it may cause incompatibilities if the 17 | /// data is shared between multiple devices where the native byte orders are different. 18 | pub const fn native() -> Self { 19 | #[cfg(target_endian = "big")] 20 | let endian = Self::Big; 21 | 22 | #[cfg(target_endian = "little")] 23 | let endian = Self::Little; 24 | 25 | endian 26 | } 27 | 28 | /// "Network-endian", an alias for big-endian. 29 | pub const fn network() -> Self { 30 | Self::Big 31 | } 32 | } 33 | 34 | /// The number of elements in variable-sized containers. 35 | #[derive(Debug, Clone, Copy, PartialEq)] 36 | pub struct Len(pub usize); 37 | -------------------------------------------------------------------------------- /declio/src/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, 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 { 74 | //! #[declio(ctx = "Len(len)")] 75 | //! vec: Vec, 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(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(ctx: Ctx, reader: &mut R) -> Result 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(&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(Ctx, &mut R) -> Result`, 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, 159 | //! } 160 | //! ``` 161 | -------------------------------------------------------------------------------- /declio/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Encoding and decoding errors. 4 | pub struct Error { 5 | message: String, 6 | source: Option>, 7 | } 8 | 9 | impl Error { 10 | /// `Creates a new `Error` with the given message. 11 | pub fn new(message: S) -> Self 12 | where 13 | S: ToString, 14 | { 15 | Self { 16 | message: message.to_string(), 17 | source: None, 18 | } 19 | } 20 | 21 | /// Creates a new `Error` with the given error value as the source. 22 | pub fn wrap(error: E) -> Self 23 | where 24 | E: std::error::Error + Send + Sync + 'static, 25 | { 26 | Self { 27 | message: error.to_string(), 28 | source: Some(Box::new(error)), 29 | } 30 | } 31 | 32 | /// Creates a new `Error` with a custom message and a source error value. 33 | pub fn with_context(message: S, error: E) -> Self 34 | where 35 | S: ToString, 36 | E: std::error::Error + Send + Sync + 'static, 37 | { 38 | Self { 39 | message: message.to_string(), 40 | source: Some(Box::new(error)), 41 | } 42 | } 43 | } 44 | 45 | impl fmt::Debug for Error { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | // defer to Display 48 | write!(f, "{}", self) 49 | } 50 | } 51 | 52 | impl fmt::Display for Error { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | write!(f, "{}", self.message) 55 | } 56 | } 57 | 58 | impl std::error::Error for Error { 59 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 60 | self.source 61 | .as_ref() 62 | .map(Box::as_ref) 63 | .map(|e| e as &(dyn std::error::Error + 'static)) 64 | } 65 | } 66 | 67 | macro_rules! convert_error { 68 | ($($t:ty,)*) => {$( 69 | impl From<$t> for Error { 70 | fn from(error: $t) -> Self { 71 | Self::wrap(error) 72 | } 73 | } 74 | )*} 75 | } 76 | 77 | convert_error! { 78 | std::convert::Infallible, 79 | std::array::TryFromSliceError, 80 | std::char::CharTryFromError, 81 | std::char::DecodeUtf16Error, 82 | std::io::Error, 83 | std::num::TryFromIntError, 84 | std::str::Utf8Error, 85 | std::string::FromUtf8Error, 86 | std::string::FromUtf16Error, 87 | } 88 | -------------------------------------------------------------------------------- /declio/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A declarative I/O serialization library. 2 | //! 3 | //! `declio` provides a pair of traits, [`Encode`] and [`Decode`], that facilitate bidirectional 4 | //! conversions between binary data streams (the `std::io` traits) and arbitrary data types. 5 | //! 6 | //! These traits are implemented for many of the types in `std`; integer and floating-point 7 | //! primitives can be encoded and decoded as their big- or little-endian binary representations, 8 | //! and collections and other container types encode and decode the data they contain. 9 | //! 10 | //! However, there are some notable exceptions; for example, there are no implementations for 11 | //! `bool` and `str`/`String`. This is because these types have several common representations, so 12 | //! to avoid accidental misuse, you are required to explicitly declare their representation. Some 13 | //! of the common representations are provided in the [`util`] module, implemented as both wrapper 14 | //! types and helper modules. 15 | //! 16 | //! This crate also provides a pair of derive macros, via the default feature `derive`, that can 17 | //! implement `Encode` and `Decode` for arbitrary compound data types. By default it will encode 18 | //! and decode all of its fields in order, but it is highly configurable, intended to target the 19 | //! many different patterns found in binary formats. 20 | //! 21 | //! Inspiration for this crate largely comes from [`deku`], but incorporating some changes based on 22 | //! my own opinions and preferences. For example, `declio` uses byte-wise data streams from 23 | //! `std::io` instead of the bit-wise `BitVec`s used by `deku`. 24 | //! 25 | //! # Examples 26 | //! 27 | //! Let's start with a simple example - encoding a single integer into a byte buffer: 28 | //! 29 | //! ``` 30 | //! use declio::Encode; 31 | //! use declio::ctx::Endian; 32 | //! 33 | //! let mut buf: Vec = Vec::new(); 34 | //! u32::encode(&0xdeadbeef, Endian::Big, &mut buf) 35 | //! .expect("encode failed"); 36 | //! 37 | //! assert_eq!(buf, [0xde, 0xad, 0xbe, 0xef]); 38 | //! ``` 39 | //! 40 | //! In this example, [`Endian::Big`] is a "context" value. `declio` provides these as an easy way to 41 | //! configure or alter an implementation of `Encode` or `Decode` at runtime, instead of using 42 | //! wrapper types or helper functions. In this case, it tells the implementation of `Encode` for 43 | //! `u32` to encode the bytes in big-endian order. It can instead be set to [`Endian::Little`] to 44 | //! reverse the byte order, and in general, [`Endian`] can be passed to any of the integer and 45 | //! floating-point primitive types for similar effects. 46 | //! 47 | //! Context can also be used to abstract some fields that are necessary to encode and decode some 48 | //! types. For example, containers with variable length like `Vec` accept a [`Len`] context value 49 | //! during decoding, which tells the `Decode` implementation how many values it should decode. 50 | //! This can be a compile-time constant like `Len(1024)`, or it can be created from another value 51 | //! at runtime, like this: 52 | //! 53 | //! ``` 54 | //! use declio::Decode; 55 | //! use declio::ctx::{Len, Endian}; 56 | //! 57 | //! let mut bytes = &[ 58 | //! // len 59 | //! 0x00, 0x02, 60 | //! 61 | //! // words[0] 62 | //! 0xde, 0xad, 63 | //! 64 | //! // words[1] 65 | //! 0xbe, 0xef, 66 | //! ][..]; 67 | //! 68 | //! let len = u16::decode(Endian::Big, &mut bytes) 69 | //! .expect("decode len failed"); 70 | //! 71 | //! let words: Vec = Vec::decode((Len(len as usize), Endian::Big), &mut bytes) 72 | //! .expect("decode bytes failed"); 73 | //! 74 | //! assert!(bytes.is_empty()); // did we consume the whole buffer? 75 | //! assert_eq!(words, [0xdead, 0xbeef]); 76 | //! ``` 77 | //! 78 | //! The reason for this is that the `Decode` implementation for `Vec` does not know how to read the 79 | //! length value. It doesn't know what integer size or byte order the binary format uses to encode 80 | //! the length; it doesn't even know if the length is encoded at all! It might be some fixed length 81 | //! defined as part of the format. 82 | //! 83 | //! `Vec::decode` can also accept an additional context value to pass to the element decoder, using 84 | //! a 2-tuple like `(Len(len as usize), Endian::Big)`. However, in this example, only a `Len` is 85 | //! passed, which is also valid and will pass `()` as context to the element decoder. 86 | //! 87 | //! ## Deriving 88 | //! 89 | //! Here is an example which makes use of derive macros to encode and decode a 90 | //! user-defined data type. This is not a complete demonstration of the features of the derive 91 | //! macros; for a more complete reference, see the [`mod@derive`] module docs. 92 | //! 93 | //! ``` 94 | //! use declio::{Encode, Decode}; 95 | //! use declio::ctx::{Endian, Len}; 96 | //! use std::convert::TryInto; 97 | //! 98 | //! #[derive(Debug, PartialEq, Encode, Decode)] 99 | //! struct WithLength { 100 | //! // Context can be passed to the field decoder with a `ctx` attribute. 101 | //! #[declio(ctx = "Endian::Little")] 102 | //! len: u16, 103 | //! 104 | //! // Context may be different for encode and decode, 105 | //! // though they should generally be as symmetric as possible. 106 | //! // For example, `Vec` requires a `Len` when decoding, but it is 107 | //! // optional for encoding. However, it should be provided anyway 108 | //! // because it will be used to check that the encoded length is 109 | //! // the same as the actual length. 110 | //! // 111 | //! // Fields declared before this one can be accessed by name 112 | //! // (or by `field_0`, `field_1`, etc for tuple structs): 113 | //! #[declio(ctx = "Len((*len).try_into()?)")] 114 | //! bytes: Vec, 115 | //! } 116 | //! 117 | //! let bytes: Vec = vec![0xde, 0xad, 0xbe, 0xef]; 118 | //! 119 | //! let with_length = WithLength { 120 | //! len: bytes.len().try_into().expect("length out of range"), 121 | //! bytes, 122 | //! }; 123 | //! 124 | //! let encoded: Vec = declio::to_bytes(&with_length) 125 | //! .expect("encode failed"); 126 | //! assert_eq!(encoded, [0x04, 0x00, 0xde, 0xad, 0xbe, 0xef]); 127 | //! 128 | //! let decoded: WithLength = declio::from_bytes(&encoded) 129 | //! .expect("decode failed"); 130 | //! 131 | //! assert_eq!(decoded, with_length); 132 | //! ``` 133 | //! 134 | //! [`deku`]: https://crates.io/crates/deku 135 | 136 | #![warn(missing_docs)] 137 | 138 | mod error; 139 | mod macros; 140 | 141 | pub mod ctx; 142 | pub mod derive; 143 | pub mod util; 144 | 145 | pub use self::error::Error; 146 | 147 | #[doc(hidden)] 148 | pub use std as export; 149 | 150 | #[cfg(feature = "derive")] 151 | /// Implements [`Decode`] for a given type. For more information, see [`derive`](derive/index.html). 152 | pub use declio_derive::Decode; 153 | 154 | #[cfg(feature = "derive")] 155 | /// Implements [`Encode`] for a given type. For more information, see [`derive`](derive/index.html). 156 | pub use declio_derive::Encode; 157 | 158 | use self::ctx::{Endian, Len}; 159 | use std::borrow::Cow; 160 | use std::{io, mem}; 161 | 162 | /// Encodes a value into a vector of bytes. 163 | pub fn to_bytes(value: T) -> Result, Error> 164 | where 165 | T: Encode, 166 | { 167 | to_bytes_with_context(value, ()) 168 | } 169 | 170 | /// Encodes a value into a vector of bytes, with context. 171 | pub fn to_bytes_with_context(value: T, ctx: Ctx) -> Result, Error> 172 | where 173 | T: Encode, 174 | { 175 | let mut bytes = Vec::new(); 176 | value.encode(ctx, &mut bytes)?; 177 | Ok(bytes) 178 | } 179 | 180 | /// Decodes a value from a byte slice. 181 | /// 182 | /// The byte slice should be consumed entirely; if there are bytes left over after decoding, it 183 | /// will return an error. 184 | pub fn from_bytes(bytes: &[u8]) -> Result 185 | where 186 | T: Decode, 187 | { 188 | from_bytes_with_context(bytes, ()) 189 | } 190 | 191 | /// Decodes a value from a byte slice, with context. 192 | /// 193 | /// The byte slice should be consumed entirely; if there are bytes left over after decoding, it 194 | /// will return an error. 195 | pub fn from_bytes_with_context(mut bytes: &[u8], ctx: Ctx) -> Result 196 | where 197 | T: Decode, 198 | { 199 | let value = T::decode(ctx, &mut bytes)?; 200 | if bytes.is_empty() { 201 | Ok(value) 202 | } else { 203 | Err(Error::new("byte slice was not fully consumed")) 204 | } 205 | } 206 | 207 | /// A type that can be encoded into a byte stream. 208 | pub trait Encode { 209 | /// Encodes `&self` to the given writer. 210 | fn encode(&self, ctx: Ctx, writer: &mut W) -> Result<(), Error> 211 | where 212 | W: io::Write; 213 | } 214 | 215 | /// A type that can be decoded from a byte stream. 216 | pub trait Decode: Sized { 217 | /// Decodes a value from the given reader. 218 | fn decode(ctx: Ctx, reader: &mut R) -> Result 219 | where 220 | R: io::Read; 221 | } 222 | 223 | impl Encode for &T 224 | where 225 | T: Encode, 226 | { 227 | fn encode(&self, ctx: Ctx, writer: &mut W) -> Result<(), Error> 228 | where 229 | W: io::Write, 230 | { 231 | (*self).encode(ctx, writer) 232 | } 233 | } 234 | 235 | impl Encode<(Len, Ctx)> for [T] 236 | where 237 | T: Encode, 238 | Ctx: Clone, 239 | { 240 | /// Encodes each element of the vector in order. 241 | /// 242 | /// If length is also to be encoded, it has to be done separately. 243 | /// 244 | /// The length context is provided as a sanity check to protect against logic errors; if the 245 | /// provided length context is not equal to the vector's length, then this function will return 246 | /// an error. 247 | fn encode(&self, (Len(len), inner_ctx): (Len, Ctx), writer: &mut W) -> Result<(), Error> 248 | where 249 | W: io::Write, 250 | { 251 | if self.len() != len { 252 | Err(Error::new( 253 | "provided length context does not match the slice length", 254 | )) 255 | } else { 256 | self.encode((inner_ctx,), writer) 257 | } 258 | } 259 | } 260 | 261 | impl Encode for [T] 262 | where 263 | T: Encode, 264 | { 265 | /// Encodes each element of the vector in order. 266 | /// 267 | /// If length is also to be encoded, it has to be done separately. 268 | /// 269 | /// The length context is provided as a sanity check to protect against logic errors; if the 270 | /// provided length context is not equal to the vector's length, then this function will return 271 | /// an error. 272 | fn encode(&self, len: Len, writer: &mut W) -> Result<(), Error> 273 | where 274 | W: io::Write, 275 | { 276 | self.encode((len, ()), writer) 277 | } 278 | } 279 | 280 | impl Encode<(Ctx,)> for [T] 281 | where 282 | T: Encode, 283 | Ctx: Clone, 284 | { 285 | /// Encodes each element of the slice in order. 286 | /// 287 | /// If length is also to be encoded, it has to be done separately. 288 | fn encode(&self, (inner_ctx,): (Ctx,), writer: &mut W) -> Result<(), Error> 289 | where 290 | W: io::Write, 291 | { 292 | for elem in self { 293 | elem.encode(inner_ctx.clone(), writer)?; 294 | } 295 | Ok(()) 296 | } 297 | } 298 | 299 | impl Encode for [T; N] 300 | where 301 | T: Encode, 302 | Ctx: Clone, 303 | { 304 | fn encode(&self, inner_ctx: Ctx, writer: &mut W) -> Result<(), Error> 305 | where 306 | W: io::Write, 307 | { 308 | for elem in self { 309 | elem.encode(inner_ctx.clone(), writer)?; 310 | } 311 | Ok(()) 312 | } 313 | } 314 | 315 | impl Decode for [T; N] 316 | where 317 | T: Decode + Copy + Default, 318 | Ctx: Clone, 319 | { 320 | fn decode(inner_ctx: Ctx, reader: &mut R) -> Result 321 | where 322 | R: io::Read, 323 | { 324 | //TODO: Use MaybeUninit when stabilized with arrays 325 | let mut arr = [Default::default(); N]; 326 | for slot in &mut arr { 327 | *slot = Decode::decode(inner_ctx.clone(), reader)?; 328 | } 329 | Ok(arr) 330 | } 331 | } 332 | 333 | impl Encode<(Len, Ctx)> for Vec 334 | where 335 | T: Encode, 336 | Ctx: Clone, 337 | { 338 | /// Encodes each element of the vector in order. 339 | /// 340 | /// If length is also to be encoded, it has to be done separately. 341 | /// 342 | /// The length context is provided as a sanity check to protect against logic errors; if the 343 | /// provided length context is not equal to the vector's length, then this function will return 344 | /// an error. 345 | fn encode(&self, ctx: (Len, Ctx), writer: &mut W) -> Result<(), Error> 346 | where 347 | W: io::Write, 348 | { 349 | self.as_slice().encode(ctx, writer) 350 | } 351 | } 352 | 353 | impl Encode for Vec 354 | where 355 | T: Encode, 356 | { 357 | /// Encodes each element of the vector in order. 358 | /// 359 | /// If length is also to be encoded, it has to be done separately. 360 | /// 361 | /// The length context is provided as a sanity check to protect against logic errors; if the 362 | /// provided length context is not equal to the vector's length, then this function will return 363 | /// an error. 364 | fn encode(&self, ctx: Len, writer: &mut W) -> Result<(), Error> 365 | where 366 | W: io::Write, 367 | { 368 | self.as_slice().encode(ctx, writer) 369 | } 370 | } 371 | 372 | impl Encode<(Ctx,)> for Vec 373 | where 374 | T: Encode, 375 | Ctx: Clone, 376 | { 377 | /// Encodes each element of the vector in order. 378 | /// 379 | /// If length is also to be encoded, it has to be done separately. 380 | fn encode(&self, ctx: (Ctx,), writer: &mut W) -> Result<(), Error> 381 | where 382 | W: io::Write, 383 | { 384 | self.as_slice().encode(ctx, writer) 385 | } 386 | } 387 | 388 | impl Decode<(Len, Ctx)> for Vec 389 | where 390 | T: Decode, 391 | Ctx: Clone, 392 | { 393 | /// Decodes multiple values of type `T`, collecting them in a `Vec`. 394 | /// 395 | /// The length of the vector / number of elements decoded is equal to the value of the 396 | /// `Len` context. 397 | fn decode((Len(len), inner_ctx): (Len, Ctx), reader: &mut R) -> Result 398 | where 399 | R: io::Read, 400 | { 401 | let mut acc = Self::with_capacity(len); 402 | for _ in 0..len { 403 | acc.push(T::decode(inner_ctx.clone(), reader)?); 404 | } 405 | Ok(acc) 406 | } 407 | } 408 | 409 | impl Decode for Vec 410 | where 411 | T: Decode, 412 | { 413 | /// Decodes multiple values of type `T`, collecting them in a `Vec`. 414 | /// 415 | /// The length of the vector / number of elements decoded is equal to the value of the 416 | /// `Len` context. 417 | fn decode(len: Len, reader: &mut R) -> Result 418 | where 419 | R: io::Read, 420 | { 421 | Self::decode((len, ()), reader) 422 | } 423 | } 424 | 425 | impl Encode for Option 426 | where 427 | T: Encode, 428 | { 429 | /// If `Some`, then the inner value is encoded, otherwise, nothing is written. 430 | fn encode(&self, inner_ctx: Ctx, writer: &mut W) -> Result<(), Error> 431 | where 432 | W: io::Write, 433 | { 434 | if let Some(inner) = self { 435 | inner.encode(inner_ctx, writer) 436 | } else { 437 | Ok(()) 438 | } 439 | } 440 | } 441 | 442 | impl Decode for Option 443 | where 444 | T: Decode, 445 | { 446 | /// Decodes a value of type `T` and wraps it in `Some`. 447 | /// 448 | /// Detecting and deserializing a `None` should be done outside of this function by 449 | /// checking the relevant conditions in other decoded values and skipping this call if a 450 | /// `None` is expected. 451 | /// 452 | /// Since serializing a `None` writes nothing, deserialization is also a no-op; just construct 453 | /// a value of `None`. 454 | fn decode(inner_ctx: Ctx, reader: &mut R) -> Result 455 | where 456 | R: io::Read, 457 | { 458 | T::decode(inner_ctx, reader).map(Some) 459 | } 460 | } 461 | 462 | impl<'a, T, Ctx> Encode for Cow<'a, T> 463 | where 464 | T: Encode + ToOwned + ?Sized, 465 | { 466 | /// Borrows a value of type `T` and encodes it. 467 | fn encode(&self, inner_ctx: Ctx, writer: &mut W) -> Result<(), Error> 468 | where 469 | W: io::Write, 470 | { 471 | T::encode(&*self, inner_ctx, writer) 472 | } 473 | } 474 | 475 | impl<'a, T, Ctx> Decode for Cow<'a, T> 476 | where 477 | T: ToOwned + ?Sized, 478 | T::Owned: Decode, 479 | { 480 | /// Decodes a value of type `T::Owned`. 481 | fn decode(inner_ctx: Ctx, reader: &mut R) -> Result 482 | where 483 | R: io::Read, 484 | { 485 | T::Owned::decode(inner_ctx, reader).map(Self::Owned) 486 | } 487 | } 488 | 489 | impl Encode for Box 490 | where 491 | T: Encode, 492 | { 493 | /// Encodes the boxed value. 494 | fn encode(&self, inner_ctx: Ctx, writer: &mut W) -> Result<(), Error> 495 | where 496 | W: io::Write, 497 | { 498 | T::encode(&*self, inner_ctx, writer) 499 | } 500 | } 501 | 502 | impl Decode for Box 503 | where 504 | T: Decode, 505 | { 506 | /// Decodes a value of type `T` and boxes it. 507 | fn decode(inner_ctx: Ctx, reader: &mut R) -> Result 508 | where 509 | R: io::Read, 510 | { 511 | T::decode(inner_ctx, reader).map(Self::new) 512 | } 513 | } 514 | 515 | impl Encode for () { 516 | /// No-op. 517 | fn encode(&self, _: (), _: &mut W) -> Result<(), Error> 518 | where 519 | W: io::Write, 520 | { 521 | Ok(()) 522 | } 523 | } 524 | 525 | impl Decode for () { 526 | /// No-op. 527 | fn decode(_: (), _: &mut R) -> Result 528 | where 529 | R: io::Read, 530 | { 531 | Ok(()) 532 | } 533 | } 534 | 535 | macro_rules! impl_primitive { 536 | ($($t:ty)*) => {$( 537 | impl Encode for $t { 538 | fn encode(&self, endian: Endian, writer: &mut W) -> Result<(), Error> 539 | where 540 | W: io::Write, 541 | { 542 | let bytes = match endian { 543 | Endian::Big => self.to_be_bytes(), 544 | Endian::Little => self.to_le_bytes(), 545 | }; 546 | writer.write_all(&bytes)?; 547 | Ok(()) 548 | } 549 | } 550 | 551 | impl Decode for $t { 552 | fn decode(endian: Endian, reader: &mut R) -> Result 553 | where 554 | R: io::Read, 555 | { 556 | let mut bytes = [0u8; mem::size_of::<$t>()]; 557 | reader.read_exact(&mut bytes)?; 558 | match endian { 559 | Endian::Big => Ok(Self::from_be_bytes(bytes)), 560 | Endian::Little => Ok(Self::from_le_bytes(bytes)), 561 | } 562 | } 563 | } 564 | )*} 565 | } 566 | 567 | impl_primitive! { 568 | u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64 569 | } 570 | 571 | // Special case: u8/i8 are single-byte values so they can be encoded/decoded without explicit 572 | // endianness context. 573 | 574 | impl Encode for u8 { 575 | fn encode(&self, _ctx: (), writer: &mut W) -> Result<(), Error> 576 | where 577 | W: io::Write, 578 | { 579 | self.encode(Endian::Big, writer) 580 | } 581 | } 582 | 583 | impl Decode for u8 { 584 | fn decode(_ctx: (), reader: &mut R) -> Result 585 | where 586 | R: io::Read, 587 | { 588 | Self::decode(Endian::Big, reader) 589 | } 590 | } 591 | 592 | impl Encode for i8 { 593 | fn encode(&self, _ctx: (), writer: &mut W) -> Result<(), Error> 594 | where 595 | W: io::Write, 596 | { 597 | self.encode(Endian::Big, writer) 598 | } 599 | } 600 | 601 | impl Decode for i8 { 602 | fn decode(_ctx: (), reader: &mut R) -> Result 603 | where 604 | R: io::Read, 605 | { 606 | Self::decode(Endian::Big, reader) 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /declio/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Defines a type that encodes and decodes as a constant byte string. 2 | /// 3 | /// When decoding, the bytes read will be compared against the given string, 4 | /// and an error will be returned if there is a mismatch. 5 | /// 6 | /// # Example 7 | /// 8 | /// ``` 9 | /// use declio::util::magic_bytes; 10 | /// 11 | /// // creates a `pub struct Foo;` and a `struct Bar;`: 12 | /// magic_bytes! { 13 | /// #[derive(Debug)] 14 | /// pub Foo(b"FOO"); 15 | /// 16 | /// #[derive(Debug)] 17 | /// Bar(b"BAR"); 18 | /// } 19 | /// 20 | /// let bytes: Vec = declio::to_bytes(&Foo).unwrap(); 21 | /// assert_eq!(bytes, b"FOO"); 22 | /// 23 | /// assert!(declio::from_bytes::(&bytes).is_ok()); 24 | /// assert!(declio::from_bytes::(&bytes).is_err()); 25 | /// ``` 26 | #[macro_export] 27 | macro_rules! magic_bytes { 28 | ($($(#[$attr:meta])* $vis:vis $name:ident($bytes:expr);)*) => {$( 29 | $(#[$attr])* 30 | $vis struct $name; 31 | 32 | impl $crate::Encode<()> for $name { 33 | fn encode(&self, _ctx: (), writer: &mut W) -> Result<(), $crate::Error> 34 | where 35 | W: std::io::Write, 36 | { 37 | ($bytes).encode((), writer) 38 | } 39 | } 40 | 41 | impl $crate::Decode<()> for $name { 42 | fn decode(_ctx: (), reader: &mut R) -> Result 43 | where 44 | R: std::io::Read, 45 | { 46 | let bytes: [u8; ($bytes).len()] = $crate::Decode::decode((), reader)?; 47 | if &bytes != $bytes { 48 | return Err($crate::Error::new(format!( 49 | "magic bytes mismatch: expected {:x?}, got {:x?}", 50 | $bytes, bytes, 51 | ))); 52 | } 53 | Ok(Self) 54 | } 55 | } 56 | )*} 57 | } 58 | -------------------------------------------------------------------------------- /declio/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Utilities that aren't part of the "core" of declio, but may be useful in reducing boilerplate. 2 | 3 | use crate::ctx::{Endian, Len}; 4 | use crate::{Decode, Encode, Error}; 5 | 6 | #[doc(inline)] 7 | pub use crate::magic_bytes; 8 | 9 | macro_rules! endian_wrappers { 10 | ($($(#[$attr:meta])* $name:ident: $endian:expr,)*) => {$( 11 | $(#[$attr])* 12 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 13 | pub struct $name(pub T); 14 | 15 | impl Encode<()> for $name 16 | where 17 | T: Encode, 18 | { 19 | fn encode(&self, _ctx: (), writer: &mut W) -> Result<(), Error> 20 | where 21 | W: std::io::Write, 22 | { 23 | self.0.encode($endian, writer) 24 | } 25 | } 26 | 27 | impl Decode<()> for $name 28 | where 29 | T: Decode, 30 | { 31 | fn decode(_ctx: (), reader: &mut R) -> Result 32 | where 33 | R: std::io::Read, 34 | { 35 | T::decode($endian, reader).map(Self) 36 | } 37 | } 38 | 39 | impl From for $name { 40 | fn from(value: T) -> Self { 41 | Self(value) 42 | } 43 | } 44 | 45 | /* NB: Orphan rules prohibit something like this: 46 | impl From<$name> for T { 47 | fn from(wrapper: $name) -> Self { 48 | wrapper.0 49 | } 50 | } 51 | */ 52 | 53 | impl $name { 54 | /// Unwraps and returns the inner `T` value. 55 | pub fn into_inner(self) -> T { 56 | self.0 57 | } 58 | } 59 | )*} 60 | } 61 | 62 | endian_wrappers! { 63 | /// Little-endian wrapper type for primitives. 64 | /// 65 | /// Encodes and decodes the inner type in little-endian format. 66 | /// 67 | /// # Example 68 | /// 69 | /// ``` 70 | /// use declio::Encode; 71 | /// use declio::util::LittleEndian; 72 | /// 73 | /// type Uint = LittleEndian; 74 | /// 75 | /// let x: Uint = 0xdeadbeef.into(); 76 | /// 77 | /// let mut bytes = Vec::new(); 78 | /// x.encode((), &mut bytes).unwrap(); 79 | /// 80 | /// assert_eq!(bytes, &[0xef, 0xbe, 0xad, 0xde]); 81 | /// ``` 82 | LittleEndian: Endian::Little, 83 | 84 | /// Big-endian wrapper type for primitives. 85 | /// 86 | /// Encodes and decodes the inner type in big-endian format. 87 | /// 88 | /// # Example 89 | /// 90 | /// ``` 91 | /// use declio::Encode; 92 | /// use declio::util::BigEndian; 93 | /// 94 | /// type Uint = BigEndian; 95 | /// 96 | /// let x: Uint = 0xdeadbeef.into(); 97 | /// 98 | /// let mut bytes = Vec::new(); 99 | /// x.encode((), &mut bytes).unwrap(); 100 | /// 101 | /// assert_eq!(bytes, &[0xde, 0xad, 0xbe, 0xef]); 102 | /// ``` 103 | BigEndian: Endian::Big, 104 | } 105 | 106 | /// Helper module alternative to [`Utf8`], for use in derive macros. 107 | /// 108 | /// # Examples 109 | /// 110 | /// ``` 111 | /// use declio::{Encode, Decode}; 112 | /// use declio::ctx::{Endian, Len}; 113 | /// use declio::util::utf8; 114 | /// use std::convert::TryInto; 115 | /// 116 | /// #[derive(Debug, PartialEq, Encode, Decode)] 117 | /// pub struct Text { 118 | /// #[declio(ctx = "Endian::Big")] 119 | /// len: u32, 120 | /// 121 | /// // Note here, we are using `with = "utf8"` instead of a `Utf8` wrapper type. 122 | /// #[declio(with = "utf8", ctx = "Len((*len).try_into()?)")] 123 | /// value: String, 124 | /// } 125 | /// 126 | /// let value = String::from("Hello World"); 127 | /// let text = Text { 128 | /// len: value.len().try_into().unwrap(), 129 | /// value, 130 | /// }; 131 | /// 132 | /// let mut bytes = Vec::new(); 133 | /// text.encode((), &mut bytes).unwrap(); 134 | /// assert_eq!(bytes, b"\x00\x00\x00\x0bHello World"); 135 | /// 136 | /// let mut decoder = bytes.as_slice(); 137 | /// let decoded = Text::decode((), &mut decoder).unwrap(); 138 | /// assert_eq!(decoded, text); 139 | /// 140 | /// ``` 141 | pub mod utf8 { 142 | use crate::{Decode, Encode, Error}; 143 | 144 | #[allow(missing_docs)] 145 | pub fn encode(string: &S, ctx: Ctx, writer: &mut W) -> Result<(), Error> 146 | where 147 | S: AsRef, 148 | [u8]: Encode, 149 | W: std::io::Write, 150 | { 151 | string.as_ref().as_bytes().encode(ctx, writer) 152 | } 153 | 154 | #[allow(missing_docs)] 155 | pub fn decode(ctx: Ctx, reader: &mut R) -> Result 156 | where 157 | Vec: Decode, 158 | R: std::io::Read, 159 | { 160 | let bytes: Vec = Decode::decode(ctx, reader)?; 161 | let string = String::from_utf8(bytes)?; 162 | Ok(string) 163 | } 164 | } 165 | 166 | /// UTF-8 wrapper type for strings. 167 | /// 168 | /// Encodes and decodes strings as a UTF-8 byte string. Like other sequence types, decoding 169 | /// requires a [`Len`] context value, to specify how many bytes should be read. 170 | /// 171 | /// # Examples 172 | /// 173 | /// ``` 174 | /// use declio::{Encode, Decode}; 175 | /// use declio::ctx::{Endian, Len}; 176 | /// use declio::util::Utf8; 177 | /// use std::convert::TryInto; 178 | /// 179 | /// #[derive(Debug, PartialEq, Encode, Decode)] 180 | /// pub struct Text { 181 | /// #[declio(ctx = "Endian::Big")] 182 | /// len: u32, 183 | /// #[declio(ctx = "Len((*len).try_into()?)")] 184 | /// value: Utf8, 185 | /// } 186 | /// 187 | /// let value = String::from("Hello World"); 188 | /// let text = Text { 189 | /// len: value.len().try_into().unwrap(), 190 | /// value: Utf8(value), 191 | /// }; 192 | /// 193 | /// let mut bytes = Vec::new(); 194 | /// text.encode((), &mut bytes).unwrap(); 195 | /// assert_eq!(bytes, b"\x00\x00\x00\x0bHello World"); 196 | /// 197 | /// let mut decoder = bytes.as_slice(); 198 | /// let decoded = Text::decode((), &mut decoder).unwrap(); 199 | /// assert_eq!(decoded, text); 200 | /// 201 | /// ``` 202 | #[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 203 | pub struct Utf8(pub String); 204 | 205 | impl Encode for Utf8 { 206 | fn encode(&self, ctx: Len, writer: &mut W) -> Result<(), Error> 207 | where 208 | W: std::io::Write, 209 | { 210 | utf8::encode(&self.0, ctx, writer) 211 | } 212 | } 213 | 214 | impl Encode<()> for Utf8 { 215 | fn encode(&self, _ctx: (), writer: &mut W) -> Result<(), Error> 216 | where 217 | W: std::io::Write, 218 | { 219 | utf8::encode(&self.0, ((),), writer) 220 | } 221 | } 222 | 223 | impl Decode for Utf8 { 224 | fn decode(ctx: Len, reader: &mut R) -> Result 225 | where 226 | R: std::io::Read, 227 | { 228 | utf8::decode(ctx, reader).map(Self) 229 | } 230 | } 231 | 232 | impl From for Utf8 { 233 | fn from(value: String) -> Self { 234 | Self(value) 235 | } 236 | } 237 | 238 | impl From<&str> for Utf8 { 239 | fn from(value: &str) -> Self { 240 | value.to_string().into() 241 | } 242 | } 243 | 244 | impl From for String { 245 | fn from(wrapper: Utf8) -> Self { 246 | wrapper.0 247 | } 248 | } 249 | 250 | /// Helper module alternative to [`ZeroOne`], for use in derive macros. 251 | /// 252 | /// # Examples 253 | /// 254 | /// ``` 255 | /// use declio::{Encode, Decode}; 256 | /// use declio::util::zero_one; 257 | /// 258 | /// #[derive(Debug, PartialEq, Encode, Decode)] 259 | /// struct MyBoolean { 260 | /// // Note here, we are using `with = "zero_one"` instead of a `ZeroOne` wrapper type. 261 | /// #[declio(with = "zero_one")] 262 | /// value: bool, 263 | /// } 264 | /// 265 | /// let value = MyBoolean { value: true }; 266 | /// 267 | /// let mut bytes = Vec::new(); 268 | /// value.encode((), &mut bytes).unwrap(); 269 | /// assert_eq!(bytes, b"\x01"); 270 | /// 271 | /// let mut decoder = bytes.as_slice(); 272 | /// let decoded = MyBoolean::decode((), &mut decoder).unwrap(); 273 | /// assert_eq!(decoded, value); 274 | /// ``` 275 | pub mod zero_one { 276 | use crate::{Decode, Encode, Error}; 277 | 278 | #[allow(missing_docs)] 279 | pub fn encode(b: &bool, _ctx: (), writer: &mut W) -> Result<(), Error> 280 | where 281 | W: std::io::Write, 282 | { 283 | let byte: u8 = match b { 284 | false => 0, 285 | true => 1, 286 | }; 287 | byte.encode((), writer) 288 | } 289 | 290 | #[allow(missing_docs)] 291 | pub fn decode(_ctx: (), reader: &mut R) -> Result 292 | where 293 | R: std::io::Read, 294 | { 295 | let byte: u8 = Decode::decode((), reader)?; 296 | match byte { 297 | 0 => Ok(false), 298 | 1 => Ok(true), 299 | _ => Err(Error::new(format!( 300 | "invalid byte value for boolean: expected 0 or 1, got {:?}", 301 | byte 302 | ))), 303 | } 304 | } 305 | } 306 | 307 | /// Zero-one wrapper type for booleans. 308 | /// 309 | /// Encodes and decodes booleans as a single byte, either a zero `0` for `false`, or a one `1` for 310 | /// true. 311 | /// 312 | /// # Examples 313 | /// 314 | /// ``` 315 | /// use declio::{Encode, Decode}; 316 | /// use declio::util::ZeroOne; 317 | /// 318 | /// let value = ZeroOne(true); 319 | /// 320 | /// let mut bytes = Vec::new(); 321 | /// value.encode((), &mut bytes).unwrap(); 322 | /// assert_eq!(bytes, b"\x01"); 323 | /// 324 | /// let mut decoder = bytes.as_slice(); 325 | /// let decoded = ZeroOne::decode((), &mut decoder).unwrap(); 326 | /// assert_eq!(decoded, value); 327 | /// ``` 328 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 329 | pub struct ZeroOne(pub bool); 330 | 331 | impl Encode<()> for ZeroOne { 332 | fn encode(&self, _ctx: (), writer: &mut W) -> Result<(), Error> 333 | where 334 | W: std::io::Write, 335 | { 336 | zero_one::encode(&self.0, (), writer) 337 | } 338 | } 339 | 340 | impl Decode<()> for ZeroOne { 341 | fn decode(_ctx: (), reader: &mut R) -> Result 342 | where 343 | R: std::io::Read, 344 | { 345 | zero_one::decode((), reader).map(Self) 346 | } 347 | } 348 | 349 | impl From for ZeroOne { 350 | fn from(value: bool) -> Self { 351 | Self(value) 352 | } 353 | } 354 | 355 | impl From for bool { 356 | fn from(wrapper: ZeroOne) -> Self { 357 | wrapper.0 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /declio/tests/derive.rs: -------------------------------------------------------------------------------- 1 | use declio::util::BigEndian; 2 | use declio::{ctx, Decode, Encode}; 3 | use std::fmt::Debug; 4 | use std::io; 5 | 6 | #[derive(Debug, PartialEq, Encode, Decode)] 7 | struct UnitStruct; 8 | 9 | #[derive(Debug, PartialEq, Encode, Decode)] 10 | struct TupleStruct(u8, BigEndian); 11 | 12 | #[derive(Debug, PartialEq, Encode, Decode)] 13 | struct Struct { 14 | x: u8, 15 | y: BigEndian, 16 | } 17 | 18 | #[derive(Debug, PartialEq, Encode, Decode)] 19 | #[declio(id_type = "u8")] 20 | enum Enum { 21 | #[declio(id = "0")] 22 | Unit, 23 | #[declio(id = "1")] 24 | Tuple(u8, BigEndian), 25 | #[declio(id = "2")] 26 | Struct { x: u8, y: BigEndian }, 27 | } 28 | 29 | #[derive(Debug, PartialEq, Encode, Decode)] 30 | struct With { 31 | #[declio(with = "little_endian")] 32 | y: u32, 33 | } 34 | 35 | #[derive(Debug, PartialEq, Encode, Decode)] 36 | struct WithSeparate { 37 | #[declio( 38 | encode_with = "little_endian::encode", 39 | decode_with = "little_endian::decode" 40 | )] 41 | y: u32, 42 | } 43 | 44 | #[derive(Debug, PartialEq, Encode, Decode)] 45 | struct FieldCtx { 46 | #[declio(ctx = "ctx::Endian::Little")] 47 | y: u32, 48 | } 49 | 50 | #[derive(Debug, PartialEq, Encode, Decode)] 51 | #[declio(ctx = "endian: ctx::Endian")] 52 | struct ContainerCtx { 53 | #[declio(ctx = "endian")] 54 | y: u32, 55 | } 56 | 57 | #[derive(Debug, PartialEq, Encode, Decode)] 58 | #[declio(id_type = "u16", id_ctx = "ctx::Endian::Little")] 59 | enum IdCtx { 60 | #[declio(id = "1")] 61 | Bar, 62 | } 63 | 64 | #[derive(Debug, PartialEq, Encode, Decode)] 65 | #[declio(ctx = "id: u8", id_expr = "id")] 66 | enum IdExpr { 67 | #[declio(id = "1")] 68 | Bar, 69 | #[declio(id = "2")] 70 | Baz, 71 | } 72 | 73 | #[derive(Debug, PartialEq, Encode, Decode)] 74 | struct SkipIf { 75 | x: u8, 76 | #[declio(skip_if = "*x == 8")] 77 | y: Option>, 78 | } 79 | 80 | mod little_endian { 81 | use super::*; 82 | 83 | pub fn encode(x: &u32, _: (), writer: &mut W) -> Result<(), declio::Error> 84 | where 85 | W: io::Write, 86 | { 87 | x.encode(ctx::Endian::Little, writer) 88 | } 89 | 90 | pub fn decode(_: (), reader: &mut R) -> Result 91 | where 92 | R: io::Read, 93 | { 94 | u32::decode(ctx::Endian::Little, reader) 95 | } 96 | } 97 | 98 | fn test_encode(input: T, expected: &[u8], ctx: Ctx) 99 | where 100 | T: Encode, 101 | { 102 | let output = declio::to_bytes_with_context(&input, ctx).unwrap(); 103 | assert_eq!(output, expected); 104 | } 105 | 106 | fn test_decode(input: &[u8], expected: &T, ctx: Ctx) 107 | where 108 | T: Decode + Debug + PartialEq, 109 | { 110 | let output: T = declio::from_bytes_with_context(input, ctx).unwrap(); 111 | assert_eq!(output, *expected); 112 | } 113 | 114 | fn test_bidir(val: T, bytes: &[u8]) 115 | where 116 | T: Encode + Decode + Debug + PartialEq, 117 | { 118 | test_bidir_ctx(val, bytes, ()); 119 | } 120 | 121 | fn test_bidir_ctx(val: T, bytes: &[u8], ctx: Ctx) 122 | where 123 | T: Encode + Decode + Debug + PartialEq, 124 | Ctx: Copy, 125 | { 126 | test_encode(&val, bytes, ctx); 127 | test_decode(bytes, &val, ctx); 128 | } 129 | 130 | #[test] 131 | fn unit_struct() { 132 | test_bidir(UnitStruct, &[]); 133 | } 134 | 135 | #[test] 136 | fn tuple_struct() { 137 | test_bidir( 138 | TupleStruct(0xab, 0xdeadbeef.into()), 139 | &[0xab, 0xde, 0xad, 0xbe, 0xef], 140 | ); 141 | } 142 | 143 | #[test] 144 | fn struct_encode() { 145 | test_bidir( 146 | Struct { 147 | x: 0xab, 148 | y: 0xdeadbeef.into(), 149 | }, 150 | &[0xab, 0xde, 0xad, 0xbe, 0xef], 151 | ); 152 | } 153 | 154 | #[test] 155 | fn unit_enum() { 156 | test_bidir(Enum::Unit, &[0x00]); 157 | } 158 | 159 | #[test] 160 | fn tuple_enum() { 161 | test_bidir( 162 | Enum::Tuple(0xab, 0xdeadbeef.into()), 163 | &[0x01, 0xab, 0xde, 0xad, 0xbe, 0xef], 164 | ); 165 | } 166 | 167 | #[test] 168 | fn struct_enum() { 169 | test_bidir( 170 | Enum::Struct { 171 | x: 0xab, 172 | y: 0xdeadbeef.into(), 173 | }, 174 | &[0x02, 0xab, 0xde, 0xad, 0xbe, 0xef], 175 | ); 176 | } 177 | 178 | #[test] 179 | fn with() { 180 | test_bidir(With { y: 0xdeadbeef }, &[0xef, 0xbe, 0xad, 0xde]); 181 | } 182 | 183 | #[test] 184 | fn with_separate() { 185 | test_bidir(WithSeparate { y: 0xdeadbeef }, &[0xef, 0xbe, 0xad, 0xde]); 186 | } 187 | 188 | #[test] 189 | fn field_ctx() { 190 | test_bidir(FieldCtx { y: 0xdeadbeef }, &[0xef, 0xbe, 0xad, 0xde]); 191 | } 192 | 193 | #[test] 194 | fn container_ctx() { 195 | test_bidir_ctx( 196 | ContainerCtx { y: 0xdeadbeef }, 197 | &[0xef, 0xbe, 0xad, 0xde], 198 | ctx::Endian::Little, 199 | ); 200 | } 201 | 202 | #[test] 203 | fn id_ctx() { 204 | test_bidir(IdCtx::Bar, &[0x01, 0x00]); 205 | } 206 | 207 | #[test] 208 | fn id_expr() { 209 | test_bidir_ctx(IdExpr::Baz, &[], 2u8); 210 | } 211 | 212 | #[test] 213 | fn skip_if() { 214 | test_bidir(SkipIf { x: 8, y: None }, &[0x08]); 215 | test_bidir( 216 | SkipIf { 217 | x: 7, 218 | y: Some(2.into()), 219 | }, 220 | &[0x07, 0x00, 0x00, 0x00, 0x02], 221 | ); 222 | } 223 | -------------------------------------------------------------------------------- /declio_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "declio_derive" 3 | version = "0.2.0" 4 | authors = ["Adam Gausmann "] 5 | edition = "2018" 6 | description = "Derive macros for declio" 7 | repository = "https://github.com/agausmann/declio" 8 | license = "MIT" 9 | keywords = ["binary", "io", "declarative", "bidirectional"] 10 | categories = ["encoding", "parsing"] 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | darling = "0.13" 17 | proc-macro2 = "1.0" 18 | quote = "1.0" 19 | syn = "1.0" 20 | -------------------------------------------------------------------------------- /declio_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use darling::{ast, Error, FromDeriveInput, FromField, FromMeta, FromVariant}; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote, ToTokens}; 4 | use syn::punctuated::Punctuated; 5 | use syn::{parse_macro_input, parse_quote, DeriveInput, Token}; 6 | 7 | #[proc_macro_derive(Encode, attributes(declio))] 8 | pub fn derive_encode(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 9 | let input = parse_macro_input!(input as DeriveInput); 10 | ContainerReceiver::from_derive_input(&input) 11 | .and_then(|receiver| receiver.validate()) 12 | .map(|data| data.encode_impl().into_token_stream()) 13 | .unwrap_or_else(|error| error.write_errors()) 14 | .into() 15 | } 16 | 17 | #[proc_macro_derive(Decode, attributes(declio))] 18 | pub fn derive_decode(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 19 | let input = parse_macro_input!(input as DeriveInput); 20 | ContainerReceiver::from_derive_input(&input) 21 | .and_then(|receiver| receiver.validate()) 22 | .map(|data| data.decode_impl().into_token_stream()) 23 | .unwrap_or_else(|error| error.write_errors()) 24 | .into() 25 | } 26 | 27 | #[derive(FromDeriveInput)] 28 | #[darling(attributes(declio))] 29 | struct ContainerReceiver { 30 | ident: syn::Ident, 31 | generics: syn::Generics, 32 | data: ast::Data, 33 | 34 | #[darling(default)] 35 | crate_path: Option, 36 | 37 | #[darling(default)] 38 | ctx: Asym, 39 | 40 | #[darling(default)] 41 | id_expr: Asym, 42 | 43 | #[darling(default)] 44 | id_type: Option, 45 | 46 | #[darling(default)] 47 | id_ctx: Asym, 48 | } 49 | 50 | struct ContainerData { 51 | ident: syn::Ident, 52 | generics: syn::Generics, 53 | crate_path: syn::Path, 54 | encode_ctx_pat: TokenStream, 55 | decode_ctx_pat: TokenStream, 56 | encode_ctx_type: TokenStream, 57 | decode_ctx_type: TokenStream, 58 | id_encode_ctx: TokenStream, 59 | id_decode_ctx: TokenStream, 60 | id_encoder: Option, 61 | id_decoder: Option, 62 | id_check_expr: Option, 63 | id_decode_expr: Option, 64 | variants: Vec, 65 | } 66 | 67 | impl ContainerReceiver { 68 | fn validate(&self) -> Result { 69 | let mut errors = Vec::new(); 70 | 71 | let ident = self.ident.clone(); 72 | let generics = self.generics.clone(); 73 | let crate_path = self 74 | .crate_path 75 | .clone() 76 | .unwrap_or_else(|| parse_quote!(declio)); 77 | 78 | let mut parse_ctx = |arg: Option<&syn::LitStr>| match arg { 79 | None => (quote!(_), quote!(())), 80 | Some(lit) => { 81 | let parts: Punctuated = 82 | match lit.parse_with(Punctuated::parse_terminated) { 83 | Ok(punct) => punct, 84 | Err(error) => { 85 | errors.push(from_syn_error(error)); 86 | Punctuated::new() 87 | } 88 | }; 89 | 90 | let parts: Vec<_> = parts 91 | .into_iter() 92 | .flat_map(|fn_arg| match fn_arg { 93 | syn::FnArg::Typed(pat_type) => Some((pat_type.pat, pat_type.ty)), 94 | _ => { 95 | errors.push( 96 | Error::custom("expected name and type, like `foo: i64`") 97 | .with_span(&fn_arg), 98 | ); 99 | None 100 | } 101 | }) 102 | .collect(); 103 | 104 | let pats = parts.iter().map(|(pat, _)| pat); 105 | let types = parts.iter().map(|(_, ty)| ty); 106 | 107 | // Special case: single context variable gets to be not-a-tuple. 108 | if parts.len() == 1 { 109 | (quote!( #( #pats )* ), quote!( #( #types )* )) 110 | } else { 111 | (quote!( ( #( #pats , )* ) ), quote!( ( #( #types , )* ) )) 112 | } 113 | } 114 | }; 115 | 116 | let (encode_ctx_pat, encode_ctx_type) = parse_ctx(self.ctx.encode()); 117 | let (decode_ctx_pat, decode_ctx_type) = parse_ctx(self.ctx.decode()); 118 | 119 | let (id_encoder, id_decoder, id_decode_expr) = match (&self.id_expr.decode(), &self.id_type) 120 | { 121 | (None, None) => (None, None, Some(quote!(()))), 122 | (Some(lit), None) => { 123 | let expr = match lit.parse() { 124 | Ok(expr) => expr, 125 | Err(error) => { 126 | errors.push(from_syn_error(error)); 127 | quote!(unreachable!("compile error")) 128 | } 129 | }; 130 | (None, None, Some(expr)) 131 | } 132 | (None, Some(lit)) => { 133 | let ty = match lit.parse() { 134 | Ok(ty) => ty, 135 | Err(error) => { 136 | errors.push(from_syn_error(error)); 137 | quote!(()) 138 | } 139 | }; 140 | ( 141 | Some(quote!( <#ty as #crate_path::Encode<_>>::encode )), 142 | Some(quote!( <#ty as #crate_path::Decode<_>>::decode )), 143 | None, 144 | ) 145 | } 146 | (Some(..), Some(..)) => { 147 | errors.push(Error::custom( 148 | "`id_expr(decode = \"...\")` and `id_type` are incompatible with each other", 149 | )); 150 | (None, None, None) 151 | } 152 | }; 153 | let id_check_expr = match &self.id_expr.encode() { 154 | Some(lit) => { 155 | let expr = match lit.parse() { 156 | Ok(expr) => expr, 157 | Err(error) => { 158 | errors.push(from_syn_error(error)); 159 | quote!(unreachable!("compile error")) 160 | } 161 | }; 162 | Some(expr) 163 | } 164 | None => None, 165 | }; 166 | 167 | let mut parse_id_ctx = |arg: Option<&syn::LitStr>| match arg { 168 | None => quote!(()), 169 | Some(lit) => match lit.parse() { 170 | Ok(expr) => expr, 171 | Err(error) => { 172 | errors.push(from_syn_error(error)); 173 | quote!(unreachable!("compile error")) 174 | } 175 | }, 176 | }; 177 | 178 | let id_encode_ctx = parse_id_ctx(self.id_ctx.encode()); 179 | let id_decode_ctx = parse_id_ctx(self.id_ctx.decode()); 180 | 181 | if self.data.is_struct() && self.id_expr.is_some() { 182 | errors.push(Error::unknown_field("id_expr")); 183 | } 184 | if self.data.is_struct() && self.id_type.is_some() { 185 | errors.push(Error::unknown_field("id_type")); 186 | } 187 | if self.data.is_enum() && self.id_expr.is_none() && self.id_type.is_none() { 188 | errors.push(Error::custom( 189 | "either `id_expr` or `id_type` is required for enums", 190 | )); 191 | } 192 | 193 | let variants = match &self.data { 194 | ast::Data::Enum(variants) => variants 195 | .iter() 196 | .flat_map(|variant| match variant.validate(&crate_path) { 197 | Ok(data) => Some(data), 198 | Err(error) => { 199 | errors.push(error); 200 | None 201 | } 202 | }) 203 | .collect(), 204 | ast::Data::Struct(fields) => match VariantData::from_struct(fields, &crate_path) { 205 | Ok(data) => vec![data], 206 | Err(error) => { 207 | errors.push(error); 208 | vec![] 209 | } 210 | }, 211 | }; 212 | 213 | if errors.is_empty() { 214 | Ok(ContainerData { 215 | ident, 216 | generics, 217 | crate_path, 218 | encode_ctx_pat, 219 | decode_ctx_pat, 220 | encode_ctx_type, 221 | decode_ctx_type, 222 | id_encode_ctx, 223 | id_decode_ctx, 224 | id_encoder, 225 | id_decoder, 226 | id_decode_expr, 227 | id_check_expr, 228 | variants, 229 | }) 230 | } else { 231 | Err(Error::multiple(errors)) 232 | } 233 | } 234 | } 235 | 236 | impl ContainerData { 237 | fn encode_impl(&self) -> TokenStream { 238 | let Self { 239 | ident, 240 | crate_path, 241 | encode_ctx_pat, 242 | encode_ctx_type, 243 | .. 244 | } = self; 245 | let (impl_generics, ident_generics, where_clause) = self.generics.split_for_impl(); 246 | let writer_binding = quote!(__declio_writer); 247 | 248 | let variant_arm = self.variants.iter().map(|variant| { 249 | variant.encode_arm( 250 | self.id_encoder.as_ref(), 251 | &self.id_encode_ctx, 252 | self.id_check_expr.as_ref(), 253 | &self.crate_path, 254 | &writer_binding, 255 | ) 256 | }); 257 | 258 | quote! { 259 | #[allow(non_shorthand_field_patterns)] 260 | impl #impl_generics #crate_path::Encode<#encode_ctx_type> for #ident #ident_generics 261 | #where_clause 262 | { 263 | fn encode(&self, #encode_ctx_pat: #encode_ctx_type, #writer_binding: &mut W) 264 | -> Result<(), #crate_path::Error> 265 | where 266 | W: #crate_path::export::io::Write, 267 | { 268 | match self { 269 | #( #variant_arm, )* 270 | } 271 | } 272 | } 273 | } 274 | } 275 | 276 | fn decode_impl(&self) -> TokenStream { 277 | let Self { 278 | ident, 279 | crate_path, 280 | decode_ctx_pat, 281 | decode_ctx_type, 282 | .. 283 | } = self; 284 | let Self { 285 | id_decoder, 286 | id_decode_ctx, 287 | id_decode_expr, 288 | .. 289 | } = self; 290 | let (impl_generics, ident_generics, where_clause) = self.generics.split_for_impl(); 291 | let reader_binding: TokenStream = quote!(__declio_reader); 292 | 293 | let variant_arm = self 294 | .variants 295 | .iter() 296 | .map(|variant| variant.decode_arm(&self.crate_path, &reader_binding)); 297 | 298 | let id_decode_expr = match (id_decoder, id_decode_expr) { 299 | (Some(decoder), None) => quote! { 300 | #decoder(#id_decode_ctx, #reader_binding) 301 | .map_err(|e| #crate_path::Error::with_context("error decoding enum id", e))? 302 | }, 303 | (None, Some(decode_expr)) => quote!(#decode_expr), 304 | _ => unreachable!(), 305 | }; 306 | 307 | quote! { 308 | impl #impl_generics #crate_path::Decode<#decode_ctx_type> for #ident #ident_generics 309 | #where_clause 310 | { 311 | fn decode(#decode_ctx_pat: #decode_ctx_type, #reader_binding: &mut R) 312 | -> Result 313 | where 314 | R: #crate_path::export::io::Read, 315 | { 316 | match #id_decode_expr { 317 | #( #variant_arm )* 318 | _ => Err(#crate_path::Error::new("unknown id value")), 319 | } 320 | } 321 | } 322 | } 323 | } 324 | } 325 | 326 | #[derive(FromVariant)] 327 | #[darling(attributes(declio))] 328 | struct VariantReceiver { 329 | ident: syn::Ident, 330 | fields: ast::Fields, 331 | 332 | id: syn::LitStr, 333 | } 334 | 335 | struct VariantData { 336 | ident: Option, 337 | id_expr: TokenStream, 338 | id_pat: TokenStream, 339 | style: ast::Style, 340 | fields: Vec, 341 | } 342 | 343 | impl VariantReceiver { 344 | fn validate(&self, crate_path: &syn::Path) -> Result { 345 | let mut errors = Vec::new(); 346 | 347 | let ident = Some(self.ident.clone()); 348 | 349 | let id_expr = match self.id.parse() { 350 | Ok(expr) => expr, 351 | Err(error) => { 352 | errors.push(from_syn_error(error)); 353 | quote!(unreachable!("compile error")) 354 | } 355 | }; 356 | 357 | let id_pat = quote!(__declio_id if __declio_id == #id_expr); 358 | 359 | let style = self.fields.style; 360 | 361 | let fields = self 362 | .fields 363 | .iter() 364 | .enumerate() 365 | .flat_map(|(index, field)| match field.validate(crate_path, index) { 366 | Ok(field) => Some(field), 367 | Err(error) => { 368 | errors.push(error); 369 | None 370 | } 371 | }) 372 | .collect(); 373 | 374 | if errors.is_empty() { 375 | Ok(VariantData { 376 | ident, 377 | id_expr, 378 | id_pat, 379 | style, 380 | fields, 381 | }) 382 | } else { 383 | Err(Error::multiple(errors)) 384 | } 385 | } 386 | } 387 | 388 | impl VariantData { 389 | fn from_struct( 390 | fields: &ast::Fields, 391 | crate_path: &syn::Path, 392 | ) -> Result { 393 | let mut errors = Vec::new(); 394 | 395 | let ident = None; 396 | let id_expr = quote!(()); 397 | let id_pat = quote!(_); 398 | let style = fields.style; 399 | 400 | let fields = fields 401 | .iter() 402 | .enumerate() 403 | .flat_map(|(index, field)| match field.validate(crate_path, index) { 404 | Ok(field) => Some(field), 405 | Err(error) => { 406 | errors.push(error); 407 | None 408 | } 409 | }) 410 | .collect(); 411 | 412 | if errors.is_empty() { 413 | Ok(VariantData { 414 | ident, 415 | id_expr, 416 | id_pat, 417 | style, 418 | fields, 419 | }) 420 | } else { 421 | Err(Error::multiple(errors)) 422 | } 423 | } 424 | 425 | fn encode_arm( 426 | &self, 427 | id_encoder: Option<&TokenStream>, 428 | id_encode_ctx: &TokenStream, 429 | id_check_expr: Option<&TokenStream>, 430 | crate_path: &syn::Path, 431 | writer_binding: &TokenStream, 432 | ) -> TokenStream { 433 | let Self { id_expr, .. } = self; 434 | 435 | let path = match &self.ident { 436 | Some(ident) => quote!(Self::#ident), 437 | None => quote!(Self), 438 | }; 439 | 440 | let field_pat = self.fields.iter().map(|field| { 441 | let FieldData { 442 | stored_ident, 443 | public_ref_ident, 444 | .. 445 | } = field; 446 | match stored_ident { 447 | Some(stored_ident) => quote!(#stored_ident: #public_ref_ident), 448 | None => quote!(#public_ref_ident), 449 | } 450 | }); 451 | let pat_fields = match self.style { 452 | ast::Style::Tuple => quote!( ( #( #field_pat, )* ) ), 453 | ast::Style::Struct => quote!( { #( #field_pat, )* } ), 454 | ast::Style::Unit => quote!(), 455 | }; 456 | 457 | let id_check_stmt = id_check_expr.map(|check_value| { 458 | quote! { 459 | if #id_expr != #check_value { 460 | return Err(#crate_path::Error::new("id context does not match variant id")); 461 | } 462 | } 463 | }); 464 | let id_encode_stmt = id_encoder.map(|encoder| { 465 | quote! { 466 | #encoder(&(#id_expr), #id_encode_ctx, #writer_binding) 467 | .map_err(|e| #crate_path::Error::with_context("error encoding enum id", e))?; 468 | } 469 | }); 470 | 471 | let field_encode_expr = self 472 | .fields 473 | .iter() 474 | .map(|field| field.encode_expr(crate_path, writer_binding)); 475 | 476 | quote! { 477 | #path #pat_fields => { 478 | #id_check_stmt 479 | #id_encode_stmt 480 | #( #field_encode_expr; )* 481 | Ok(()) 482 | } 483 | } 484 | } 485 | 486 | fn decode_arm(&self, crate_path: &syn::Path, reader_binding: &TokenStream) -> TokenStream { 487 | let Self { id_pat, .. } = self; 488 | 489 | let private_owned_ident = self.fields.iter().map(|field| &field.private_owned_ident); 490 | let public_ref_ident = self.fields.iter().map(|field| &field.public_ref_ident); 491 | let field_decode_expr = self 492 | .fields 493 | .iter() 494 | .map(|field| field.decode_expr(crate_path, reader_binding)); 495 | 496 | let path = match &self.ident { 497 | Some(ident) => quote!(Self::#ident), 498 | None => quote!(Self), 499 | }; 500 | 501 | let field_cons = self.fields.iter().map(|field| { 502 | let FieldData { 503 | stored_ident, 504 | private_owned_ident, 505 | .. 506 | } = field; 507 | match stored_ident { 508 | Some(stored_ident) => quote!(#stored_ident: #private_owned_ident), 509 | None => quote!(#private_owned_ident), 510 | } 511 | }); 512 | let cons_fields = match self.style { 513 | ast::Style::Tuple => quote!( ( #( #field_cons, )* ) ), 514 | ast::Style::Struct => quote!( { #( #field_cons, )* } ), 515 | ast::Style::Unit => quote!(), 516 | }; 517 | 518 | quote! { 519 | #id_pat => { 520 | #( 521 | let #private_owned_ident = #field_decode_expr; 522 | #[allow(unused_variables)] 523 | let #public_ref_ident = &#private_owned_ident; 524 | )* 525 | Ok(#path #cons_fields) 526 | } 527 | } 528 | } 529 | } 530 | 531 | #[derive(FromField)] 532 | #[darling(attributes(declio))] 533 | struct FieldReceiver { 534 | ident: Option, 535 | ty: syn::Type, 536 | 537 | #[darling(default)] 538 | ctx: Asym, 539 | 540 | #[darling(default)] 541 | with: Option, 542 | 543 | #[darling(default)] 544 | encode_with: Option, 545 | 546 | #[darling(default)] 547 | decode_with: Option, 548 | 549 | #[darling(default)] 550 | skip_if: Option, 551 | } 552 | 553 | struct FieldData { 554 | stored_ident: Option, 555 | public_ref_ident: syn::Ident, 556 | private_owned_ident: syn::Ident, 557 | encode_ctx: TokenStream, 558 | decode_ctx: TokenStream, 559 | encoder: TokenStream, 560 | decoder: TokenStream, 561 | skip_if: Option, 562 | } 563 | 564 | impl FieldReceiver { 565 | fn validate(&self, crate_path: &syn::Path, index: usize) -> Result { 566 | let Self { ty, .. } = self; 567 | let mut errors = Vec::new(); 568 | 569 | let stored_ident = self.ident.clone(); 570 | let public_ref_ident = match &self.ident { 571 | Some(ident) => ident.clone(), 572 | None => format_ident!("field_{}", index), 573 | }; 574 | let private_owned_ident = format_ident!("__declio_owned_{}", public_ref_ident); 575 | 576 | let encode_ctx = match self.ctx.encode() { 577 | Some(lit) => match lit.parse() { 578 | Ok(expr) => expr, 579 | Err(err) => { 580 | errors.push(from_syn_error(err)); 581 | quote!(unreachable!("compile error")) 582 | } 583 | }, 584 | None => quote!(()), 585 | }; 586 | 587 | let decode_ctx = match self.ctx.decode() { 588 | Some(lit) => match lit.parse() { 589 | Ok(expr) => expr, 590 | Err(err) => { 591 | errors.push(from_syn_error(err)); 592 | quote!(unreachable!("compile error")) 593 | } 594 | }, 595 | None => quote!(()), 596 | }; 597 | 598 | let encoder = match (&self.encode_with, &self.with) { 599 | (None, None) => quote!(<#ty as #crate_path::Encode<_>>::encode), 600 | (Some(encode_with), None) => quote!(#encode_with), 601 | (None, Some(with)) => quote!(#with::encode), 602 | _ => { 603 | errors.push(Error::custom( 604 | "`encode_with` and `with` are incompatible with each other", 605 | )); 606 | quote!(__compile_error_throwaway) 607 | } 608 | }; 609 | 610 | let decoder = match (&self.decode_with, &self.with) { 611 | (None, None) => quote!(<#ty as #crate_path::Decode<_>>::decode), 612 | (Some(decode_with), None) => quote!(#decode_with), 613 | (None, Some(with)) => quote!(#with::decode), 614 | _ => { 615 | errors.push(Error::custom( 616 | "`decode_with` and `with` are incompatible with each other", 617 | )); 618 | quote!(__compile_error_throwaway) 619 | } 620 | }; 621 | 622 | let skip_if = match &self.skip_if { 623 | Some(lit) => match lit.parse() { 624 | Ok(expr) => Some(expr), 625 | Err(error) => { 626 | errors.push(from_syn_error(error)); 627 | Some(quote!(unreachable!("compile error"))) 628 | } 629 | }, 630 | None => None, 631 | }; 632 | 633 | if errors.is_empty() { 634 | Ok(FieldData { 635 | stored_ident, 636 | public_ref_ident, 637 | private_owned_ident, 638 | encode_ctx, 639 | decode_ctx, 640 | encoder, 641 | decoder, 642 | skip_if, 643 | }) 644 | } else { 645 | Err(Error::multiple(errors)) 646 | } 647 | } 648 | } 649 | 650 | impl FieldData { 651 | fn encode_expr(&self, crate_path: &syn::Path, writer_binding: &TokenStream) -> TokenStream { 652 | let Self { 653 | public_ref_ident, 654 | encoder, 655 | encode_ctx, 656 | .. 657 | } = self; 658 | let error_context = format!("error encoding field {}", public_ref_ident); 659 | let raw_encoder = quote! { 660 | #encoder(#public_ref_ident, #encode_ctx, #writer_binding) 661 | .map_err(|e| #crate_path::Error::with_context(#error_context, e))? 662 | }; 663 | match &self.skip_if { 664 | Some(skip_if) => quote! { 665 | if #skip_if { 666 | () 667 | } else { 668 | #raw_encoder 669 | } 670 | }, 671 | None => raw_encoder, 672 | } 673 | } 674 | 675 | fn decode_expr(&self, crate_path: &syn::Path, reader_binding: &TokenStream) -> TokenStream { 676 | let Self { 677 | public_ref_ident, 678 | decode_ctx, 679 | decoder, 680 | .. 681 | } = self; 682 | let error_context = format!("error decoding field {}", public_ref_ident); 683 | let raw_decoder = quote! { 684 | #decoder(#decode_ctx, #reader_binding) 685 | .map_err(|e| #crate_path::Error::with_context(#error_context, e))? 686 | }; 687 | match &self.skip_if { 688 | Some(skip_if) => quote! { 689 | if #skip_if { 690 | Default::default() 691 | } else { 692 | #raw_decoder 693 | } 694 | }, 695 | None => raw_decoder, 696 | } 697 | } 698 | } 699 | 700 | enum Asym { 701 | Single(T), 702 | Multi { 703 | encode: Option, 704 | decode: Option, 705 | }, 706 | } 707 | 708 | impl Asym { 709 | fn is_some(&self) -> bool { 710 | match self { 711 | Self::Single(..) => true, 712 | Self::Multi { encode, decode } => encode.is_some() || decode.is_some(), 713 | } 714 | } 715 | 716 | fn is_none(&self) -> bool { 717 | !self.is_some() 718 | } 719 | 720 | fn encode(&self) -> Option<&T> { 721 | match self { 722 | Self::Single(val) => Some(val), 723 | Self::Multi { encode, .. } => encode.as_ref(), 724 | } 725 | } 726 | 727 | fn decode(&self) -> Option<&T> { 728 | match self { 729 | Self::Single(val) => Some(val), 730 | Self::Multi { decode, .. } => decode.as_ref(), 731 | } 732 | } 733 | } 734 | 735 | impl FromMeta for Asym 736 | where 737 | T: FromMeta + std::fmt::Debug, 738 | { 739 | fn from_meta(item: &syn::Meta) -> Result { 740 | match item { 741 | syn::Meta::List(value) => { 742 | Self::from_list(&value.nested.iter().cloned().collect::>()) 743 | } 744 | _ => T::from_meta(item).map(Self::Single), 745 | } 746 | } 747 | 748 | fn from_list(items: &[syn::NestedMeta]) -> Result { 749 | let mut encode = None; 750 | let mut decode = None; 751 | 752 | let encode_path: syn::Path = parse_quote!(encode); 753 | let decode_path: syn::Path = parse_quote!(decode); 754 | 755 | for item in items { 756 | match item { 757 | syn::NestedMeta::Meta(meta) => match meta.path() { 758 | path if *path == encode_path => { 759 | if encode.is_none() { 760 | encode = Some(T::from_meta(meta)?); 761 | } else { 762 | return Err(Error::duplicate_field_path(path)); 763 | } 764 | } 765 | path if *path == decode_path => { 766 | if decode.is_none() { 767 | decode = Some(T::from_meta(meta)?); 768 | } else { 769 | return Err(Error::duplicate_field_path(path)); 770 | } 771 | } 772 | other => return Err(Error::unknown_field_path(other)), 773 | }, 774 | syn::NestedMeta::Lit(..) => return Err(Error::unsupported_format("literal")), 775 | } 776 | } 777 | Ok(Self::Multi { encode, decode }) 778 | } 779 | } 780 | 781 | impl Default for Asym { 782 | fn default() -> Self { 783 | Self::Multi { 784 | encode: None, 785 | decode: None, 786 | } 787 | } 788 | } 789 | 790 | fn from_syn_error(err: syn::Error) -> Error { 791 | Error::custom(&err).with_span(&err.span()) 792 | } 793 | --------------------------------------------------------------------------------