Skip to content

Commit 9478e5b

Browse files
Add index attribute for explicit index assignment
Fixes: #17
1 parent c857b36 commit 9478e5b

File tree

4 files changed

+81
-12
lines changed

4 files changed

+81
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Prefer explicit indexing over automatically assigned indices:
88
- Require `auto_index` attribute to enable automatic index assignment
9+
- Add `index` attribute for explicit index assignment
910

1011
## [v0.1.1][] (2024-04-03)
1112

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ and a custom `offset` container attribute.
88
use serde_indexed::{DeserializeIndexed, SerializeIndexed};
99
1010
#[derive(Clone, Debug, PartialEq, SerializeIndexed, DeserializeIndexed)]
11-
#[serde_indexed(auto_index, offset = 1)]
1211
pub struct SomeKeys {
12+
#[serde(index = 1)]
1313
pub number: i32,
14-
#[serde(skip_serializing_if = "Option::is_none")]
14+
#[serde(index = 2, skip_serializing_if = "Option::is_none")]
1515
pub option: Option<u8>,
16+
#[serde(index = 3)]
1617
pub bytes: [u8; 7],
1718
}
1819
```

src/parse.rs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,19 @@ impl Parse for Input {
9393
}
9494
}
9595

96-
fn parse_field<T: ToTokens + Copy>(span: T, i: usize, field: &syn::Field) -> Result<Field> {
96+
fn parse_field<T: ToTokens + Copy>(
97+
span: T,
98+
attrs: &StructAttrs,
99+
i: usize,
100+
field: &syn::Field,
101+
indices: &mut Vec<usize>,
102+
) -> Result<Field> {
97103
let ident = field
98104
.ident
99105
.as_ref()
100106
.ok_or_else(|| Error::new_spanned(span, "Tuple structs are not supported"))?;
101107
let mut skip_serializing_if = None;
108+
let mut index = None;
102109
for attr in &field.attrs {
103110
if attr.path().is_ident("serde") {
104111
attr.parse_nested_meta(|meta| {
@@ -110,16 +117,43 @@ fn parse_field<T: ToTokens + Copy>(span: T, i: usize, field: &syn::Field) -> Res
110117
}
111118
skip_serializing_if = Some(syn::parse2(tokens)?);
112119
Ok(())
120+
} else if meta.path.is_ident("index") {
121+
if index.is_some() {
122+
return Err(meta.error("Multiple attributes for index"));
123+
}
124+
if attrs.auto_index {
125+
return Err(meta.error(
126+
"The index attribute cannot be combined with the auto_index attribute",
127+
));
128+
}
129+
let litint: LitInt = meta.value()?.parse()?;
130+
let int = litint.base10_parse()?;
131+
if indices.contains(&int) {
132+
return Err(meta.error("This index has already been assigned"));
133+
}
134+
index = Some(int);
135+
Ok(())
113136
} else {
114137
Err(meta.error("Unkown field attribute"))
115138
}
116139
})?;
117140
}
118141
}
142+
let index = if attrs.auto_index {
143+
i
144+
} else if let Some(index) = index {
145+
indices.push(index);
146+
index
147+
} else {
148+
return Err(Error::new_spanned(
149+
span,
150+
"Field without index attribute and auto_index is not set",
151+
));
152+
};
119153
Ok(Field {
120154
label: ident.to_string(),
121155
member: syn::Member::Named(ident.clone()),
122-
index: i,
156+
index,
123157
skip_serializing_if,
124158
})
125159
}
@@ -128,17 +162,11 @@ fn fields_from_ast(
128162
attrs: &StructAttrs,
129163
fields: &syn::punctuated::Punctuated<syn::Field, Token![,]>,
130164
) -> Result<Vec<Field>> {
131-
if !attrs.auto_index {
132-
return Err(Error::new_spanned(
133-
fields,
134-
"auto_index attribute must be set",
135-
));
136-
}
137-
138165
// serde::internals::ast.rs:L183
166+
let mut indices = Vec::new();
139167
fields
140168
.iter()
141169
.enumerate()
142-
.map(|(i, field)| parse_field(fields, i, field))
170+
.map(|(i, field)| parse_field(fields, &attrs, i, field, &mut indices))
143171
.collect()
144172
}

tests/basics.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,3 +487,42 @@ mod generics {
487487
assert_eq!(deserialized, example);
488488
}
489489
}
490+
491+
mod index {
492+
use super::*;
493+
494+
#[derive(PartialEq, Debug, SerializeIndexed, DeserializeIndexed)]
495+
struct WithIndices {
496+
#[serde(index = 9)]
497+
test1: usize,
498+
#[serde(index = 2)]
499+
test2: usize,
500+
#[serde(index = 5)]
501+
test3: usize,
502+
}
503+
504+
fn indices_example() -> WithIndices {
505+
WithIndices {
506+
test1: 42,
507+
test2: 1,
508+
test3: 99,
509+
}
510+
}
511+
512+
#[test]
513+
fn tokens() {
514+
assert_tokens(
515+
&indices_example(),
516+
&[
517+
Token::Map { len: Some(3) },
518+
Token::U64(9),
519+
Token::U64(42),
520+
Token::U64(2),
521+
Token::U64(1),
522+
Token::U64(5),
523+
Token::U64(99),
524+
Token::MapEnd,
525+
],
526+
);
527+
}
528+
}

0 commit comments

Comments
 (0)