Repository navigation
Conversation
…ully `ElementMapAccess` consumes the `End` event of its element only when it is read until `next_key_seed()` returns `None`. A `Visitor` is not obliged to do that, and when it stops early the `End` event stays in the stream and is then observed by the deserializer of the parent element. This is reachable from safe user code through `impl_deserialize_for_internally_tagged_enum!`: the generated `visit_map()` hands the map to serde's `MapAccessDeserializer`, whose `deserialize_enum()` reads exactly one entry, so mapping a variant onto an inner enum leaves the element half-read. Read the accessor to its end in a new `ElementMapAccess::end()`, called from the new `deserialize_element()` helper that replaces the four bare `visitor.visit_map(ElementMapAccess::new(...))` call sites. It is a no-op when the visitor already reached the end, which is what `#[derive(Deserialize)]` does for structs. closes tafia#928
|
Follow-up on the verification note in the description: I have since been able to run the suite, so here is the real evidence. I have also pushed the missing Environment: macOS arm64, Before the fix, the three new tests fail on current master: This is not only a
That is the part I would ask you to weigh when judging whether this is worth fixing: release builds lose list items and report spurious missing fields. After the fix, 25 test binaries, 4313 passed, 0 failed, 40 ignored, exit code 0. Limits of what I checked: everything above is macOS arm64 only, and I did not run the MSRV or minimal-versions jobs, so CI will need to confirm Linux and the other feature combinations. A judgement call worth disagreeing with. I chose to skip the unread remainder of the element rather than to error on it. Skipping is what makes the reported document deserialise to the value the reporter expected, and it keeps the derive-generated path byte-identical since those visitors always reach the end. The alternative — returning an error when a visitor stops early — would be stricter and would surface mapping mistakes like the one in #928 instead of quietly accepting them, at the cost of turning today's panic into a hard error for anyone currently relying on the release-mode behaviour. If you would rather have the strict version, I am happy to redo it that way. Note also that this only restores synchronisation; it does not change the fact that, as you pointed out in the issue, |
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1018 +/- ##
==========================================
- Coverage 57.31% 55.23% -2.08%
==========================================
Files 46 51 +5
Lines 18197 18840 +643
==========================================
- Hits 10429 10407 -22
- Misses 7768 8433 +665
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
I do not think, that this is true. If you look at the serde-json source code, you will find that it expect that the visitor will consume all the keys. |
|
You're right, and I tested it rather than argue. Thank you for the pointer. I reproduced the equivalent shape against fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Action, A::Error> {
let e = Element::deserialize(MapAccessDeserializer::new(map))?;
Ok(Action(e))
}So Which leaves a design question that is yours, not mine:
I lean towards 3 being the honest reading of #928, but I have not explored it and it is a bigger change. Happy to take whichever direction you prefer, or to close this if the drain is the wrong shape entirely. |
|
@hxperl Please read the AI assisted contributions policy https://github.com/tafia/quick-xml/blob/master/CONTRIBUTING.md#ai-assisted-contributions-policy |
|
Closing this — the premise in the description was wrong. serde_json errors on a partially-read map rather than draining it, so draining here diverges from the ecosystem, and the right fix is probably in the macro that generates a one-entry visitor rather than in the deserializer. Leaving #928 for someone who can give it the design attention it needs. Sorry for the noise. |
ElementMapAccessconsumes theEndevent of its element only when it is read untilnext_key_seed()returnsNone. AVisitoris under no obligation to do that, and when one stops early theEndevent stays in the stream and is then seen by the deserializer of the parent element, which desynchronises everything after it.That is reachable from safe user code through
impl_deserialize_for_internally_tagged_enum!. The generatedvisit_map()hands the map to serde'sMapAccessDeserializer, and itsdeserialize_enum()reads exactly one entry — so mapping a variant onto an inner enum leaves the element half-read and</action>unconsumed. That is the shape reported in #928.The change
A new
ElementMapAccess::end()reads the accessor to its end, called from a newdeserialize_element()helper that replaces the four barevisitor.visit_map(ElementMapAccess::new(...))call sites.It is a no-op when the visitor already reached the end, which is what
#[derive(Deserialize)]does for structs — so the common path is unchanged.Test
tests/serde-issues.rsgainsissue928_partially_read_element, which reproduces the reported mapping: theCreatevariant deliberately maps to the innerElementenum rather than toElementHolder, which is what pushes serde down thedeserialize_enumpath that reads only one entry.Verification — please read this part
I could not complete a
cargo testrun on this machine, and I am not going to imply otherwise. The environment I was working in ran out of disk repeatedly and the build directory had to be deleted mid-flight more than once, so I have no trustworthy green run to report. CI will need to be the first real confirmation that this compiles and passes.What I am confident about is the reasoning: the mechanism above is read off the source, the
end()call is a no-op on the pathderive(Deserialize)takes, and the regression test encodes the exact mapping from the issue rather than an approximation of it.If CI is red, or if you would rather the drain happened somewhere other than
deserialize_element(), say so and I will rework it.Closes #928