[BUG](validation) Reject None in validate() and validate_json() - #721
Merged
Merged
Conversation
Seth Fitzsimmons (sethfitz)
temporarily deployed
to
staging
September 3, 2026 20:21 — with
GitHub Actions
Inactive
🗺️ Schema reference docs preview is live!
Note ♻️ This preview updates automatically with each push to this PR. |
Seth Fitzsimmons (sethfitz)
force-pushed
the
fix-validate-none-union-seed
branch
from
September 4, 2026 17:09
f404a7b to
d180cd0
Compare
Seth Fitzsimmons (sethfitz)
temporarily deployed
to
staging
September 4, 2026 17:10 — with
GitHub Actions
Inactive
Seth Fitzsimmons (sethfitz)
force-pushed
the
fix-validate-none-union-seed
branch
from
September 9, 2026 15:47
d180cd0 to
21d2889
Compare
Seth Fitzsimmons (sethfitz)
requested review from
Roel Bollens (RoelBollens-TomTom) and
Victor Schappert (vcschapp)
September 9, 2026 16:05
Roel Bollens (RoelBollens-TomTom)
approved these changes
Sep 9, 2026
Seth Fitzsimmons (sethfitz)
force-pushed
the
fix-validate-none-union-seed
branch
from
September 13, 2026 00:50
21d2889 to
0a25b17
Compare
The non-discriminated union was built with
reduce(or_, non_discriminated_models, None)
whose None initializer made the resulting union None | Segment | ..., so
validate(None) and validate_json("null") returned successfully instead
of raising ValidationError.
Guard the empty case instead, matching what the CLI's equivalent code in
overture-schema-cli already does. Three annotation details follow from
that: the generator is materialized into a tuple because a generator's
truthiness cannot be tested for emptiness; non_discriminated_union
widens to type[BaseModel] | UnionType | None because reduce over a
single-element tuple returns the bare model type; and model_union gets
an explicit type[BaseModel] | UnionType declaration, because mypy infers
a branch variable from its first assignment and so read the narrower
UnionType off the both-present branch.
The non-discriminated bucket is non-empty in practice: Segment is an
Annotated discriminated union rather than a class, so it fails
_can_discriminate's isinstance(model_class, type) gate.
Tests assert both directions -- the two null forms raise, and a genuine
feature still round-trips -- so a regression that broke the adapter into
rejecting everything could not pass the negative tests by accident.
Signed-off-by: Seth Fitzsimmons <seth@mojodna.net>
Victor Schappert (vcschapp)
force-pushed
the
fix-validate-none-union-seed
branch
from
September 16, 2026 03:45
0a25b17 to
d7c0455
Compare
Victor Schappert (vcschapp)
approved these changes
Sep 16, 2026
Victor Schappert (vcschapp)
deleted the
fix-validate-none-union-seed
branch
September 16, 2026 03:50
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #720
validate(None)andvalidate_json("null")currently return successfully. They should raiseValidationError.The non-discriminated union is built with
reduce(or_, non_discriminated_models, None). TheNoneinitializer makes the resulting unionNone | Segment | ..., soNonevalidates as a document. The equivalent code inoverture-schema-cliguards the empty case instead, and rejectsNonecorrectly — this brings the validation package into line with it.The non-discriminated bucket is non-empty in practice:
Segmentis anAnnotateddiscriminated union rather than a class, so it fails_can_discriminate'sisinstance(model_class, type)gate.Three annotation details follow from the change: the generator is materialized into a tuple, because a generator's truthiness can't be tested for emptiness;
non_discriminated_unionwidens totype[BaseModel] | UnionType | None, becausereduceover a single-element tuple returns the bare model type; andmodel_uniongets an explicittype[BaseModel] | UnionTypedeclaration, because mypy infers a branch variable from its first assignment and so read the narrowerUnionTypeoff the both-present branch. mypy caught the second and third.