Skip to content

pxf: refuse (pxf.required) with (pxf.default) on one field at bind time - #26

Merged
trendvidia merged 2 commits into
mainfrom
fix/required-default-exclusive-22
Oct 31, 2026
Merged

trendvidia merged 2 commits into
mainfrom
fix/required-default-exclusive-22

Conversation

@bfrancojr

Copy link
Copy Markdown
Collaborator

A schema with both a required and a default on one field now fails when it is bound, as draft -01 requires (trendvidia/protowire#453). Before, it bound and a document omitting the field had no single meaning.

Fixes #22
Fixes #23

What this means for a port that wraps protowire-cpp

The bind checks live in protowire-cpp, which this port wraps, and its own check (protowire-cpp#50) is not merged or tagged. There were two ways to go:

  • Wait for a tag and bump the pin. Nothing here could pass CI until then.
  • Check in the bridge now. This is what the PR does.

The check is in module.cc and uses cpp's own IsRequired and GetDefault, so the option numbers stay in one place. It is meant to be temporary, and #25 tracks removing it. If you would rather wait for the cpp tag, drop the second commit and keep the test file.

It needed the pin bump first, which is the first commit. The workflows pinned cpp v1.0.0, which reads these options at 50000/50001, so it could not see them on a current schema. That pin also segfaulted on deep PXF (#23). The bump touches publish.yml, so it changes what the next wheels contain.

What it covers

  • unmarshal, unmarshal_full and their _bytes forms raise ValueError.
  • validate_descriptor reports a "required_default" violation.
  • The scope is the file and its import closure, including nested messages.
  • skip_validate=True skips it, as it skips the other bind checks. That includes bind_row, which defaults to skipping.
  • (pxf.required) = false beside a default stays accepted.

Bracket options are the only spelling covered, because they are the only one this port reads. protowire-cpp has no reader for the @required / @default carrier, so a schema written that way gets neither the options nor this check. No test schema in this repo carried the pair, so none changed.

The message is the Go wording: "(pxf.required) and (pxf.default) are exclusive on one field: the first rejects a document where the field is absent, and the second treats it as supplied".

Evidence

  • .venv/bin/python -m pytest -q on the head commit, built against sibling protowire-cpp main (8d0867a): 108 passed in 0.74s. Before this PR: 100 passed.
  • Same tests built against an export of the v1.1.0 tag: 103 passed, 5 skipped. The skips are from running the copy outside the sibling layout. I did not look at each skip reason.
  • With the walk removed from FindRequiredWithDefault: 4 failed, 104 passed. The four are test_required_with_default_is_refused_at_bind, test_option_order_does_not_matter, test_nested_message_is_covered and test_import_closure_is_covered_even_when_no_field_uses_the_type.
  • A real schema compiled by protoc 36.2 against cpp's annotations.proto, string role = 1 [(pxf.required) = true, (pxf.default) = "viewer"];, is refused. The same field with (pxf.required) = false is accepted.
  • The accepted-case tests assert that unmarshal_full applied the default and enforced required, so those options were really read and the accept is not a parser that saw nothing.
  • FFI change: the 31 HARDENING corpus rows that apply to Python all give the expected verdict on this build, using the harness from the protowire#449 PR. That stands in for the manual cpp round-trip check in CONTRIBUTING, which I did not run.

Not proven here: Linux and Windows on the new pin. Those legs run only in this PR's CI.

Also fixed

  • Violation.kind is "key_option" for a misplaced (pxf.key). On v1.1.0 it would otherwise be an empty string.

Filed

The v1.0.0 pin predates the depth cap and the move of the PXF and SBE
options to their registered numbers, so CI and the wheels could not see
(pxf.required) on a current schema at all. The bind check for #22 needs the
numbers; the depth cap is the rest of #23.

Fixes #23
…me (#22)

Draft -01 § Required and Default forbids the pair, over the scope of bind-time
checks. The bind checks live in protowire-cpp, whose own check for this
(protowire-cpp#50) is unmerged and untagged, so the bridge makes it for now,
through cpp's IsRequired and GetDefault so the option numbers stay in one
place. It covers the import closure and honors skip_validate, as cpp's
checks do, and goes when the pin carries the upstream check.

v1.1.0 adds ViolationKind::kKeyOption, which the switch in
PxfValidateDescriptor left as an empty kind; it now reads key_option.

Fixes #22
@trendvidia
trendvidia merged commit 7ebf119 into main Oct 31, 2026
18 checks passed
@trendvidia
trendvidia deleted the fix/required-default-exclusive-22 branch October 31, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants