Skip to content

Introduce SupportedFieldValidator#6722

Merged
jrhee17 merged 3 commits intoline:mainfrom
jrhee17:feat/xds-support-1
Apr 22, 2026
Merged

Introduce SupportedFieldValidator#6722
jrhee17 merged 3 commits intoline:mainfrom
jrhee17:feat/xds-support-1

Conversation

@jrhee17
Copy link
Copy Markdown
Contributor

@jrhee17 jrhee17 commented Apr 14, 2026

Motivation:

Armeria's xDS implementation supports a subset of envoy proto fields, but there is no
mechanism to detect when a user's xDS configuration sets fields that Armeria silently ignores.
This can lead to confusing behavior where config appears correct but has no effect.

Modifications:

  • Add armeria/xds/supported.proto with custom proto extensions:
    • (armeria.xds.supported) for field options
    • (armeria.xds.supported_value) for enum value options
  • Add SupportedFieldValidator that recursively walks protobuf messages and reports
    any set field lacking the (armeria.xds.supported) = true annotation
    • Uses JSON path format for field paths (e.g., $.edsConfig.serviceName)
    • Passes the root descriptor name, field path, and raw value to the handler
    • Skips external packages (google.protobuf, etc.) to avoid false positives
    • Caches supported field sets per descriptor for performance
  • Add UnsupportedFieldHandler strategy interface with built-in implementations:
    • warn() — logs unsupported fields
    • reject() — throws on first unsupported field
    • ignore() — no-op sentinel enabling early-exit optimization
    • andThen() — composition support
  • Add IgnoreUnsupportedFieldHandler as a package-private enum singleton
  • Add @FunctionalInterface to XdsValidatorIndex
  • Add test proto (test_supported.proto) and SupportedFieldValidatorTest

Result:

  • Provides the validation infrastructure for detecting unsupported xDS fields.
    This PR does not wire the validator into the production path or annotate the
    envoy proto files — those will follow in subsequent PRs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af7cd4b0-3782-4240-a3c8-92499c00ef98

📥 Commits

Reviewing files that changed from the base of the PR and between ac4c6fa and b1a730e.

📒 Files selected for processing (3)
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java
  • xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java
  • xds-api/src/test/proto/armeria/xds/testing/test_supported.proto
✅ Files skipped from review due to trivial changes (3)
  • xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java
  • xds-api/src/test/proto/armeria/xds/testing/test_supported.proto

📝 Walkthrough

Walkthrough

Adds protobuf extensions for marking supported fields/enum values and a new SupportedFieldValidator that traverses protobuf Messages to detect unsupported fields/enum values, delegating reporting to a configurable UnsupportedFieldHandler (warn/reject/ignore). Includes a no-op handler, tests, test protos, and a minor SPI annotation update.

Changes

Cohort / File(s) Summary
Protobuf extensions
xds-api/src/main/proto/armeria/xds/supported.proto
Adds (armeria.xds.supported) on FieldOptions and (armeria.xds.supported_value) on EnumValueOptions.
Validator & logic
xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java
New SupportedFieldValidator that caches supported FieldDescriptors per descriptor, walks Message fields (including maps/repeateds), builds JSONPath-like locations, conditionally recurses into messages, and delegates unsupported findings to a handler.
Handler API
xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java
New @FunctionalInterface with handle(...), andThen(...), and static factories: warn(), reject(), ignore().
No-op handler
xds-api/src/main/java/com/linecorp/armeria/xds/api/IgnoreUnsupportedFieldHandler.java
New enum singleton IgnoreUnsupportedFieldHandler.INSTANCE implementing UnsupportedFieldHandler as a no-op.
Tests & test protos
xds-api/src/test/java/.../SupportedFieldValidatorTest.java, xds-api/src/test/proto/armeria/xds/testing/test_supported.proto
Adds comprehensive unit tests and a test proto verifying supported/unsupported fields, enum value checks, maps/repeateds, recursion rules, handler behaviors (warn/reject/ignore/andThen), and exclusions for well-known wrapper/Any types.
SPI minor update
xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidatorIndex.java
Annotated XdsValidatorIndex with @FunctionalInterface (minor change and copyright year update).

Sequence Diagram(s)

