[STIP-28][Feature][Zeta] Enhance OptionRule to support rich declarative validation beyond presence checks#10977
[STIP-28][Feature][Zeta] Enhance OptionRule to support rich declarative validation beyond presence checks#10977nzw921rx wants to merge 16 commits into
Conversation
|
@davidzollo @zhangshenghang Can you help review it? Any suggestions would be greatly appreciated. If there are any design issues, you can move on to issue: #10976 😄 |
|
@yzeng1618 Can you help confirm if the support of OptionRulesService to return transform has a negative impact on AI generated configuration? Thank you very much. |
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for working on this. I reviewed the full current head from the real validation path instead of only reading the new utility classes.
What this PR fixes
- User pain: plugin authors can describe whether an option is required, bundled, or conditional today, but they still cannot declaratively say "this value must be > 0" or "field B must be greater than field A" in
OptionRuleitself. - Proposed fix: this PR adds
ConditionOperator,ConditionEvaluators,OptionRule.valueConstraints, and REST metadata exposure so those validations can run during config validation. - One-line summary: the direction is good, but the latest head still has two framework-level correctness holes before it is safe to merge.
Call chain I traced
connector config validate
-> SeaTunnelConfValidateCommand.execute()
-> ConfigValidator.of(readonlyConfig).validate(factory.optionRule())
-> required / optional / conditional checks
-> valueConstraints loop [ConfigValidator.java:174-176]
-> ConditionEvaluators.evaluate(...)
-> ConfigValidator.validateUnknownKeys(readonlyConfig, factory.optionRule(), pluginName)
-> collectDeclaredKeys(rule) [ConfigValidator.java:117-129]
-> validatePaths(...) [ConfigValidator.java:96-114]
Findings
-
Blocker: keys referenced only from
valueConstraintsare still rejected as unknown keys.
Atseatunnel-api/.../ConfigValidator.java:78-93,117-129,collectDeclaredKeys()only collects required / optional options and nested conditional option rules. It does not collectrule.getValueConstraints()references. So a rule like theOptionRuleexample itself (Condition.greaterThan(TIMESTAMP_VALUE, 0)) is accepted byvalidate(), but once the user actually providestimestamp_value,validateUnknownKeys()rejects it as an unknown key. That means part of the new declarative constraint feature cannot be used on the normal config-validation path. -
Blocker: numeric comparison loses correctness for large integers.
Atseatunnel-api/.../ConditionEvaluators.java:215-223, everyNumbercomparison is normalized throughDouble.compare(number.doubleValue(), ...). That is fine for ports or small counters, but it is not safe for largelongvalues such as timestamps / offsets / IDs beyond the2^53precision boundary. In those cases different integers can collapse to the samedouble, sogreaterThan/lessThan/ field-to-field comparisons can silently return the wrong answer. -
Non-blocking: the public Javadoc example calls a method that does not exist.
seatunnel-api/.../OptionRule.java:67-74usesCondition.range(TIMEOUT, 1000, 60000), but there is noCondition.range(...)factory in the codebase. That will mislead the next caller even though it does not affect runtime behavior.
Tests and CI
The new tests cover many happy-path constraint cases, which is a good step. What is still missing is:
- a regression test for
validateUnknownKeys()+valueConstraints - a regression test for large-integer comparison precision
The good news is the current GitHub Build is green, so the remaining blockers here are source-level correctness blockers, not CI noise.
Merge conclusion
Conclusion: can merge after fixes.
Must fix before merge
- Finding 1: include
valueConstraints-referenced options (and compare-field options when present) in the declared-key set used byvalidateUnknownKeys(). - Finding 2: stop normalizing every
Numberthroughdoublefor ordering comparisons.
Nice to clean up
- Finding 3: update the
OptionRuleexample to a real API shape.
Happy to re-review once you push the next revision.
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for the update. I re-reviewed the latest head from scratch against the full current validation path.
What this PR solves
- User pain: connector authors can declare required/optional/exclusive relationships today, but cannot describe richer value constraints directly in
OptionRule. - Fix approach: add declarative
ConditionOperator/ConditionEvaluatorssupport, wire it intoConfigValidator, and expose the metadata through the REST response. - One-line summary: the two framework-level blockers from my previous round are fixed on the latest head, and I did not find a new blocking issue on the current code.
Runtime path I checked
connector config validate
-> ConfigValidator.validate(rule)
-> validate required/optional/single-choice rules
-> validate valueConstraints
-> ConditionEvaluators.evaluate(...)
-> ConfigValidator.validateUnknownKeys(...)
-> collectDeclaredKeys(rule)
-> includes valueConstraints + compareOption keys
Re-review result
- The old unknown-key blocker is fixed:
collectDeclaredKeys()now includes value-constraint keys and compare-option keys, so constraint-only options are no longer rejected as unknown. - The old large-integer blocker is fixed for the real timestamp/offset-style path I called out before:
compareNumbers()now avoids routingLongcomparisons throughdouble. - The Javadoc example was also corrected to use real APIs.
Tests / CI
- The new regression coverage around
ConfigValidatorTestis much better now. - The current GitHub
Buildis green. - I did not see a flaky-test pattern in the added UTs.
Conclusion: can merge
- Blocking items
- None from my side on the latest head.
- Suggested non-blocking follow-up
- If this condition DSL grows further later, it may be worth adding one focused regression around decimal-style numeric comparison semantics as a future hardening step, but that is not a blocker for this PR.
I took a rough look at the CLI (#10789) consumption side and wanted to double-check two points with you — I may not have the full picture, so please help confirm. |
|
Thanks for checking the downstream CLI side as well. Your read is consistent with what I see on the current PR head Since there is still no new commit after the latest Daniel review on this head, I am keeping this as a reply-only follow-up rather than reopening a full review. From Daniel's side I am not adding a new blocker based on this CLI discussion on the current revision. |
…t and make it support transform
…and Long comparison precision
9efeec6 to
2678687
Compare
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for the update. I re-reviewed the latest head from scratch again against the full current validation path.
What this PR solves
- User pain:
OptionRulecan describe presence rules today, but not richer constraints like> 0orfield A < field Bin a declarative way. - Fix approach: add
ConditionOperator/ConditionEvaluators, wirevalueConstraintsintoConfigValidator, and expose the constraint metadata through REST. - One-line summary: the two framework-level blockers from my earlier rounds are fixed on the latest head, and I did not find a new blocking issue.
Runtime path I checked
connector config validate
-> ConfigValidator.validate(rule)
-> required / optional / conditional checks
-> valueConstraints checks
-> ConditionEvaluators.evaluate(...)
unknown key check
-> ConfigValidator.validateUnknownKeys(...)
-> collectDeclaredKeys(rule)
-> now includes valueConstraints + compareOption keys
REST metadata exposure
-> OptionRulesService
-> OptionRuleResponse
Re-review result
- The old unknown-key blocker is fixed: keys referenced only from
valueConstraintsare now included in the declared-key set. - The old large-integer precision blocker is fixed:
Longcomparisons no longer route throughdouble. - The new docs/tests line up with the current runtime path from what I checked.
Tests / stability
- The updated unit coverage is much stronger now.
- I did not see a flaky-test pattern in the added or changed tests.
- The current GitHub
Buildis still running, but I do not have a source-level blocker to add on top of the latest code.
Conclusion: can merge
- Blocking items
- None from my side on the latest head.
- Suggested non-blocking follow-up
- If this DSL grows further later, it may be worth documenting the intended behavior for mixed numeric types more explicitly, but that is not a blocker for this PR.
From the code-review side this looks good now.
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for the update. I re-reviewed the latest head from scratch again, this time focusing on the actual delta after my June 1 review instead of assuming the earlier LGTM still held.
What problem this PR solves
- User pain:
OptionRulecan express presence rules today, but not richer value-level constraints like> 0,not blank, orstart < endas part of the contract itself. - Fix approach: add declarative
Condition/ConditionOperator/Conditionssupport, run those constraints throughConfigValidator, and expose the metadata through the REST option-rule path. - One-line summary: this turns value validation into a first-class part of the SeaTunnel config contract instead of leaving it scattered in ad hoc runtime checks.
Full runtime path I checked
plugin defines OptionRule
-> required / optional / conditional declarations
-> valueConstraints attached to the rule
user config validation
-> ConfigValidator.validate(rule)
-> required / optional / conditional checks
-> valueConstraints checks
-> ConditionEvaluators.evaluate(...)
-> numeric / string / collection / field-to-field evaluation
unknown-key guard
-> ConfigValidator.validateUnknownKeys(...)
-> collectDeclaredKeys(rule)
-> collectConditionKeys(condition)
REST metadata exposure
-> OptionRulesService
-> OptionRuleResponse
Core logic re-review
I rechecked the current implementations in:
seatunnel-api/.../Condition.javaseatunnel-api/.../ConditionEvaluators.javaseatunnel-api/.../ConditionOperator.javaseatunnel-api/.../ConfigValidator.javaseatunnel-api/.../OptionRule.javaseatunnel-engine/.../OptionRulesService.javaseatunnel-engine/.../OptionRuleResponse.java
The two framework-level blockers I raised in earlier rounds still remain fixed on the current head:
- keys referenced only from
valueConstraintsare still collected into the declared-key set, so they are no longer rejected byvalidateUnknownKeys(); - large integer comparisons still avoid the old “force everything through
double” precision problem.
The latest delta is mostly API cleanup (Condition + Conditions usage), broader ConfigValidatorTest coverage, and doc refresh. After tracing the real validation path again, I did not find a new source-level correctness blocker in the current code.
Compatibility / side effects
- Compatibility: fully compatible from my review perspective. This is additive behavior, not a breaking change to existing option definitions.
- Performance / side effects: the extra work stays on the config-validation path, so I do not see a CPU / memory / concurrency blocker.
- Error handling: the wrapped validation messages in
ConditionEvaluators.evaluate()are now easier to diagnose.
Tests / stability
- The expanded UT coverage is materially better now, especially around declared-key collection, cross-field comparisons, and applicability rules.
- I did not see a flaky-test pattern in the new unit tests. No
Thread.sleep, weak async assertions, or environment-sensitive resource usage showed up in the added test code. - Stability rating: Stable.
Existing review context
I also checked the current community context again. I do not have another maintainer/reviewer blocker to add on top of the latest code, and my current conclusion is still aligned with the earlier “source path looks good” direction.
Current blocker outside the source path
The remaining gate is CI, not a new code-path issue from my side:
- GitHub still shows
Buildas failed on the current head. - From the current check metadata I could confirm at least
Run / unit-test (11, ubuntu-latest)failed, and several sibling lanes were cancelled afterward. - When I checked, the contributor-side workflow was still not exposing the final failed-step log yet, so I cannot honestly pin that failure to a specific new source regression from this revision.
Merge conclusion
Conclusion: can merge after fixes.
- Blocking items
- Please get the current
Buildback to green first, starting with the failingRun / unit-test (11, ubuntu-latest)lane.
- Suggested follow-up
- Once that failing lane exposes its final stack trace, we can tell whether this is just CI noise or a real regression from the latest revision. From the code-review side, I do not have a new blocker beyond the red build gate.
Overall, the current head still looks coherent on the real validation path, and the previous Daniel correctness blockers remain closed.
Re-review (after commits 728e3ee...894091d)Apologies for being less thorough on the first pass — re-evaluating the whole PR from scratch as required, and acknowledging that I had under-credited the depth of the rewrite. Local verification this time: 146/146 tests pass in What this PR really does (concrete example)Today, "port must be in [1, 65535]" or "start_ts must be less than end_ts" lives as imperative import static org.apache.seatunnel.api.configuration.util.Conditions.*;
OptionRule.builder()
.required(PORT, greaterOrEqual(PORT, 1).and(lessOrEqual(PORT, 65535)))
.required(HOST, notBlank(HOST))
.required(START_TS, END_TS, lessThanField(START_TS, END_TS))
.build();
Verifying the previous round's blockers were fixedAll 8 items I raised in the previous review are addressed:
Bonus improvement that wasn't in the previous review but is genuinely production-grade: New findings on this passIssue 1 (medium) — Asymmetric cross-field:
|
| # | Issue | Location | Severity |
|---|---|---|---|
| 1 | optional(head) + required(compareField) implicitly forces head to be required |
ConfigValidator#isConstraintApplicable |
medium |
| 4 | OptionValidationException message format change not documented |
ConfigValidator#validate(OptionRule) |
medium |
| 2 | MetadataExportCommand doesn't expose conditionOperator / conditionOperatorCategory |
MetadataExportCommand#exportCondition |
low |
| 3 | Docs imply length/size constraints that aren't shipped | Conditions.java, configuration-and-option-system docs |
low |
| 5 | Condition.* static factories removed without @Deprecated bridges or doc |
Condition.java |
low |
| 6 | compareNumbers error message leaks raw values |
ConditionEvaluators#compareNumbers |
low |
| 7 | Conditions.java class Javadoc has a truncated example |
Conditions.java |
low |
| 8 | extractInnerMessage couples to exception text format |
ConditionEvaluators#extractInnerMessage |
low |
| 9 | Missing test for optional(head) + required(compareField) + head absent |
ConfigValidatorTest |
low |
Verification commands
| Command | Result | Note |
|---|---|---|
./mvnw -B -pl seatunnel-api spotless:check -nsu -Dmaven.gitcommitid.skip=true |
PASS | clean |
./mvnw -B -pl seatunnel-api test -Dtest=ConfigValidatorTest,ConditionTest -nsu -Dmaven.gitcommitid.skip=true |
PASS (146/146) | |
./mvnw -B -pl seatunnel-engine/seatunnel-engine-server test -Dtest=OptionRulesServiceTest -am ... |
not run | seatunnel-config-shade failed to compile (missing ConfigValue/Entry symbols, unrelated to this PR — typical when shade-relocate hasn't run) |
git grep "Condition\.of\([^,]*, *null" in main repo |
none | no current production callers, backward-compat risk for Condition.of(opt, null) narrowing is effectively zero |
Conclusion: ready to merge after two doc additions; the rest can be follow-ups
No runtime blockers remain. The remaining 9 items are either documentation gaps (4, 5), low-severity polish (2, 3, 6, 7, 8, 9), or one semantic edge case that I think deserves discussion before code change (1).
Recommended pre-merge (≈30 min):
- Add a single
incompatible-changes.mdblock covering (a)OptionValidationExceptionmessage format change and (b)Condition.*static factory relocation. Or alternatively keep@Deprecatedbridges onCondition.*.
Recommended follow-up PR:
- Decide on Issue 1's semantic (Option A: head-only
isConstraintApplicable, or Option B: Javadoc warning on cross-field factories). - Mirror the structured operator metadata in
MetadataExportCommand(Issue 2). - Tighten docs scope to currently-shipped operators (Issue 3).
- Polish 6/7/8 + add the missing test from Issue 9.
Overall assessment
This is a meaningful step up from the previous revision. The contributor turned every one of the 8 prior findings into a precise fix backed by tests, plus added error aggregation and structured REST metadata that weren't requested but are genuinely production-grade. The 17-operator scope is a deliberate, healthy narrowing relative to the prior 30 — better to ship a tight set with full evaluator + REST + AI parity than 30 operators in inconsistent states.
Solid work. Looking forward to seeing this land.
|
@davidzollo Thank you for your review. I have made the following fixes based on the results, and welcome your review at any time
|
…le validation messages
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for the update. I re-reviewed the latest head from scratch again against the full current validation path.
What this PR solves
- User pain:
OptionRulecan describe presence rules today, but not richer constraints like> 0orfield A < field Bin a declarative way. - Fix approach: add
ConditionOperator/ConditionEvaluators, wirevalueConstraintsintoConfigValidator, and expose the constraint metadata through REST. - One-line summary: the two framework-level blockers from my earlier rounds are fixed on the latest head, and I did not find a new blocking issue.
Runtime path I checked
connector config validate
-> ConfigValidator.validate(rule)
-> required / optional / conditional checks
-> valueConstraints checks
-> ConditionEvaluators.evaluate(...)
unknown key check
-> ConfigValidator.validateUnknownKeys(...)
-> collectDeclaredKeys(rule)
-> now includes valueConstraints + compareOption keys
REST metadata exposure
-> OptionRulesService
-> OptionRuleResponse
Re-review result
- The old unknown-key blocker is fixed: keys referenced only from
valueConstraintsare now included in the declared-key set. - The old large-integer precision blocker is fixed:
Longcomparisons no longer route throughdouble. - The new docs/tests line up with the current runtime path from what I checked.
Tests / stability
- The updated unit coverage is much stronger now.
- I did not see a flaky-test pattern in the added or changed tests.
- The current GitHub
Buildis still queued, but I do not have a source-level blocker to add on top of the latest code.
Conclusion: can merge
- Blocking items
- None from my side on the latest head.
- Suggested non-blocking follow-up
- If this DSL grows further later, it may be worth documenting the intended behavior for mixed numeric types more explicitly, but that is not a blocker for this PR.
From the code-review side this looks good now.
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for the update. I re-reviewed the latest head from scratch again against the full current validation path, including the new test-only follow-up commit.
What this PR solves
- User pain:
OptionRulecan describe presence rules today, but not richer declarative value constraints like> 0,not blank, orfield A < field B. - Fix approach: add
ConditionOperator/ConditionEvaluators, wirevalueConstraintsintoConfigValidator, and expose the metadata through the REST option-rule path. - One-line summary: the framework-level correctness blockers from my earlier rounds remain fixed on the current head, and the latest commit only tightens the validation test assertion.
Runtime chain I checked
plugin defines OptionRule
-> required / optional / conditional declarations
-> valueConstraints attached to the rule
user config validation
-> ConfigValidator.validate(rule)
-> required / optional / conditional checks
-> valueConstraints checks
-> ConditionEvaluators.evaluate(...)
REST metadata exposure
-> OptionRulesService
-> OptionRuleResponse
Re-review result
ConfigValidator.java,ConditionEvaluators.java,OptionRule.java, and the REST metadata path still line up with the intended design.- The previous Daniel blockers around declared-key collection and large-integer comparison remain fixed on the current head.
- The latest commit only changes
SeaTunnelConfValidateCommandTest.java:101-104to assert the current unified validation message shape, which is consistent with the runtime behavior.
Tests / stability
- The updated UT coverage remains strong.
- I do not see a flaky-test pattern in the changed tests.
- The current PR checks are green from the metadata I reviewed.
Conclusion: can merge
- Blocking items
- None from my side on the latest head.
- Suggested non-blocking follow-up
- None.
From the code-review side this looks good to merge.
Purpose
Closes #10976
Description
The current
OptionRule+Conditionsystem only supports presence checks and equality conditions. This forces connector developers to write imperativeif/elsevalidation scattered across*Config.javaandSource/Sinkconstructors — invisible to REST API, CLI, Web UI,--checkoffline validation, and AI-assisted config generation.This PR enhances the existing
required()/optional()/conditional()methods withCondition-accepting overloads so that value constraints can be declared directly inoptionRule().What's changed
ConditionOperatorenum (new) — 30 operators with self-describing metadata (category,arity,source,displaySymbol), covering:>,>=,<,<=is not blank,starts with,ends with,contains,matches,is uppercase,is lowercaselength ==,length >=,length <=is not empty,has unique elements,size ==,size >=,size <=< [field],<= [field],> [field],>= [field],== [field],!= [field],size == [field]Conditionclass — 27 new static factory methods + defensive constructor validation (null operator, missingcompareOptionfor FIELD ops, missingexpectValuefor BINARY+LITERAL ops) + circular chain detection inand()/or().OptionRule.Builder— 6 new overloads:required(Option, Condition, Condition...)required(Option, Option, Condition, Condition...)optional(Option, Condition, Condition...)optional(Option, Option, Condition, Condition...)conditional(Option, T, Condition, Condition...)conditional(Option, T, Option, Option, Condition, Condition...)Usage example
Test Plan