fixed_size/lib.rs
1//! struct attribute to set fixed sizes for certain fields which are normally dynamic
2//!
3//! This is useful when generating structs from protobufs using prost and
4//! also using those structs with a serde format that requires fixed length strings
5//!
6//! # Example
7//! ```protoc
8//! syntax = "proto3";
9//! message Foo
10//! {
11//! string my_string = 1;
12//! }
13//! ````
14//! Prost will create use [`String`] for the my_string field. If you have a binary format requiring
15//! exactly 4 characters in a string this will be difficult to handle in a generic manner. If you add
16//! the `#[fixed(my_string=4)]` attribute then you'll end up with a `ArrayString::<4>` instead.
17//!
18//! By default, ArrayString will be used but this can be overridden with `#[fixed(typ=MyString, thestring=4)]`
19//! The typical use is
20//! ```rust
21//! use arrayvec::ArrayString;
22//!
23//! struct MyString<const CAP: usize>(ArrayString<CAP>);
24//!
25//! impl<const CAP: usize> AsRef<ArrayString<CAP>> for MyString<CAP> {
26//! fn as_ref(&self) -> &ArrayString<CAP> {
27//! &self.0
28//! }
29//!}
30//! impl<const CAP: usize> serde::Serialize for MyString<CAP> {
31//! fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
32//! where S: serde::Serializer
33//! {
34//! // specialized serialize to override ArrayString's conversion to &str
35//! todo!()
36//! }
37//! }
38//! // More impls, probably AsMut, etc.
39//! ```
40//!
41//! ```rust
42//! use arrayvec::ArrayString;
43//! use fixed_size::fixed;
44//!
45//! #[fixed(my_string=4)]
46//! #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
47//! struct Foo {
48//! my_string: String,
49//! }
50//!
51//! let foo = Foo { my_string: ArrayString::<4>::from("abcd").unwrap() };
52//! // bincode actually supports var length strings but it's just used as an example and test
53//! let encoded = bincode::serialize(&foo).unwrap();
54//! let decoded: Foo = bincode::deserialize(&encoded[..]).unwrap();
55//! assert_eq!(foo, decoded);
56//! ```
57//!
58//! Adding fewer than 4 characters to my_string will 0 pad the value. Adding more than
59//! 4 characters will result in an error.
60
61extern crate proc_macro;
62
63use proc_macro::TokenStream;
64use proc_macro2::Span;
65use quote::quote;
66use std::collections::HashMap;
67use syn::{parse::{Parse, ParseStream, Result}, Token, punctuated::Punctuated,
68 fold::Fold, Expr, ExprAssign, Ident, LitInt, Lit, parse_macro_input,
69 ItemStruct, Type, Field, parse_quote};
70
71type MapType = HashMap<Ident, LitInt>;
72struct Args {
73 size_map: MapType,
74 typ: Ident,
75}
76
77const ERRMSG: &str = "Must specify an Ident=Int or typ=Structname";
78
79impl Parse for Args {
80 fn parse(input: ParseStream) -> Result<Self> {
81 let vars = Punctuated::<ExprAssign, Token![,]>::parse_terminated(input)?;
82 let mut size_map = MapType::new();
83 let mut typ = Ident::new("ArrayString", Span::mixed_site());
84 for var in vars.into_iter() {
85 match (&*var.left, &*var.right) {
86 (Expr::Path(p), Expr::Lit(v)) => {
87 let key = p.path.get_ident().unwrap();
88 if let Lit::Int(num) = &v.lit {
89 size_map.insert(key.clone(), num.clone());
90 } else {
91 return Err(input.error(ERRMSG));
92 }
93 },
94 (Expr::Path(p), Expr::Path(v)) => {
95 let key = p.path.get_ident().unwrap();
96 if key.to_string() != "typ" {
97 return Err(input.error(ERRMSG));
98 }
99 if let Some(val) = v.path.get_ident() {
100 typ = val.clone();
101 } else {
102 return Err(input.error(ERRMSG));
103 }
104 }
105 (_, _) => {
106 return Err(input.error(ERRMSG));
107 }
108 }
109 }
110
111 Ok(Args { size_map, typ })
112 }
113}
114
115impl Fold for Args {
116 fn fold_field(&mut self, input: Field) -> syn::Field {
117 if let Some(key) = &input.ident {
118 let typ = &self.typ;
119 if let Some(num) = self.size_map.get(key) {
120 if let Type::Path(p) = &input.ty {
121 if p.path.is_ident("String") {
122 return Field {
123 attrs: input.attrs,
124 vis: input.vis,
125 mutability: input.mutability,
126 ident: input.ident,
127 colon_token: input.colon_token,
128 ty: parse_quote!{#typ::<#num>},
129 };
130 }
131 }
132 }
133 }
134 input
135 }
136}
137
138/// Replace one or more variable length fields with a fixed length equivalent
139///
140/// Pass in a list of `field_name=length` arguments. Optionally
141/// pass `typ=MyType` to use a different type for the replacement. See
142/// the crate documentation for moreinformation.
143#[proc_macro_attribute]
144pub fn fixed(args: TokenStream, input: TokenStream) -> TokenStream {
145 let mut args = parse_macro_input!(args as Args);
146 let input = parse_macro_input!(input as ItemStruct);
147 let output = args.fold_item_struct(input);
148 proc_macro::TokenStream::from(quote!(#output))
149}