Add index attribute for explicit index assignment

Fixes: https://github.com/trussed-dev/serde-indexed/issues/17
This commit is contained in:
Robin Krahl
2025-06-05 11:29:55 +02:00
parent 3deb892485
commit 07586dba9e
4 changed files with 79 additions and 11 deletions
+1
View File
@@ -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
+3 -2
View File
@@ -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<u8>,
#[serde(index = 3)]
pub bytes: [u8; 7],
}
```
+36 -9
View File
@@ -113,7 +113,12 @@ impl Parse for Input {
}
}
fn parse_field(index: usize, field: &syn::Field) -> Result<Field> {
fn parse_field(
attrs: &StructAttrs,
auto_index: usize,
field: &syn::Field,
indices: &mut Vec<usize>,
) -> Result<Field> {
let ident = field
.ident
.as_ref()
@@ -123,6 +128,7 @@ fn parse_field(index: usize, field: &syn::Field) -> Result<Field> {
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<Field> {
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<Field> {
}
}
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<syn::Field, Token![,]>,
) -> Result<Vec<Field>> {
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;
}
+39
View File
@@ -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,
],
);
}
}