Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Inconsistent behavior for required and ignore_empty #115

Closed
jhump opened this issue Oct 24, 2023 · 3 comments
Closed

[BUG] Inconsistent behavior for required and ignore_empty #115

jhump opened this issue Oct 24, 2023 · 3 comments
Labels
Bug Something isn't working

Comments

@jhump
Copy link
Member

jhump commented Oct 24, 2023

This issue represents a few different defects:

  1. The current documentation does not fully specify required and ignore_empty field constraints. It is unclear how message fields are handled (is an empty message one that is unset/null or one that is a default/empty message instance?) or how this interacts with field presence and explicitly configured default values (which may be non-zero).
  2. The current implementations actually disagree on the meaning of "empty" and how that is applied to the required and ignore_empty field constraints. So once a clear specification is provided for these constraints, some implementations will likely need to be updated to comply with the revised spec.
  3. The current conformance tests do not have any cases which enforce how these constraints are applied, which is how we find ourselves with inconsistent implementations.

In addition to resulting in inconsistency, some of the implementations are also written in a way that will likely not work correctly with Protobuf Editions. In particular, some implementations do not fully lean on the reflection support of the relevant Protobuf runtime, which will correctly consider "features" in source files that use Protobuf Editions. This means that the new representation for things like implicit vs. explicit field presence is likely to confuse the implementation logic.

Below is a summary of how the various runtimes actually implement these constraints:

In general, the implementations should really look much more like one another. Even if we add conformance tests to better detect inconsistencies like above, it would instill greater confidence if the implementation code also looked more uniform, using the same conditions and reflection APIs across all implementations, where possible. (FWIW, the Protobuf runtimes do provide reasonably consistent APIs for reflection, across languages.)

In my opinion, the implementation in the Go runtime is closest to what I would intuitively expect -- that a default value, even if set explicitly, is not checked when ignore_empty is true. However, I would argue that the name of this constraint is very confusing since "empty" doesn't intuitively apply to scalar fields at all. I think ignore_default would be more clear, especially since it ignores other constraints not only when the field is absent but also when explicitly set to the default value. The only down-sides to the Go implementation is that its unclear how its usage of FieldDescriptor.Default() interacts with non-scalar fields. Is an explicitly-set empty message considered "empty"? And, either way, should it? My opinion is that an explicitly set field is not empty, only an absent one. But I can see this interpretation being debatable (even if the constraint were named ignore_default).

Some of the inconsistencies are a bit subtle, such that devising a conformance test to distinguish one's behavior from another might be tricky. But we should at least have conformance tests that cover the following scenarios, which should have teased out issues in the above implementations:

  • A proto2 optional field is "present" and thus valid per the required constraint as long as it is explicitly set, even if set to its zero or default value. (This would catch potential issues in the Python implementation.)
  • We should have test cases that use messages that define default values for all scalar field types, and then verify that they are correctly considered empty. This can be done with a validation that would otherwise fail with the field's default or zero value, which should be skipped. Before such cases can be formulated, we need to first agree on and fully specify the meaning of ignore_empty. (This would catch the inconsistencies between using a field's configured default vs. hard-coding to zero.)
  • We should have cases that verify (both positive and negative outcomes) whether absent and present-but-empty message values are considered empty.
@jhump jhump added the Bug Something isn't working label Oct 24, 2023
@jhump
Copy link
Member Author

jhump commented Oct 24, 2023

cc: @Alfus, @rodaine

@rodaine
Copy link
Member

rodaine commented Oct 30, 2023

Thanks @jhump. The conformance tests are indeed limited in capturing the full breadth of behaviors. required, for instance, used to be exclusively on the MessageConstraints but now has adopted a wider meaning. The Go implementation is unofficially our official reference implementation so we will likely use its behavior as the guidepost of what is expected and should test & document accordingly.

@rodaine
Copy link
Member

rodaine commented Nov 1, 2023

@jhump, #124 improves on the documentation for these fields and adds conformance tests to cover the expected behavior of required (with a fast follow for ignore_empty if approved).

rodaine added a commit that referenced this issue Nov 2, 2023
This patch improves behavioral documentation of the
`OneOfConstraints.required`, `FieldConstraints.required`, and
`FieldConstraints.ignore_empty` standard constraints. The conformance
tests have also been expanded to check the behavior for `required` (with
`ignore_empty` to follow in a follow up).

Local conformance testing has verified that the current
`protovalidate-go` behavior matches the expectations of these
constraints.

Partially addresses: #115
rodaine added a commit that referenced this issue Nov 6, 2023
… keys/values (#128)

Oneof tests were redundant (and contradictory to the behavior we're
going for), so removed them in favor of the more exhaustive ones in the
ignore_empty suite. Likewise, added tests to ensure the behavior of
ignore_empty as applied to repeated items and map keys/values is
consistent as well.

Relates to #115
rodaine added a commit to bufbuild/protovalidate-go that referenced this issue Nov 6, 2023
rodaine added a commit to bufbuild/protovalidate-python that referenced this issue Nov 7, 2023
Brings the python library in conformance with the revised spec around
`required` and `ignore_empty` constraints. See
bufbuild/protovalidate#115

The two `# type: ignore` comments are required as typeshed does not
currently recognize the `has_presence` property on FieldDescriptor
class. I may open an upstream patch to add it in (and audit the rest of
the protobuf type definitions).
rodaine added a commit to bufbuild/protovalidate-cc that referenced this issue Nov 7, 2023
Bringing the `required` and `ignore_empty` constraints into conformance.

See bufbuild/protovalidate#115
rodaine added a commit to bufbuild/protovalidate-java that referenced this issue Nov 8, 2023
Bringing the `required` and `ignore_empty` constraints into conformance.

See bufbuild/protovalidate#115
@rodaine rodaine closed this as completed Nov 16, 2023
igor-tsiglyar pushed a commit to igor-tsiglyar/protovalidate that referenced this issue Apr 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants