Skip to content

feat(zeroparser): add MessageRegistry::from_descriptors for sibling/imported types#461

Open
pixie79 wants to merge 2 commits into
databricks:mainfrom
pixie79:registry-from-descriptors
Open

feat(zeroparser): add MessageRegistry::from_descriptors for sibling/imported types#461
pixie79 wants to merge 2 commits into
databricks:mainfrom
pixie79:registry-from-descriptors

Conversation

@pixie79

@pixie79 pixie79 commented Jun 30, 2026

Copy link
Copy Markdown

What changes are proposed in this pull request?

Add a new constructor, MessageRegistry::from_descriptors(root, others), to the
zeroparser registry so a parse registry can be built from the whole
FileDescriptorSet — the root message plus the sibling/imported top-level
messages it references — rather than from the root descriptor alone.

Concretely:

  • New APIfrom_descriptors(root: &DescriptorProto, others: &[(String, &DescriptorProto)]).
    It registers the root exactly as from_descriptor does (so root-relative
    lookups are identical), then registers every other top-level message under its
    true package-qualified FQN (leading dot), e.g. package google.protobuf +
    message StringValue → key .google.protobuf.StringValue. That key matches
    the type_name form carried on message-typed FieldDescriptorProtos, so
    field lookups now resolve.
  • Shared collectorcollect_messages now delegates to a small
    collect_messages_with_prefix(root, initial_prefix, acc) helper that threads
    the enclosing namespace as a prefix. An empty prefix yields .Name (the
    existing root behaviour); a package prefix like .google.protobuf yields
    .google.protobuf.Name.
  • No behavioural change to existing APIfrom_descriptor is unchanged
    (it simply calls the helper with the root prefix), so all current callers and
    semantics are preserved byte-for-byte. The change is purely additive.

Why are these changes needed?

MessageRegistry::from_descriptor only recurses nested_type. It therefore
registers the root message and its nested children, but never the sibling
or imported top-level messages that live in other files of the same
FileDescriptorSet.

The most common casualty is the Protobuf well-known types. A root message with a
field typed .google.protobuf.StringValue (imported from
google/protobuf/wrappers.proto) compiles and loads fine, but at parse time the
lookup for .google.protobuf.StringValue misses the registry and decoding fails
on every record that populates such a field:

UnknownTypeName: Unknown message type '.google.protobuf.StringValue' not in registry

This affects any schema that uses imported messages — the google.protobuf
wrappers (StringValue, Int64Value, …), Timestamp, Duration, Any, or a
user's own imported .proto types. There is currently no public way to populate
the registry with those sibling/imported descriptors, because the messages map
and the collector are private. from_descriptors provides that path without
disturbing the existing root-only constructor, so callers that have the full
descriptor set can opt in and decode imported-type fields correctly.

How is this tested?

cargo test -p databricks-zerobus-ingest-sdk --features zeroparser (the
zeroparser module is feature-gated):

  • zeroparser::registry — 6 passed, 0 failed, including the two new tests:
    • message_registry_from_descriptors_registers_siblings — proves
      from_descriptor alone does not register an imported
      .google.protobuf.StringValue (lookup is None), while from_descriptors
      registers it under its true FQN, keeps the root as the parse root, and the
      field still resolves to .google.protobuf.StringValue.
    • message_registry_from_descriptors_empty_package_and_nesting — covers the
      empty (unnamed) package case (sibling registers at .Name) and confirms a
      sibling's own nested types are collected (.Sibling.Inner).
  • Full zeroparser suite — 82 passed, 0 failed; the e2e example binary
    compiles green.

The change is also exercised downstream (a consumer that builds the registry
from the full FileDescriptorSet via from_descriptors): a record carrying a
populated StringValue and Timestamp on the wire previously failed with the
UnknownTypeName error above and now decodes and round-trips correctly.


Signed-off-by: Mark Olliver mark.olliver@flutter.com

…mported types

MessageRegistry::from_descriptor recurses only nested_type, so it registers
the root message and its nested children but never the sibling or imported
top-level messages from other files in a FileDescriptorSet. A root field
typed e.g. `.google.protobuf.StringValue` (from google/protobuf/wrappers.proto)
therefore misses on registry.get() and parsing fails with
`UnknownTypeName: Unknown message type '.google.protobuf.StringValue' not in
registry` on every record that populates such a field.

Add `from_descriptors(root, others)` which keeps the root as the parse root
(unchanged from_descriptor behaviour) and additionally registers every other
top-level message under its TRUE package-qualified FQN with a leading dot, by
threading the proto package as the initial prefix through the existing
collector. `collect_messages` now delegates to a `collect_messages_with_prefix`
helper so the prefix-threading is shared; the public `from_descriptor` API is
byte-for-byte unchanged.

Includes unit tests covering sibling registration under a non-empty package,
the empty-package (root-prefix) case, and nested-type collection on siblings.

Signed-off-by: Mark Olliver <mark.olliver@flutter.com>
@pixie79 pixie79 force-pushed the registry-from-descriptors branch from 7775b1d to 80fcd0b Compare June 30, 2026 19:35
Comment thread rust/sdk/src/zeroparser/registry.rs Outdated
/// `root` remains the parse root (`root_descriptor`); the existing
/// `from_descriptor` API is unchanged.
#[inline(always)]
pub fn from_descriptors(root: &DescriptorProto, others: &[(String, &DescriptorProto)]) -> Self {

@brankogrb-db brankogrb-db Jul 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Change seems reasonable but let's add e2e tests for it

Comment thread rust/sdk/src/zeroparser/registry.rs Outdated
}
}

/// Build a registry from a root descriptor plus the other top-level message

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's try to keep comments short and concise.

Comment thread rust/sdk/src/zeroparser/registry.rs Outdated
/// `root` remains the parse root (`root_descriptor`); the existing
/// `from_descriptor` API is unchanged.
#[inline(always)]
pub fn from_descriptors(root: &DescriptorProto, others: &[(String, &DescriptorProto)]) -> Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

use &str instead of String

@brankogrb-db

Copy link
Copy Markdown
Contributor

Let's also run existing benchmarks to make sure we didn't hit some perf regression - even though we shouldn't

@brankogrb-db

Copy link
Copy Markdown
Contributor

Since I see you generated this PR with AI, I suggest running self-review on this PR with AI also. I think it can be super helpful on finding out some of the issues

Borrow the package strings in the from_descriptors API
(`others: &[(&str, &DescriptorProto)]`), trim the verbose rustdoc to a
concise summary, and update the two unit-test call sites.

Add true end-to-end coverage: create_registry_for_version now builds the
registry via from_descriptors, threading every top-level message in the
compiled FileDescriptorSet (including the imported google.protobuf types)
under its package-qualified FQN. The former negative test
test_google_protobuf_types_not_in_registry becomes
test_google_protobuf_types_decode_success, decoding a
GoogleProtobufTypesMessage and asserting Timestamp, Duration and every
wrapper type resolve and decode correctly for both proto2 and proto3.

Add a NEXT_CHANGELOG entry for the new zeroparser API.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Mark Olliver <mark.olliver@flutter.com>
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