From 07586dba9e03bf20bd17b7d6413ecb1a4c6253ab Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 1 Jun 2025 23:16:44 +0200 Subject: [PATCH] Add index attribute for explicit index assignment Fixes: https://github.com/trussed-dev/serde-indexed/issues/17 --- CHANGELOG.md | 1 + src/lib.rs | 5 +++-- src/parse.rs | 45 ++++++++++++++++++++++++++++++++++++--------- tests/basics.rs | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0be1c29..d34e643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - No longer fails deserialising maps with unknown fields ([#19][]) - Prefer explicit indexing over automatically assigned indices ([#17][]): - Require `auto_index` attribute to enable automatic index assignment + - Add `index` attribute for explicit index assignment [#2]: https://github.com/trussed-dev/serde-indexed/issues/2 [#11]: https://github.com/trussed-dev/serde-indexed/pull/11 diff --git a/src/lib.rs b/src/lib.rs index 9f5dfc3..4b7c211 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,11 +8,12 @@ and a custom `offset` container attribute. use serde_indexed::{DeserializeIndexed, SerializeIndexed}; #[derive(Clone, Debug, PartialEq, SerializeIndexed, DeserializeIndexed)] -#[serde_indexed(auto_index, offset = 1)] pub struct SomeKeys { + #[serde(index = 1)] pub number: i32, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(index = 2, skip_serializing_if = "Option::is_none")] pub option: Option, + #[serde(index = 3)] pub bytes: [u8; 7], } ``` diff --git a/src/parse.rs b/src/parse.rs index 7324de1..412aeef 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -113,7 +113,12 @@ impl Parse for Input { } } -fn parse_field(index: usize, field: &syn::Field) -> Result { +fn parse_field( + attrs: &StructAttrs, + auto_index: usize, + field: &syn::Field, + indices: &mut Vec, +) -> Result { let ident = field .ident .as_ref() @@ -123,6 +128,7 @@ fn parse_field(index: usize, field: &syn::Field) -> Result { let mut deserialize_with = None; let mut serialize_with = None; let mut no_increment = false; + let mut explicit_index = None; for attr in &field.attrs { if attr.path().is_ident("serde") { @@ -191,6 +197,22 @@ fn parse_field(index: usize, field: &syn::Field) -> Result { serialize_with = Some(syn::parse2(serialize_tokens)?); deserialize_with = Some(syn::parse2(deserialize_tokens)?); + Ok(()) + } else if meta.path.is_ident("index") { + if explicit_index.is_some() { + return Err(meta.error("Multiple attributes for index")); + } + if attrs.auto_index { + return Err(meta.error( + "The index attribute cannot be combined with the auto_index attribute", + )); + } + let litint: LitInt = meta.value()?.parse()?; + let int = litint.base10_parse()?; + if indices.contains(&int) { + return Err(meta.error("This index has already been assigned")); + } + explicit_index = Some(int); Ok(()) } else { return Err(meta.error("Unkown field attribute")); @@ -199,6 +221,17 @@ fn parse_field(index: usize, field: &syn::Field) -> Result { } } + let index = if attrs.auto_index { + auto_index + } else if let Some(index) = explicit_index { + indices.push(index); + index + } else { + return Err(Error::new_spanned( + field, + "Field without index attribute and `#[serde(auto_index)]` is not enabled on the struct", + )); + }; Ok(Field { label: ident.to_string(), member: syn::Member::Named(ident.clone()), @@ -216,18 +249,12 @@ fn fields_from_ast( attrs: &StructAttrs, fields: &syn::punctuated::Punctuated, ) -> Result> { - if !attrs.auto_index { - return Err(Error::new_spanned( - fields, - "auto_index attribute must be set", - )); - } - + let mut indices = Vec::new(); let mut index = 0; fields .iter() .map(|field| { - let field = parse_field(index, field)?; + let field = parse_field(attrs, index, field, &mut indices)?; if !field.no_increment { index += 1; } diff --git a/tests/basics.rs b/tests/basics.rs index 64a3c07..eb1d149 100644 --- a/tests/basics.rs +++ b/tests/basics.rs @@ -617,3 +617,42 @@ mod generics { ) } } + +mod index { + use super::*; + + #[derive(PartialEq, Debug, SerializeIndexed, DeserializeIndexed)] + struct WithIndices { + #[serde(index = 9)] + test1: usize, + #[serde(index = 2)] + test2: usize, + #[serde(index = 0x5A)] + test3: usize, + } + + fn indices_example() -> WithIndices { + WithIndices { + test1: 42, + test2: 1, + test3: 99, + } + } + + #[test] + fn tokens() { + assert_tokens( + &indices_example(), + &[ + Token::Map { len: Some(3) }, + Token::U64(9), + Token::U64(42), + Token::U64(2), + Token::U64(1), + Token::U64(0x5A), + Token::U64(99), + Token::MapEnd, + ], + ); + } +}