sequenceDiagram
  participant Client as "Client"
  participant Validator as "SupportedFieldValidator\n(runs validation)"
  participant Cache as "Descriptor Cache"
  participant Message as "protobuf.Message"
  participant Handler as "UnsupportedFieldHandler"
  participant Logger as "Logger/Throw"

  Client->>Validator: validate(message)
  Validator->>Message: getDescriptor()/getAllFields()
  Validator->>Cache: lookupSupportedFields(descriptor)
  loop for each field
    Validator->>Validator: build fieldPath
    alt field not supported
      Validator->>Handler: handle(descriptorName, fieldPath, value)
      Handler->>Logger: warn / throw / nop
    else supported and is message/map/enum
      Validator->>Message: recurse into nested message / iterate map entries
      Validator->>Cache: (re)use supported set for nested descriptor
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • trustin
  • minwoox
  • ikhoon

Poem

🐇
I nibble proto leaves and note each path,
Hop over fields that follow the graph.
If unsupported, I whisper or shout —
Or quietly ignore and hop about.
Hooray — validation for every craft!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Introduce SupportedFieldValidator' directly and clearly summarizes the primary change — the introduction of the SupportedFieldValidator class and related infrastructure.
Description check ✅ Passed The description is comprehensive and well-related to the changeset, detailing motivation, modifications, and results that align with the actual changes made across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jrhee17 jrhee17 added this to the 1.39.0 milestone Apr 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java (1)

206-239: These tests don't assert the behaviors their names claim.

rejectFailsFast() only proves that some IllegalArgumentException is thrown, and ignoreEarlyExit() only proves that ignore() suppresses errors. Both still pass if fail-fast ordering or the early-return optimization regresses. Either strengthen the assertions to cover those behaviors, or rename the tests to the weaker contract they actually verify.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java`
around lines 206 - 239, The tests currently only check for any exception or lack
thereof; strengthen them to assert the specific fast-fail/early-exit behaviors:
for rejectFailsFast(), construct a TestCluster that has a top-level unsupported
field (setUnsupportedField("bad")) AND an invalid nested field in
TestOutlierDetection (e.g., setConsecutiveErrors(-1)) then call
SupportedFieldValidator.of(UnsupportedFieldHandler.reject()).validate(cluster)
and assert that an IllegalArgumentException is thrown whose message references
the top-level unsupported field (e.g., "unsupportedField") and does NOT
reference the nested field (e.g., "consecutiveErrors"), proving fail-fast; for
ignoreEarlyExit(), similarly build a cluster with setUnsupportedField("bad") and
an invalid nested outlier (setConsecutiveErrors(-1)), use
SupportedFieldValidator.of(UnsupportedFieldHandler.ignore()) and assert that
validate(cluster) completes without throwing (verifying early exit/skip),
referencing SupportedFieldValidator.validate,
UnsupportedFieldHandler.reject()/ignore(), TestCluster and TestOutlierDetection
to locate the code.
xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java (1)

34-63: Move the logger field into the warn() method to avoid exposing an internal implementation detail through the public interface.

Fields declared in an interface are implicitly public static final, making UnsupportedFieldHandler.logger part of the public API surface even though it is only used internally by warn(). Keep the logger local to the method instead.

♻️ Proposed fix
-    Logger logger = LoggerFactory.getLogger(UnsupportedFieldHandler.class);
-
     /**
      * Returns a handler that logs a warning for each unsupported field path.
      */
     static UnsupportedFieldHandler warn() {
+        final Logger logger = LoggerFactory.getLogger(UnsupportedFieldHandler.class);
         return (descriptorName, fieldPath, value) ->
                 logger.warn("Unsupported xDS field detected in {}: {}", descriptorName, fieldPath);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java`
