prost-reflect
Reflection and dynamic messages for prost-based Protobuf code in Rust.
Repository Health
Technical Analysis
prost-reflect extends the popular prost Protobuf library with reflection support: it lets Rust programs load a compiled FileDescriptorSet at runtime and work with Protobuf messages whose shape isn’t known until then. Instead of generating a fixed Rust struct for every message type ahead of time, code can decode raw bytes into a DynamicMessage, inspect and mutate fields by name or number through a MessageDescriptor, and re-encode them, which is exactly the workflow tools like generic gRPC gateways, protobuf-to-JSON bridges, and schema-driven data pipelines need.
For projects that do generate static types with prost, the crate also provides a ReflectMessage trait (implementable by hand or via the optional derive macro) so those statically-typed messages can still expose descriptor metadata and interoperate with the dynamic API. Optional feature flags add canonical Protobuf JSON mapping via serde, human-readable text-format parsing, and miette-powered diagnostics that point at the exact line and column of a malformed descriptor.
What You Get
- A DescriptorPool type that parses and validates a compiled FileDescriptorSet, exposing message, enum, field, and service descriptors for runtime introspection
- DynamicMessage for decoding, mutating, and encoding Protobuf messages whose type is only known at runtime, with field access by name or field number
- A ReflectMessage trait plus an optional derive macro to bridge statically-generated prost types into the same descriptor/reflection API
- Optional serde support for converting DynamicMessage to and from the canonical Protobuf JSON mapping
- Optional text-format parsing and miette-based diagnostics that report descriptor errors with source-span pointers
- A companion prost-reflect-build crate for wiring descriptor generation into a build.rs alongside prost-build
Common Use Cases
- Building generic Protobuf-to-JSON gateways or gRPC reflection services that must handle arbitrary message types without recompiling
- Writing protobuf debugging, inspection, or CLI tooling that decodes messages using descriptors fetched at runtime
- Implementing schema registries or data pipelines where the Protobuf schema is loaded dynamically rather than baked into the binary
- Adding descriptor metadata to existing prost-generated types via the ReflectMessage derive for use in shared reflection-aware code paths
Under The Hood
Architecture
The crate is split into three internal modules that form a clear pipeline: descriptor parses a raw FileDescriptorSet (via generated types in descriptor/types.rs) into a validated, indexed DescriptorPool, catching duplicate names, invalid field numbers, and unresolved imports during construction rather than at use time (descriptor/api.rs, the crate’s largest module, exposes the resulting MessageDescriptor/FieldDescriptor/EnumDescriptor API); dynamic builds DynamicMessage and its field-storage representation (dynamic/fields.rs, dynamic/unknown.rs) on top of those descriptors, implementing prost::Message so a dynamic value can be encoded and decoded exactly like a generated struct; and reflect supplies the ReflectMessage trait plus well-known-type support (reflect/wkt.rs) that lets statically-typed prost messages plug into the same descriptor-lookup API used by dynamic ones. Descriptors are reference-counted (Arc) so pools and message descriptors can be cloned cheaply and shared across dynamic messages built from the same schema.
Tech Stack
Rust 2021 edition targeting MSRV 1.82, built on prost 0.14 and prost-types 0.14 for the core wire format. Everything else is opt-in via Cargo features: serde (plus base64 and serde-value) for the canonical Protobuf JSON mapping, text-format (via logos) for parsing human-readable proto text, miette for span-aware diagnostic errors, and derive for the companion prost-reflect-derive proc-macro crate. The repository is a Cargo workspace with sibling crates for build-time descriptor generation (prost-reflect-build, which wraps prost-build), an integration test suite (prost-reflect-tests), and a dedicated Protobuf conformance-test runner (prost-reflect-conformance-tests).
Code Quality
The crate hand-rolls its own error type (DescriptorErrorKind in descriptor/error.rs) rather than reaching for a generic error crate, carrying enough structured detail — duplicate names, invalid field/oneof indices, missing imports — to power the optional miette integration’s precise source-span diagnostics. Testing combines insta snapshot tests (dozens of committed .snap files covering descriptor-validation edge cases like name conflicts and option resolution) with proptest property tests for round-trip encode/decode behavior. CI runs the test suite across beta, stable, and the pinned MSRV toolchain, plus a separate lint job enforcing cargo fmt --check and cargo clippy -D warnings, and checks that the crate still compiles with each feature combination in isolation. Crate-level lints (#![deny(unsafe_code)], #![warn(missing_debug_implementations, missing_docs)]) are enforced throughout the source.
API Design
The public API mirrors prost’s own conventions closely — DynamicMessage implements prost::Message so it drops into existing encode/decode call sites — while keeping the reflection surface small: decode a pool once, look up a MessageDescriptor by fully-qualified name, and get/set fields by name or number. The optional derive macro removes most of the boilerplate needed to make an existing prost-generated struct participate in the same reflection API. Documentation is unusually thorough for a crate this size: module-level docs are assembled from standalone Markdown files in doc/ (intro, decoding, JSON mapping, implementing ReflectMessage) and rendered inline via include_str!, each with runnable code examples, and the crate builds with all-features on docs.rs.