Skip to content

Implement lazy message conversion for protovalidate#402

Draft
andrewparmet wants to merge 17 commits into
open-toast:mainfrom
andrewparmet:protovalidate-lazy
Draft

Implement lazy message conversion for protovalidate#402
andrewparmet wants to merge 17 commits into
open-toast:mainfrom
andrewparmet:protovalidate-lazy

Conversation

@andrewparmet

@andrewparmet andrewparmet commented Jun 25, 2025

Copy link
Copy Markdown
Collaborator

Run conformance tests using the existing eager implementation with:

./gradlew testing:protovalidate-conformance:conformance -PlazyBufImpl=false

and the new lazy implementation with:

./gradlew testing:protovalidate-conformance:conformance -PlazyBufImpl=true

Depends on bufbuild/protovalidate-java#202

…tovalidate

- Replace PackageHacks.kt / ProtoktEvaluator / ProtoktEvaluatorBuilder with
  direct use of protovalidate-java's new public validate(MessageReflector,
  Descriptor) entrypoint.
- ProtoktMessageReflector implements MessageReflector; ProtoktObjectValue
  implements Value. Both live in the public protokt.v1.buf.validate package.
- Validator.validate(message) looks up the descriptor by @GeneratedMessage's
  fullTypeName.
- Make RuntimeContext incremental: descriptor registry is a PersistentMap
  and a new operator plus(descriptor) returns a copy with the descriptor
  registered. Avoids O(N²) rebuild when loading descriptors one at a time.
- protokt-reflect now depends on kotlinx-collections-immutable.
- Wire mavenLocal into the Java project convention so the protovalidate-java
  SNAPSHOT resolves during local development.
# Conflicts:
#	gradle/libs.versions.toml
#	protokt-protovalidate/src/main/kotlin/build/buf/protovalidate/PackageHacks.kt
#	protokt-protovalidate/src/main/kotlin/protokt/v1/buf/validate/Validator.kt
#	testing/protovalidate-conformance/build.gradle.kts
Duration, Timestamp, wrappers, and Empty have fixed field layouts and
map to a single CEL-native value. Read the fields straight off the
protokt message by descriptor and return the CEL-native form directly,
skipping the round-trip through RuntimeContext.convertValue and
BaseProtoCelValueConverter.toRuntimeValue.

Any, Value, Struct, and ListValue keep the DynamicMessage fallback:
their shape is open so we let cel-java's converter handle them.

FieldMask drops off the WKT list entirely; with cel-java Patch 2
applied it flows through the ordinary ProtoktStructValue path like any
other message.

Unset wrapper fields now materialize as NullValue.NULL_VALUE per the
CEL spec.
protovalidate-java's MessageReflector now exposes the descriptor
itself, so Validator.validate takes only the reflector. Implement
getDescriptorForType on ProtoktMessageReflector and drop the second
argument from the delegate call.
@andrewparmet andrewparmet force-pushed the protovalidate-lazy branch 2 times, most recently from 2744188 to 0433ce5 Compare May 30, 2026 18:18
… entries

Get the MessageReflector + StructValue path to 2840/2840 conformance parity.

- ProtoktObjectValue.rawValue: translate UInt/ULong/Enum/Bytes/List/Map to
  the boxed Java types com.google.protobuf.Message#getField returns; map
  fields surface as List<MapEntry>.
- ProtoktMessageReflector.getField: reverse protokt's wrap annotation when a
  MESSAGE-typed field surfaces as a scalar, returning a JvmMessageReflector
  over the unwrapped com.google.protobuf.Message.
- Bump protovalidate (testing protos + conformance CLI) to 1.2.0 to match
  library message wording, and pin protovalidateJava to the standard
  god-branch snapshot.
- Wire -Xfriend-paths in protokt-protovalidate, promote fieldOptions / wrap
  in RuntimeContext from private to internal.
Commit the protokt protovalidate integration to the planner CEL runtime.

- Drop the manual BaseProtoCelValueConverter.toRuntimeValue pre-unwrap for
  open WKTs (Any, Value, Struct, ListValue). It was a workaround for the
  standard DefaultInterpreter's select path, which calls maybeUnwrap but not
  toRuntimeValue on the way out. The planner's dispatch does both, so handing
  it the plain com.google.protobuf.Message (or a DynamicMessage default for
  unset fields) and letting it convert on read is sufficient.
- Remove the now-unused WellKnownTypes.unwrap helper, its converter field,
  and the BaseProtoCelValueConverter import.
- Pin cel = 0.13.0-planner-SNAPSHOT to consume the planner-only cel-java
  branch (two fixes instead of three).

Conformance holds at 2840/2840.
cel-java 0.13.1 ships the CelValue-path fixes the protovalidate integration
relied on (fixed32/64 as unsigned, FieldMask navigable as a message), so the
local cel fork snapshot is no longer needed. Pin stock dev.cel:cel:0.13.1.
This branch had accumulated changes unrelated to protovalidate from its old
base: a generated grpc-krpc yarn.lock, proto-google-common-protos API dumps, a
gradle-wrapper downgrade, reverts of edition-2023 packing in FieldParser and of
multi-type dispatch in the conformance driver, a conformance proto-export
restructure, and stale version reversions in libs.versions.toml.

Reset all of those to origin/main so the diff is just the protovalidate
integration: ProtoktMessageReflector, ProtoktStructValue, the Validator and
RuntimeContext wiring, the protokt-protovalidate friend-paths build config,
mavenLocal for the protovalidate-java snapshot, and the cel/protovalidate
version pins.

Conformance holds at 2840/2840.
pkwarren pushed a commit to bufbuild/protovalidate-java that referenced this pull request Jun 15, 2026
Moves protovalidate-java's CEL setup from the deprecated standard
runtime to the planner runtime.

## Notable callouts

- Wire the compiler and runtime by hand rather than through
`plannerCelBuilder()`. `CelRuntimeImpl` directs callers to subset the
standard library via `setStandardFunctions` rather than
`setStandardEnvironmentEnabled`, so the runtime drops stdlib `matches`
that way while the checker continues to use
`setStandardEnvironmentEnabled(false)`. This keeps `CustomOverload`'s
caching `matches` replacement (cel-java#1038).
- Set
`CelOptions.current().enableHeterogeneousNumericComparisons(true)`,
which `CelRuntimeImpl` requires.
- Update `cel-java` to 0.13.1 preparing to support alternative Protobuf
runtimes.

## Related work:

- cel-java: 0.13.1 carries the CelValue-path fixes this work relies on
(fixed32/64 treated as unsigned; FieldMask navigable as a message,
cel-expr/cel-java#1074).
- protovalidate-java: #480 introduces `MessageReflector` to support
alternative Protobuf runtimes, which builds on this runtime change
(supersedes #202).
- protokt: open-toast/protokt#402 is the downstream consumer that
validates protokt messages through `MessageReflector` on this planner
runtime.

## Testing

`./gradlew :conformance:conformance` passes.
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.

1 participant