around lines 34 - 63, The interface currently exposes a public static final
Logger field named logger, leaking an internal implementation detail; move the
logger declaration inside the static factory method warn() so the logger becomes
a private local variable scoped to warn(), update warn() to create and use that
local logger (keep the same log message), and remove the top-level Logger field
from UnsupportedFieldHandler to avoid exposing it via the public interface.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidatorIndex.java`:
- Line 2: Revert the header change so the original copyright year is preserved:
update the top-of-file header in XdsValidatorIndex.java back to the original LY
Corporation year (keep the existing LY header unchanged) instead of modifying it
to 2026; locate the file/class XdsValidatorIndex and restore the prior copyright
line.

---

Nitpick comments:
In
`@xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java`:
- Around line 34-63: The interface currently exposes a public static final
Logger field named logger, leaking an internal implementation detail; move the
logger declaration inside the static factory method warn() so the logger becomes
a private local variable scoped to warn(), update warn() to create and use that
local logger (keep the same log message), and remove the top-level Logger field
from UnsupportedFieldHandler to avoid exposing it via the public interface.

In
`@xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java`:
- Around line 206-239: The tests currently only check for any exception or lack
thereof; strengthen them to assert the specific fast-fail/early-exit behaviors:
for rejectFailsFast(), construct a TestCluster that has a top-level unsupported
field (setUnsupportedField("bad")) AND an invalid nested field in
TestOutlierDetection (e.g., setConsecutiveErrors(-1)) then call
SupportedFieldValidator.of(UnsupportedFieldHandler.reject()).validate(cluster)
and assert that an IllegalArgumentException is thrown whose message references
the top-level unsupported field (e.g., "unsupportedField") and does NOT
reference the nested field (e.g., "consecutiveErrors"), proving fail-fast; for
ignoreEarlyExit(), similarly build a cluster with setUnsupportedField("bad") and
an invalid nested outlier (setConsecutiveErrors(-1)), use
SupportedFieldValidator.of(UnsupportedFieldHandler.ignore()) and assert that
validate(cluster) completes without throwing (verifying early exit/skip),
referencing SupportedFieldValidator.validate,
UnsupportedFieldHandler.reject()/ignore(), TestCluster and TestOutlierDetection
to locate the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a5ec0c61-91b3-4da2-9968-1fea783b6e74

📥 Commits

Reviewing files that changed from the base of the PR and between aabdc8a and 6c61bab.

📒 Files selected for processing (7)
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/IgnoreUnsupportedFieldHandler.java
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java
  • xds-api/src/main/proto/armeria/xds/supported.proto
  • xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java
  • xds-api/src/test/proto/armeria/xds/testing/test_supported.proto
  • xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidatorIndex.java

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (8150425) to head (b1a730e).
⚠️ Report is 423 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main   #6722       +/-   ##
============================================
- Coverage     74.46%       0   -74.47%     
============================================
  Files          1963       0     -1963     
  Lines         82437       0    -82437     
  Branches      10764       0    -10764     
============================================
- Hits          61385       0    -61385     
+ Misses        15918       0    -15918     
+ Partials       5134       0     -5134     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java (1)

131-146: Enum values in map fields are not validated.

When a map field has an enum as its value type, the condition valueField.getJavaType() == FieldDescriptor.JavaType.MESSAGE is false, so the loop is skipped and continue executes immediately. This means enum values within maps don't get checked against (armeria.xds.supported_value).

If this is intentional for v1, consider adding a comment; otherwise, add enum handling for map values.

♻️ Suggested enhancement to validate enum map values
 if (fd.isMapField()) {
     final FieldDescriptor valueField = fd.getMessageType().findFieldByNumber(2);
     if (valueField != null &&
         valueField.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
         int i = 0;
         for (Message mapEntry : (Iterable<Message>) value) {
             final Object mapValue = mapEntry.getField(valueField);
             if (mapValue instanceof Message) {
                 doValidate((Message) mapValue, descriptorName,
                            fieldPath + '[' + i + "].value");
             }
             i++;
         }
+    } else if (valueField != null &&
+               valueField.getJavaType() == FieldDescriptor.JavaType.ENUM &&
+               !unsupportedPackage(valueField.getEnumType().getFile().getPackage())) {
+        int i = 0;
+        for (Message mapEntry : (Iterable<Message>) value) {
+            final EnumValueDescriptor ev = (EnumValueDescriptor) mapEntry.getField(valueField);
+            if (unsupportedEnumValue(ev)) {
+                handler.handle(descriptorName, fieldPath + '[' + i + "].value", ev);
+            }
+            i++;
+        }
     }
     continue;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java`
around lines 131 - 146, The map-field branch in SupportedFieldValidator skips
non-MESSAGE value types, so enum map values aren’t validated; update the map
handling inside SupportedFieldValidator (the block that inspects
fd.isMapField()) to also handle valueField.getJavaType() ==
FieldDescriptor.JavaType.ENUM: iterate the same map entries, extract mapValue
from mapEntry.getField(valueField), detect enum values (e.g.,
EnumValueDescriptor or the enum representation returned by reflection), and
invoke the same enum-validation logic used elsewhere in this class (the code
path used for single enum fields) to check against the
(armeria.xds.supported_value) annotation; keep the existing MESSAGE handling and
only add an ENUM branch before the continue.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java`:
- Around line 131-146: The map-field branch in SupportedFieldValidator skips
non-MESSAGE value types, so enum map values aren’t validated; update the map
handling inside SupportedFieldValidator (the block that inspects
fd.isMapField()) to also handle valueField.getJavaType() ==
FieldDescriptor.JavaType.ENUM: iterate the same map entries, extract mapValue
from mapEntry.getField(valueField), detect enum values (e.g.,
EnumValueDescriptor or the enum representation returned by reflection), and
invoke the same enum-validation logic used elsewhere in this class (the code
path used for single enum fields) to check against the
(armeria.xds.supported_value) annotation; keep the existing MESSAGE handling and
only add an ENUM branch before the continue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bbf528ee-584f-454f-9de2-b8446dd37159

📥 Commits

Reviewing files that changed from the base of the PR and between 6c61bab and ac4c6fa.

📒 Files selected for processing (3)
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java
  • xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java
✅ Files skipped from review due to trivial changes (1)
  • xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java

@jrhee17 jrhee17 marked this pull request as ready for review April 15, 2026 01:02
Copy link
Copy Markdown
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

void handle(String descriptorName, String fieldPath, Object value);

/**
* Returns a composed handler that first invokes this handler, then the {@code after} handler.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted that after won't be invoked if the first handler rejects the unsupported field.

@jrhee17
Copy link
Copy Markdown
Contributor Author

jrhee17 commented Apr 20, 2026

Gentle ping in case this PR was forgotten @minwoox 🙇

Copy link
Copy Markdown
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍

}

private static boolean unsupportedPackage(String pkg) {
return !(pkg.startsWith("envoy.") || pkg.startsWith("xds.") || pkg.startsWith("armeria."));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance that a user wants to customize this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be possible if users want to validate their custom proto, but we can always create an extension point later on rather than prematurely adding APIs.

@jrhee17 jrhee17 merged commit 15e30b8 into line:main Apr 22, 2026
15 of 17 checks passed
jrhee17 added a commit that referenced this pull request May 4, 2026
Motivation:

The `(armeria.xds.supported)` proto annotation was introduced in #6722
to detect unsupported xDS field usage, but the annotation was not yet
applied to the proto field definitions. Additionally,
`XdsValidatorIndex.assertValid()` accepted `Object` instead of the more
type-safe `Message`, and a `StrictXdsValidatorIndex` (which collects all
violations and throws a single exception) was missing for use in test
environments.

Modifications:

- Changed `XdsValidatorIndex.assertValid(Object)` to
`assertValid(Message)` for type safety, and updated all call sites
(`XdsValidatorIndexRegistry`, `XdsResourceValidator`,
`XdsExtensionRegistry`, `DefaultXdsValidatorIndex`).
- Added `protobuf-java` dependency to `xds-validator` module to support
the `Message` parameter type.
- Extracted pgv validation logic from `DefaultXdsValidatorIndex` into a
dedicated `PgvValidator` class.
- Refactored `DefaultXdsValidatorIndex` to compose `PgvValidator` and
`SupportedFieldValidator` (with `warn()` handler).
- Added `StrictXdsValidatorIndex` with priority `1` for test
environments, which collects all unsupported field violations and
reports them in a single `IllegalArgumentException`.
- Annotated supported fields with `(armeria.xds.supported) = true`
across all xDS proto files.

Result:

- In production, `DefaultXdsValidatorIndex` logs warnings for
unsupported xDS fields via `SupportedFieldValidator`.
- In test environments (where `StrictXdsValidatorIndex` is on the
classpath via SPI), unsupported field usage fails fast with a clear
error listing all violations.
- Proto field annotations serve as living documentation of which xDS
fields Armeria actively supports.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants