Skip to content

feat(api7): add server-side configuration validator#432

Merged
bzp2010 merged 7 commits intomainfrom
feat/validate-api7
Apr 18, 2026
Merged

feat(api7): add server-side configuration validator#432
bzp2010 merged 7 commits intomainfrom
feat/validate-api7

Conversation

@jarvis9443
Copy link
Copy Markdown
Contributor

@jarvis9443 jarvis9443 commented Apr 16, 2026

Summary

Add a validate subcommand that validates local ADC configuration against the server-side /apisix/admin/configs/validate API (dry-run, no side effects).

Changes

SDK (libs/sdk/)

  • Add BackendValidationError, BackendValidateResult interfaces
  • Add optional validate?(events: Array<ADCSDK.Event>) => Observable<BackendValidateResult> and supportValidate?() to Backend interface

Backend API7 (libs/backend-api7/)

  • Add Validator class that:
    • Flattens events (including subEvents) and groups by resource type
    • Transforms ADC resources to backend format using FromADC transformers (same as Operator)
    • Builds name index for mapping server error indices back to human-readable resource names
    • POSTs to /apisix/admin/configs/validate with gateway_group_id
  • Add supportValidate() with version gating (>= 3.9.10)
  • Add validate() returning Observable<BackendValidateResult>

CLI (apps/cli/)

  • Add ValidateTask that checks backend support, subscribes to validate observable, and formats errors with resource name/id/index
  • Add ValidateCommand with pipeline: InitBackend → LoadLocal → Lint → DiffResource (empty remote) → Validate
  • Register in command index

E2E Tests (libs/backend-api7/e2e/)

  • 9 test cases: supportValidate check, empty config, valid service+route, valid consumer, invalid plugin, invalid route, multiple errors, mixed resources, dry-run verification
  • Version-gated with conditionalDescribe(semverCondition(gte, '3.9.10'))
  • Tests use DifferV3.diff(config, {}) to generate events from config, matching the CLI pipeline

Usage

# Validate a single file
adc validate -f adc.yaml --backend api7ee --gateway-group default

# Validate multiple files
adc validate -f service-a.yaml -f service-b.yaml --backend api7ee

# Skip lint check
adc validate -f adc.yaml --backend api7ee --no-lint

Add a new 'validate' subcommand that validates local ADC configuration
against the server-side /apisix/admin/configs/validate API. This performs
comprehensive server-side validation (JSON Schema, plugin check_schema,
duplicate ID detection) as a dry-run without persisting any changes.

Changes:
- SDK: Add BackendValidateResult, BackendValidationError interfaces and
  optional validate()/supportValidate() methods to Backend interface
- backend-api7: Implement Validator class that transforms ADC hierarchical
  configuration to flat backend-native format and POSTs to validate endpoint
- backend-api7: Add supportValidate() with version gating (>= 3.9.10)
- CLI: Add ValidateTask with version checking and error formatting
- CLI: Add validate command with -f, --no-lint options
- E2E: Add validate test suite with valid/invalid/multi-error/dry-run cases

APISIX and standalone backend support will follow in a separate PR.
@jarvis9443 jarvis9443 requested a review from bzp2010 as a code owner April 16, 2026 11:35
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds end-to-end backend validation: a new CLI validate command and task, SDK validation types, BackendAPI7 supportValidate()/validate() methods with a Validator HTTP client, and E2E tests covering validation outcomes and dry-run semantics.

Changes

Cohort / File(s) Summary
CLI command
apps/cli/src/command/index.ts, apps/cli/src/command/validate.command.ts
Register new ValidateCommand and add a validate subcommand accepting multi --file and --lint; builds a Listr pipeline to init backend, load/filter local configs, optionally run lint, and invoke validation.
CLI tasks
apps/cli/src/tasks/index.ts, apps/cli/src/tasks/validate.ts
Re-export validate tasks; added ValidateTask that verifies backend.supportValidate(), calls backend.validate(), formats validation errors into a single message, and throws on failure.
SDK types
libs/sdk/src/backend/index.ts
Added BackendValidationError and BackendValidateResult types and extended Backend interface with optional validate() method.
BackendAPI7 core
libs/backend-api7/src/index.ts
Added MINIMUM_VALIDATE_VERSION, supportValidate() (version check via semver), and validate(config) delegating to the new Validator.
BackendAPI7 validator
libs/backend-api7/src/validator.ts
New Validator class that constructs a validation payload, POSTs to /apisix/admin/configs/validate, emits AXIOS_DEBUG events, and returns structured success/error results mapping resource names/IDs and omitting undefined fields.
E2E tests
libs/backend-api7/e2e/validate.e2e-spec.ts
New end-to-end suite (gated semver >= 3.9.10) exercising supportValidate(), success/failure cases, aggregated errors, mixed resource validation, and dry-run (no state mutation).

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Command
    participant Task as Validate Task
    participant Backend as BackendAPI7
    participant Validator as Validator Class
    participant API as APISIX Admin API

    CLI->>Task: run validate with options
    Task->>Backend: supportValidate()
    Backend->>Backend: version() & semver check
    Backend-->>Task: support true/false

    alt supported
        Task->>Backend: validate(config)
        Backend->>Validator: instantiate and call validate(config)
        Validator->>API: POST /apisix/admin/configs/validate (rgba(52,152,219,0.5))
        alt 200 OK
            API-->>Validator: 200 {…}
            Validator-->>Backend: { success: true, errors: [] }
        else 400 Bad Request
            API-->>Validator: 400 { error_msg, errors }
            Validator-->>Backend: { success: false, errorMessage, errors }
        end
        Backend-->>Task: BackendValidateResult
        Task->>Task: format errors & throw if !success
    else not supported
        Task-->>Task: throw unsupported error
    end

    Task-->>CLI: return success or exit with error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • bzp2010
  • guoqqqi
  • juzhiyuan
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning E2E test suite has critical gaps: missing coverage for non-400 HTTP errors, 4 unsupported resource types untested, ValidateTask error paths uncovered, and no CLI-level E2E tests for validate command integration. Add E2E tests for non-400 HTTP errors, all resource types, ValidateTask error paths, and CLI-level validate command integration with real configurations.
✅ Passed checks (3 passed)
Check name Status Explanation
Security Check ✅ Passed Code review confirms validate command implementation secures sensitive data in logging and error handling without exposing credentials or plugin configurations.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(api7): add server-side configuration validator' clearly and concisely describes the main change: adding a server-side configuration validation feature for the API7 backend.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/validate-api7

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

Comment thread libs/backend-api7/e2e/validate.e2e-spec.ts Fixed
Comment thread libs/backend-api7/e2e/validate.e2e-spec.ts Fixed
Copy link
Copy Markdown

@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 (1)
libs/backend-api7/e2e/validate.e2e-spec.ts (1)

10-13: Unused imports: syncEvents, createEvent, deleteEvent.

These utilities are imported but not used in any test case.

♻️ Proposed fix
 import {
   conditionalDescribe,
   generateHTTPSAgent,
   semverCondition,
-  syncEvents,
-  createEvent,
-  deleteEvent,
 } from './support/utils';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/backend-api7/e2e/validate.e2e-spec.ts` around lines 10 - 13, The import
list includes unused symbols syncEvents, createEvent, and deleteEvent; remove
these three identifiers from the import statement (or, if they were intended to
be used, add the appropriate test cases that call syncEvents, createEvent, and
deleteEvent) so the test file no longer contains unused imports—update the
import line where syncEvents, createEvent, deleteEvent are listed to only import
the utilities actually used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/backend-api7/src/validator.ts`:
- Around line 113-118: The current mapping for config.ssls computes sslId as
ssl.id ?? ADCSDK.utils.generateId(ssl.snis?.[0] ?? '') which will call
generateId('') when snis is undefined or an empty array; change the logic in the
block that builds body.ssls (the map that sets sslId and calls
fromADC.transformSSL) to explicitly detect missing/empty ssl.snis and handle it
safely: either require/validate presence of snis (log a warning and skip or
throw), or derive a non-empty id using a safer seed (e.g., use a random/unique
value or serialize the ssl object) instead of ''. Ensure you reference
config.ssls, ssl.snis, ADCSDK.utils.generateId, sslId and fromADC.transformSSL
when making the change so no empty-string seed is passed to generateId.

---

Nitpick comments:
In `@libs/backend-api7/e2e/validate.e2e-spec.ts`:
- Around line 10-13: The import list includes unused symbols syncEvents,
createEvent, and deleteEvent; remove these three identifiers from the import
statement (or, if they were intended to be used, add the appropriate test cases
that call syncEvents, createEvent, and deleteEvent) so the test file no longer
contains unused imports—update the import line where syncEvents, createEvent,
deleteEvent are listed to only import the utilities actually used.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccc2e6a2-01a4-4230-8913-b42d44c6bdbe

📥 Commits

Reviewing files that changed from the base of the PR and between d13f53a and e374db9.

📒 Files selected for processing (8)
  • apps/cli/src/command/index.ts
  • apps/cli/src/command/validate.command.ts
  • apps/cli/src/tasks/index.ts
  • apps/cli/src/tasks/validate.ts
  • libs/backend-api7/e2e/validate.e2e-spec.ts
  • libs/backend-api7/src/index.ts
  • libs/backend-api7/src/validator.ts
  • libs/sdk/src/backend/index.ts

Comment thread libs/backend-api7/src/validator.ts Outdated
@nic-6443 nic-6443 added the test/api7 Trigger the API7 test on the PR label Apr 17, 2026
Comment thread libs/backend-api7/e2e/validate.e2e-spec.ts
Comment thread apps/cli/src/command/validate.command.ts
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 17, 2026

CLA assistant check
All committers have signed the CLA.

@nic-6443
Copy link
Copy Markdown

Usage example demonstrating the batched error output with resource names.

Config with two routes both missing required fields for limit-count:

