Skip to content

Conversation

@jordens
Copy link

@jordens jordens commented Apr 22, 2025

Summary

miniconf is a library we use to access (get/set over serial, mqtt, store in flash) settings on embedded devices. It is a serde-based hierarchical heterogeneous key-value access mechanism.
We'd like to generate a schema for the settings trees and found serde-reflection very useful for this (thanks!).

We implement functionality similar to trace_value/trace_type from Tracer using our intermediate miniconf::TreeSerialize/TreeDeserialize traits. See below for what that looks like.

For trace_value and trace_type_once we would like Serializer and Deserializer to be pub. Happy to split that out of this PR if it's uncontroversial.

For trace_type we also need a way to mark the top level enum in incomplete_enums before tracing again (see trace_type).

Downstream use case:
https://github.com/quartiq/miniconf/blob/c741c31b8fcd5e8769861c16081f0d6895e019a2/miniconf/src/trace.rs#L13-L63

Test Plan

CI

@ma2bd
Copy link
Contributor

ma2bd commented May 4, 2025

Interesting. We may want to mark the newly public function(s) as #[doc(hidden)].

Introduces a third EnumProgress variant to mark enums as pending
re-analysis. This now keeps them marked as incomplete but lets the
Deserialize make progress. It upholds the promise the the registry can only
be complete if incomplete_enums is empty.

* add docs to Serializer::new, Deserializer::new
* expose EnumProgress
* make incomplete_enums only pub(crate) again
@jordens jordens marked this pull request as ready for review May 5, 2025 12:37
@jordens jordens requested a review from ma2bd as a code owner May 5, 2025 12:37
@jordens
Copy link
Author

jordens commented May 5, 2025

I think I found a decent way to keep up the invariant that only if incomplete_enums is empty, a complete registry can be returned and I left out the doc hiding. AFAICT it would not have made a difference w.r.t. semver rules anyway.
Let me know if you still prefer to hide the new API.

@jordens jordens changed the title Make Serializer and Deserializer pub, access incomplete_enum mechanism Make Serializer and Deserializer pub and expose incomplete_enum mechanism May 5, 2025
@jordens
Copy link
Author

jordens commented Jul 15, 2025

@ma2bd anything I can do to advance this PR?

* upstream/main:
  prepare release of serde-reflection 0.5.1
  Add dynamic JSON converter module (zefchain#82)
  Fix parameter naming in generated code to resolve linter warnings (zefchain#80)
  attempt to fix get-solc in CI (zefchain#83)
  Prepare release of serde-generate 0.32.0 and serde-generate-bin 0.8.0 (zefchain#78)
  Several improvements to solidity support in `serde_reflection` (zefchain#77)
  prepare release of serde-generate 0.31.0 and serde-generate-bin 0.7.0
  Improve the support for solidity (zefchain#74)
  Fix dart bytes hashcode (zefchain#76)
@ma2bd
Copy link
Contributor

ma2bd commented Dec 4, 2025

@ma2bd anything I can do to advance this PR?

Hi @jordens, Thanks for your patience. Couple of questions:

  • Why do we need EnumProgress::Pending? I can't see the difference between removing an entry from incomplete_enums and marking it as EnumProgress::Pending. If there is a difference, would you be able to provide a unit test demonstrating the new functionality?
  • The debug_assert! is also puzzling me. Are we worried about an infinite loop? But then why not assert! or better return an Error instead?

Shows how one would use serde_reflection::Deserializer directly
and explains the relevance of EnumProcess::Pending when doing so.
Copy link
Author

@jordens jordens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ma2bd thanks for looking at this!

  • To make use of an exposed Deserializer the user also needs access to incomplete_enums to signal that further variants should be traced. If the user then removes an enum from incomplete_enums the tracer state is inconsistent (until and unless it is re-traced): tracer.registry() at that point would succeed but be wrong. Before this PR that inconsistency only existed internally and for a short time:

    let (format, value) = self.trace_type_once::<T>(samples)?;
    values.push(value);
    if let Format::TypeName(name) = &format {
    if let Some(&progress) = self.incomplete_enums.get(name) {
    // Restart the analysis to find more variants of T.
    self.incomplete_enums.remove(name);
    during the interval from L260 to the next trace in L255. To make it always consistent for a downstream Deserializer user I need a way to distinguish the state "incomplete but marked for re-tracing" (i.e. EnumProgress::Pending) from the state "unknown or complete" (not present in incomplete_enums). I added a unittest showing the functionality and where the inconsistent state would show up. I tried to keep it readable. If you want, I can juggle the commits so that the test will actually show the wrong behavior before a subsequent commit fixes it.

  • The infinite loop at that point was undetectable before. With the new EnumProcess::Pending it can now be detected. Since an Infinite loop here appears to imply wrong behavior in serde_reflection, I choose assert over returning an error. But I agree that the performance penalty is negligible and it should not be debug_assert. Changed.

@ma2bd
Copy link
Contributor

ma2bd commented Dec 9, 2025

@ma2bd thanks for looking at this!

  • To make use of an exposed Deserializer the user also needs access to incomplete_enums to signal that further variants should be traced. If the user then removes an enum from incomplete_enums the tracer state is inconsistent (until and unless it is re-traced): tracer.registry() at that point would succeed but be wrong. Before this PR that inconsistency only existed internally and for a short time:
    let (format, value) = self.trace_type_once::<T>(samples)?;
    values.push(value);
    if let Format::TypeName(name) = &format {
    if let Some(&progress) = self.incomplete_enums.get(name) {
    // Restart the analysis to find more variants of T.
    self.incomplete_enums.remove(name);

    during the interval from L260 to the next trace in L255. To make it always consistent for a downstream Deserializer user I need a way to distinguish the state "incomplete but marked for re-tracing" (i.e. EnumProgress::Pending) from the state "unknown or complete" (not present in incomplete_enums). I added a unittest showing the functionality and where the inconsistent state would show up. I tried to keep it readable. If you want, I can juggle the commits so that the test will actually show the wrong behavior before a subsequent commit fixes it.
  • The infinite loop at that point was undetectable before. With the new EnumProcess::Pending it can now be detected. Since an Infinite loop here appears to imply wrong behavior in serde_reflection, I choose assert over returning an error. But I agree that the performance penalty is negligible and it should not be debug_assert. Changed.

Thanks for your answer. I tried your unit test and it doesn't seem to require the new value Pending to pass. So I'm proposing #85

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants