davidzollo commented on PR #10977:
URL: https://github.com/apache/seatunnel/pull/10977#issuecomment-4598394379
## 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 `seatunnel-api` (`ConfigValidatorTest` 143 + `ConditionTest` 3),
`spotless:check` clean.
### 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 `if/else` inside `*Config.java`, invisible to REST/CLI/Web
UI/AI tooling, and reported one-at-a-time. After this PR:
```java
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();
```
`ConfigValidator` runs every constraint at job-submission time, aggregates
**all** failures (structural + value) into a single `Option validation failed
(N errors): [1] ... [2] ...` message, REST `/option-rules` exposes both the
symbolic `compareOperator` and the structured `conditionOperator` /
`conditionOperatorCategory` so frontends/AI don't need to string-match.
### Verifying the previous round's blockers were fixed
All 8 items I raised in the previous review are addressed:
| # | Previous finding | Status | How |
|---|---|---|---|
| 1 | `optional(start, end, lessThanField(start, end))` threw `Cannot
compare null` when one side missing | **Fixed** | `isConstraintApplicable` now
splits the chain at OR boundaries and requires AND-segments to be fully
present; tests `testOptionalCrossFieldOnlyStartPresent` / `OnlyEndPresent` /
`NoFalsePositive` pin it down |
| 2 | `greaterOrEqual(PORT,1).or(notBlank(HOST))` broke short-circuit when
PORT null | **Fixed** | All numeric/FIELD evaluators are now null-safe (`v !=
null && ...`); covered by `testOrChainNumericNullWithStringFallback` (4
scenarios) |
| 3 | `greaterThan(longOpt, 0)` raised `Numeric type mismatch` because `int`
vs `Long` | **Fixed** | `compareNumberValues` now normalizes via `BigDecimal` /
`double` / `long` three-tier; `testNumericTypeMismatchIntVsLong` /
`DoubleVsInt` cover it |
| 4 | REST `ConditionNode` only had symbol string; AI/frontend forced to
string-match | **Fixed** | Added `conditionOperator` (e.g. `GREATER_OR_EQUAL`)
+ `conditionOperatorCategory` (e.g. `NUMERIC`) |
| 5 | Raw-type `new Condition(...)` factories | **Fixed** | Factories moved
to `Conditions.java` (clean generics); narrow `Condition.java` to data + chain
logic |
| 6 | Evaluator exceptions dropped option key | **Fixed** |
`ConditionEvaluators.evaluate` wraps inner exceptions as `Failed to evaluate
constraint '%s' on option '%s': %s` |
| 7 | `Condition.of(opt, null)` API narrowed without
`incompatible-changes.md` entry | **Fixed** | Entry added in both
`docs/{en,zh}/introduction/concepts/incompatible-changes.md` |
| 8 | Test coverage gaps (optional cross-field single-side, OR with numeric,
type mismatch) | **Fixed** | Test count went from 32 → **143** in
`ConfigValidatorTest`; nearly every scenario I called out has an explicit test |
Bonus improvement that wasn't in the previous review but is genuinely
production-grade: `collectErrors` aggregates structural + value errors into one
`OptionValidationException` instead of fail-fast. Big UX win for users
debugging large HOCON configs.
### New findings on this pass
#### Issue 1 (medium) — Asymmetric cross-field: `optional(head) +
required(compareField)` makes the optional head implicitly required
`isConstraintApplicable` collects `head + compareOption + all chain nodes`
and returns `true` as soon as **any** of them is `AbsolutelyRequiredOptions`.
Consider:
```java
.required(START_TS)
.optional(MAX, lessThanField(MAX, START_TS))
```
- `allOptions = {MAX, START_TS}`
- `START_TS` is absolutely required → first-stage hit → applicable
- `FIELD_LESS_THAN(null, START_TS)` → returns `false`
- Error reported: `constraint: 'max' < 'start_ts'`
User's clear intent: "MAX is optional; if set, must be less than START_TS."
Actual behavior: MAX is silently required. The symmetric case (`required(head)
+ optional(compareField)`) is already pinned down by
`testRequiredPrimaryWithAbsentOptionalCompareField` as "must fail", which is a
consistent but easy-to-trip design choice.
**Suggested fix (Option A, recommended):** keep "required head forces
applicable" but only inspect the **head** node, not the entire option set. That
makes both directions symmetric — the head decides. Adjust
`testRequiredPrimaryWithAbsentOptionalCompareField` accordingly (END_TS is
optional, so the constraint should skip and let structural validation handle
the absence).
**Option B (conservative):** keep both semantics, but add a Javadoc warning
on every cross-field factory in `Conditions.java` stating "if used together
with `optional(head, ...)`, the compareOption must also be optional".
#### Issue 2 (low) — `MetadataExportCommand` didn't pick up
`conditionOperator` / `conditionOperatorCategory`
`OptionRulesService.toConditionNode` now exposes the structured operator
metadata for the REST API, but `MetadataExportCommand.exportCondition` still
only emits `compareOperator` (symbol string). The PR description specifically
calls out AI-assisted config generation, and the CLI export is exactly the
surface AI tools consume offline. Quick fix: mirror the same fields via
`op.name()` / `op.getCategory().name()`.
#### Issue 3 (low) — Docs hint at length/size constraints that the PR didn't
ship
The previous revision had `LENGTH_*`, `COLLECTION_SIZE_*`,
`FIELD_EQUAL/NOT_EQUAL`, `FIELD_SIZE_EQUAL`, etc. Those have been (rightly)
trimmed — the surface is now 17 operators in 4 categories. But the architecture
doc still uses "string length limit" / "fixed collection size" as motivating
examples. Suggest either narrowing the docs or adding a "currently supported
operators" callout in `Conditions.java`'s class-level Javadoc so users know
what's actually available without grepping.
#### Issue 4 (medium) — `OptionValidationException` message format changed;
no `incompatible-changes.md` entry
Old format (per error):
```
ErrorCode:[API-02], ErrorDescription:[Option item validate failed] - There
are unconfigured options, the options('host') are required.
```
New format (aggregated):
```
ErrorCode:[API-02], ErrorDescription:[Option item validate failed] - Option
validation failed (2 errors):
[1] option: 'host'
type: required
constraint: required option is not configured
[2] option: 'port'
type: value
constraint: 'port' >= 1
```
This is strictly a better UX, but any consumer doing
`getMessage().contains("are required")` will break. Connector unit tests doing
string assertions on the message (the PR itself rewrote a handful of them) will
fail. Worth adding a second `incompatible-changes.md` entry next to the
`Condition.of(opt, null)` one.
#### Issue 5 (low) — `Condition.*` static factories were removed without a
deprecated bridge
`Condition.greaterThan(...)`, `Condition.notBlank(...)`, etc. moved to
`Conditions.*`. Anyone using them outside the main repo will get a compile
error. Either:
- Keep `@Deprecated public static <T> Condition<T> greaterThan(...) { return
Conditions.greaterThan(...); }` bridges, **or**
- Document the rename in `incompatible-changes.md` alongside the others.
#### Issue 6 (low) — `compareNumbers` error message includes raw values
```java
throw new OptionValidationException(
"Cannot compare null values in numeric comparison: left=%s, right=%s",
a, b);
```
For numeric options this is fine, but if a connector ever wires up a
sensitive option as `Number`, the raw value leaks into logs. Safer to swap to
`leftType=%s, rightType=%s`.
#### Issue 7 (low) — `Conditions.java` class Javadoc has a truncated example
```java
* <pre>{@code
* static *
* OptionRule.builder()
```
Looks like an incomplete `import static
org.apache.seatunnel.api.configuration.util.Conditions.*;` line.
#### Issue 8 (low) — `extractInnerMessage` couples to
`OptionValidationException` text format
Splitting on `" - "` to strip the `ErrorCode:[API-02] - ` prefix is fine
today, but ties the evaluator wrapper to an implementation detail of the
exception. A `getRawMessage()` accessor on `OptionValidationException` would be
cleaner. Not urgent.
#### Issue 9 (low) — Missing test for the Issue 1 scenario
`testRequiredPrimaryWithAbsentOptionalCompareField` pins down
`required(head) + optional(compareField)`. The mirror case (`optional(head) +
required(compareField)`, head absent) is not explicitly tested. Worth adding
one to make the chosen semantic regression-proof.
### Issue summary
| # | 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.md` block covering (a)
`OptionValidationException` message format change and (b) `Condition.*` static
factory relocation. Or alternatively keep `@Deprecated` bridges on
`Condition.*`.
**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.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]