services:
  - name: qa-multi-err-svc
    upstream:
      scheme: http
      nodes:
        - host: httpbin.org
          port: 80
          weight: 100
    routes:
      - name: qa-multi-err-r1
        uris:
          - /qa-multi-err-1
        plugins:
          limit-count: {}
      - name: qa-multi-err-r2
        uris:
          - /qa-multi-err-2
        plugins:
          limit-count: {}

Output:

Error: Configuration validation failed:
Configuration validation failed
  - [routes, name="qa-multi-err-r1"]: limit-count plugin: (root): count is required
(root): policy is required
(root): time_window is required
  - [routes, name="qa-multi-err-r2"]: limit-count plugin: (root): count is required
(root): policy is required
(root): time_window is required

All violations across all resources are reported in a single pass, each tagged with resource_type and name so users can locate the offending resource directly.

Comment thread libs/sdk/src/backend/index.ts Outdated
Comment on lines +101 to +103
validate?: (
config: ADCSDK.Configuration,
) => Promise<BackendValidateResult>;
Copy link
Copy Markdown
Collaborator

@bzp2010 bzp2010 Apr 17, 2026

Choose a reason for hiding this comment

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

This API signature implies (and in fact does): validation applies to all resources, even those that already exist remotely. I'm not sure if this is a good approach.

I suggest having it accept an Array<ADCSDK.Event>, so we can control at the higher level whether to perform a diff.

All we need to do is pass an empty ADCSDK.Configuration to ctx in the DiffResourceTask. This assumes that there are no resources on the remote side, so the CLI will emit events for the creation of all configurations. This is essentially consistent with the current implementation.

Later, in the API7 backend, we can convert and construct the data structures needed for API calls from these events.

Suggested change
validate?: (
config: ADCSDK.Configuration,
) => Promise<BackendValidateResult>;
validate?: (
events: Array<ADCSDK.Event>,
) => Observable<BackendValidateResult>;

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.

Done in a98878b. Changed validate() to accept Array<ADCSDK.Event> and return Observable<BackendValidateResult>. The validate command now runs DiffResourceTask (with empty remote) before ValidateTask, so events flow through the same pipeline as sync.

f
Signed-off-by: Jarvis <jarvis@api7.ai>
Per reviewer feedback, change validate() to accept Array<ADCSDK.Event>
instead of ADCSDK.Configuration. This aligns with the existing sync()
pattern and allows future diff-then-validate workflows.

The validate command now runs DiffResourceTask (with empty remote config)
before ValidateTask, generating CREATE events for all local resources.
The Validator rebuilds the request body from flattened events using the
same fromADC transformation logic as the Operator.
@nic-6443 nic-6443 requested a review from bzp2010 April 17, 2026 10:19
Copy link
Copy Markdown
Collaborator

@bzp2010 bzp2010 left a comment

Choose a reason for hiding this comment

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

Overall, this PR achieved its intended goals, but I feel the code quality is subpar; perhaps the agent isn't sufficiently familiar with this codebase.
I'll merge it first and then perform some minor refactoring.

return semver.gte(version, MINIMUM_VALIDATE_VERSION);
}

public validate(events: Array<ADCSDK.Event>) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
public validate(events: Array<ADCSDK.Event>) {
public validate(events: Array<ADCSDK.Event>): Observable<BackendValidateResult> {

Comment on lines +233 to +236
public async supportValidate(): Promise<boolean> {
const version = await this.version();
return semver.gte(version, MINIMUM_VALIDATE_VERSION);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I’m wondering if we really need to implement this API. Perhaps it would be sufficient to simply call the validate function unconditionally in the CLI and perform the validation based on the version number within each backend’s validate function.

For backends that don’t currently support this, they can choose not to implement this API, since it’s optional in the Backend interface.

Comment on lines +17 to +25
const supported = await ctx.backend.supportValidate();
if (!supported) {
const version = await ctx.backend.version();
throw new Error(
`Validate is not supported by the current backend version (${version}). Please upgrade to a newer version.`,
);
}

const result = await lastValueFrom(ctx.backend.validate!(ctx.diff));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
const supported = await ctx.backend.supportValidate();
if (!supported) {
const version = await ctx.backend.version();
throw new Error(
`Validate is not supported by the current backend version (${version}). Please upgrade to a newer version.`,
);
}
const result = await lastValueFrom(ctx.backend.validate!(ctx.diff));
if (!ctx.backend.validate)
throw new Error(`Validate is not supported by the current backend.`);
const result = await lastValueFrom(ctx.backend.validate(ctx.diff));

ref: https://github.com/api7/adc/pull/432/changes#r3104831632

@bzp2010 bzp2010 changed the title feat: add validate command for API7 EE backend feat(api7): add server-side configuration validator Apr 18, 2026
@bzp2010 bzp2010 merged commit 633fadd into main Apr 18, 2026
31 checks passed
@bzp2010 bzp2010 deleted the feat/validate-api7 branch April 18, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test/api7 Trigger the API7 test on the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants