Skip to content

feat: rust backend apisix standalone - #559

Merged
bzp2010 merged 11 commits into
rust-nextfrom
bzp/feat-rust-backend-apisix-standalone
Aug 6, 2026
Merged

feat: rust backend apisix standalone#559
bzp2010 merged 11 commits into
rust-nextfrom
bzp/feat-rust-backend-apisix-standalone

Conversation

@bzp2010

@bzp2010 bzp2010 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Description

Backend for apisix standalone.

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible

Summary by CodeRabbit

  • New Features

    • Added standalone APISIX support for multiple servers, including configuration discovery, synchronization, validation, and caching.
    • Added support for routes, services, upstreams, consumers, credentials, SSLs, global rules, plugins, and stream routes.
    • Added cache controls, version tracking, and stale-configuration recovery.
  • Bug Fixes

    • Improved synchronization failure reporting when no individual resource event is available.
    • Preserved accurate configuration version and event handling.
  • Tests

    • Added Rust end-to-end coverage for caching, resources, validation, and multi-server synchronization.

@bzp2010 bzp2010 self-assigned this Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67da2450-6613-426b-9f12-43da78ca5569

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@bzp2010 bzp2010 added the test/apisix-standalone Trigger the APISIX standalone test on the PR label Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (12)
rust/crates/adc-sdk/src/backend/mod.rs (1)

44-48: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align the trait documentation with the optional event contract.

BackendSyncResult documents batch/server-level results with event: None, but the Backend::sync documentation still describes failures as per-event results. State that a backend may return one result per batch or server and that server identifies the target. This prevents callers from assuming that every result maps to one input event.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-sdk/src/backend/mod.rs` around lines 44 - 48, Update the
Backend::sync documentation to state that results may be emitted per batch or
server rather than per input event, and clarify that the server field identifies
the target. Keep the existing optional event contract in BackendSyncResult
consistent with this description.
rust/crates/adc-backend-apisix-standalone/Cargo.toml (1)

25-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving dashmap and indexmap to workspace dependencies.

Every other dependency in this manifest uses workspace = true. dashmap and indexmap pin versions inline. If another crate later needs either one, the versions can drift. Declare both in the root [workspace.dependencies] and reference them with workspace = true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/Cargo.toml` around lines 25 - 26,
Move the version declarations for dashmap and indexmap from this crate’s
dependencies into the root [workspace.dependencies] table, then update the
manifest entries for dashmap and indexmap to use workspace = true, matching the
existing workspace dependency pattern.
rust/crates/adc-backend-apisix-standalone/src/backend.rs (1)

69-82: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reject an ambiguous token count instead of silently reusing tokens[0].

paired_tokens is true only when tokens.len() == servers.len(). For any other non-empty count, every server receives tokens[0]. A caller that supplies 2 tokens for 3 servers gets no error here. The third server then rejects the admin API request with a 401, and the cause is not visible from that error.

Accept exactly one token or exactly servers.len() tokens, and reject every other count.

♻️ Proposed change
         let servers_count = opts.servers.len();
+        if opts.tokens.is_empty() {
+            return Err(BackendError::Other(
+                "apisix-standalone backend requires at least one token".into(),
+            ));
+        }
+        if opts.tokens.len() != 1 && opts.tokens.len() != servers_count {
+            return Err(BackendError::Other(format!(
+                "apisix-standalone backend requires either 1 token shared by every server or exactly {servers_count} tokens, got {}",
+                opts.tokens.len()
+            )));
+        }
         // A `token` per `server`, positionally paired, when the two lists
         // are the same length; otherwise every server shares `tokens[0]` —
         // matches the TS backend's own `opts.token.split(',')` convention.
         let paired_tokens = opts.tokens.len() == servers_count;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/src/backend.rs` around lines 69 -
82, Update the token validation in the server construction flow around
paired_tokens to accept only one token or exactly servers_count tokens; reject
all other counts with a BackendError before mapping servers. Preserve positional
token selection for the exact-length case and shared-token behavior for the
single-token case.
rust/crates/adc-backend-apisix-standalone/src/fetcher.rs (1)

83-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

One unreachable server fails the whole dump.

find_latest probes every server and line 87 propagates the first Err it finds. A standalone cluster is deployed as n redundant instances. If one instance is down or slow enough to time out, dump returns an error even though the remaining instances hold a readable config.

The doc comment at lines 62-65 states that this is intentional. Confirm that the TypeScript backend behaves the same way. If it tolerates partial probe failures, consider skipping failed probes and returning Err only when every probe fails.

♻️ Proposed change if partial failures should be tolerated
         let results = concurrent_map(self.servers.clone(), None, probe).await;
 
         let mut latest: Option<(String, i64)> = None;
+        let mut last_error: Option<BackendError> = None;
+        let mut probed = 0usize;
         for result in results {
-            let (server, timestamp) = result?;
+            let (server, timestamp) = match result {
+                Ok(value) => value,
+                Err(error) => {
+                    last_error = Some(error);
+                    continue;
+                }
+            };
+            probed += 1;
             if latest.as_ref().is_none_or(|(_, best)| timestamp >= *best) {
                 latest = Some((server, timestamp));
             }
         }
+        if probed == 0 {
+            if let Some(error) = last_error {
+                return Err(error);
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/src/fetcher.rs` around lines 83 -
91, Update find_latest to tolerate individual probe failures by skipping Err
results from concurrent_map and considering only successful (server, timestamp)
pairs. Track whether any probe succeeded and return an error only when all
probes fail, while preserving the latest-timestamp selection and the documented
behavior consistent with the TypeScript backend.
rust/crates/adc-backend-apisix-standalone/src/transformer.rs (2)

180-227: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

to_adc scans every collection once per service.

For each service, this block iterates input.upstreams twice, input.routes once, and input.stream_routes once. The total cost is O(services × (upstreams + routes + stream_routes)). A standalone document with 500 services and 5000 routes performs 2.5 million comparisons on every dump, and dump also runs after every sync to refresh the cached Configuration.

Build the per-service groupings once before the loop.

♻️ Proposed refactor sketch
+    use std::collections::HashMap;
+
+    let mut routes_by_service: HashMap<&str, Vec<adc::Route>> = HashMap::new();
+    for route in input.routes.iter().flatten() {
+        routes_by_service.entry(route.service_id.as_str()).or_default().push(route_to_adc(route));
+    }
+    let mut stream_routes_by_service: HashMap<&str, Vec<adc::StreamRoute>> = HashMap::new();
+    for route in input.stream_routes.iter().flatten() {
+        stream_routes_by_service
+            .entry(route.service_id.as_str())
+            .or_default()
+            .push(stream_route_to_adc(route));
+    }
+    let upstreams_by_id: HashMap<&str, &typing::Upstream> =
+        input.upstreams.iter().flatten().map(|u| (u.id.as_str(), u)).collect();
+    let mut named_by_service: HashMap<&str, Vec<&typing::Upstream>> = HashMap::new();
+    for upstream in input.upstreams.iter().flatten() {
+        if let Some(owner) = upstream
+            .labels
+            .as_ref()
+            .and_then(|labels| labels.get(typing::ADC_UPSTREAM_SERVICE_ID_LABEL))
+        {
+            named_by_service.entry(owner.as_str()).or_default().push(upstream);
+        }
+    }

Then look each one up by service.id inside the map.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/src/transformer.rs` around lines
180 - 227, Refactor to_adc so the input.upstreams, input.routes, and
input.stream_routes collections are grouped by owning service ID once before the
service map, rather than filtered inside it. Within the service closure, look up
each precomputed grouping using service.id and preserve the existing upstream
conversion, label handling, and route mapping behavior.

145-164: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass the credentials prefix in instead of rebuilding it twice.

Line 153 builds format!("{username}/credentials/"), and line 265 builds the identical string inside a filter closure. The closure version allocates once for every (consumer, credential) pair. The two sites must also stay in agreement, because line 265 selects the credentials and line 154 strips the prefix from them.

Compute the prefix once per consumer and pass it to credential_to_adc.

♻️ Proposed refactor
-fn credential_to_adc(credential: &typing::ConsumerCredential, username: &str) -> Option<adc::ConsumerCredential> {
+fn credential_to_adc(credential: &typing::ConsumerCredential, prefix: &str) -> Option<adc::ConsumerCredential> {
     let plugins = credential.plugins.clone()?;
     let (plugin_name, config) = plugins.into_iter().next()?;
     let config = match config {
         Value::Object(map) => map,
         _ => Map::new(),
     };
 
-    let prefix = format!("{username}/credentials/");
-    let id = credential.id.strip_prefix(&prefix).unwrap_or(&credential.id).to_string();
+    let id = credential.id.strip_prefix(prefix).unwrap_or(&credential.id).to_string();

At the call site:

         .map(|consumer| {
+            let prefix = format!("{}/credentials/", consumer.username);
             let owned: Vec<adc::ConsumerCredential> = credentials
                 .iter()
-                .filter(|credential| credential.id.starts_with(&format!("{}/credentials/", consumer.username)))
-                .filter_map(|credential| credential_to_adc(credential, &consumer.username))
+                .filter(|credential| credential.id.starts_with(&prefix))
+                .filter_map(|credential| credential_to_adc(credential, &prefix))
                 .collect();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/src/transformer.rs` around lines
145 - 164, Compute the credentials prefix once per consumer at the call site and
pass it into credential_to_adc instead of constructing it inside that function.
Update credential_to_adc’s signature and use the supplied prefix for
strip_prefix, while reusing the same prefix in the credential-selection filter
so both paths remain consistent and avoid per-pair allocations.
rust/crates/adc-backend-apisix-standalone/src/typing.rs (1)

245-257: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider defaulting status so one legacy SSL entry cannot fail the whole document.

snis, cert, key, and status have no #[serde(default)]. The document is deserialized as a single unit. If any one SSL entry omits status, the entire ApisixStandalone deserialization fails and Fetcher::dump returns an error for every resource, not just that SSL. Route.status at line 73 is already Option<i64>, so the treatment is inconsistent between the two models.

♻️ Proposed change
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub ssl_protocols: Option<Vec<SslProtocol>>,
 
-    pub status: i64,
+    #[serde(default = "default_ssl_status")]
+    pub status: i64,
 }
+
+fn default_ssl_status() -> i64 {
+    1
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/src/typing.rs` around lines 245 -
257, Add serde default handling for the status field in the SSL model so entries
that omit status deserialize successfully, matching the optional treatment used
by Route.status. Update the status declaration near certs, keys, client, and
ssl_protocols while preserving the existing status type and serialization
behavior.
rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_upstream.rs (1)

85-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Positional assertions on upstreams make the test depend on differ event order.

Lines 86-88 assert that upstreams[0], upstreams[1], and upstreams[2] are the default upstream, nd-upstream1, and nd-upstream2, in that order. That order comes from the order in which Operator::sync applies the differ's events, which is not part of any asserted contract.

Line 136 and line 141 repeat the dependency, and line 148 checks named[0].nodes[0].host without first confirming that named[0] is nd-upstream1. If the order changes, line 148 asserts against the wrong upstream and the test either fails for an unrelated reason or passes for the wrong one.

Look each upstream up by name.

♻️ Proposed change
+fn by_name<'a>(
+    upstreams: &'a [adc_backend_apisix_standalone::tests::typing::Upstream],
+    name: &str,
+) -> &'a adc_backend_apisix_standalone::tests::typing::Upstream {
+    upstreams.iter().find(|u| u.name == name).unwrap_or_else(|| panic!("no upstream named {name}"))
+}
     let upstreams = raw.upstreams.unwrap();
     assert_eq!(upstreams.len(), 3);
-    assert_eq!(upstreams[1].name, "nd-upstream1");
+    let nd1 = by_name(&upstreams, "nd-upstream1");
     assert_eq!(
-        upstreams[1].labels.as_ref().and_then(|l| l.get(ADC_UPSTREAM_SERVICE_ID_LABEL)),
+        nd1.labels.as_ref().and_then(|l| l.get(ADC_UPSTREAM_SERVICE_ID_LABEL)),
         Some(&generate_id("test"))
     );
-    assert_eq!(upstreams[1].nodes.as_ref().unwrap()[0].host, "8.8.8.8");
+    assert_eq!(nd1.nodes.as_ref().unwrap()[0].host, "8.8.8.8");

Apply the same lookup to the ADC-facing named list at lines 145-148.

Also applies to: 133-148

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_upstream.rs`
around lines 85 - 91, Replace positional indexing in the upstream assertions
with name-based lookups, covering both the `upstreams` collection and the
ADC-facing `named` list. Update the checks around the existing upstream
assertions and node-host validation to first locate `test`, `nd-upstream1`, and
`nd-upstream2` by name, then assert their labels and nodes without relying on
event ordering.
.github/workflows/e2e.yaml (1)

144-152: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Set up the Rust toolchain before Swatinem/rust-cache.

The cache key includes the installed Rust toolchains. rustup update stable can change that state after the cache key is created, which causes cache misses and rebuilds. Move the existing rustup update stable and rustup default stable commands before the cache action. The repository has no rust-toolchain pin.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e.yaml around lines 144 - 152, Move the existing rustup
update stable and rustup default stable commands from the Run Rust E2E tests
step to a setup step before Swatinem/rust-cache. Keep the cache action after the
stable toolchain is installed and retain the cargo test command unchanged.
rust/crates/adc-backend-apisix-standalone/src/operator.rs (1)

403-407: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider making Create idempotent.

EventType::Create pushes unconditionally. If the base config already contains an entry with the same id, the collection gets two entries with that id. This can happen when old_raw_config is stale relative to the servers. Replacing an existing entry with the same identity would keep the document well-formed in that case.

♻️ Proposed change
         EventType::Create => {
-            field.get_or_insert_with(Vec::new).push(build()?);
+            let target_id = generate_id_from_event(event)?;
+            let vec = field.get_or_insert_with(Vec::new);
+            match vec.iter_mut().find(|item| identity(item) == target_id) {
+                Some(slot) => *slot = build()?,
+                None => vec.push(build()?),
+            }
             Ok(true)
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/src/operator.rs` around lines 403 -
407, Update the EventType::Create branch in the event handling match to replace
an existing collection entry with the same identity instead of unconditionally
appending build()?; retain the append behavior when no matching entry exists and
preserve the existing Ok(true) result.
rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_global_rule.rs (1)

85-96: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the raw conf-version reader into common.

raw_global_rules_conf_version duplicates raw_consumers_conf_version in rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_consumer.rs (Lines 26-37). Only the JSON key differs. A single helper in tests/common/mod.rs that takes the field name would remove the duplication.

♻️ Proposed helper for tests/common/mod.rs
/// Reads a `*_conf_version` field straight off the admin API — bypasses
/// this crate's own cache entirely.
pub async fn raw_conf_version(field: &str) -> Option<i64> {
    let client = HttpClient::new(HttpClientConfig {
        server: SERVER1.to_string(),
        token: TOKEN.to_string(),
        timeout: None,
        tls: TlsConfig::default(),
    })
    .unwrap();
    let request = client.request(Method::GET, "/apisix/admin/configs").unwrap();
    let body: Value = client.send_json(request).await.unwrap();
    body.get(field).and_then(|v| v.as_i64())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_global_rule.rs`
around lines 85 - 96, Move the shared admin API reading logic from
raw_global_rules_conf_version and raw_consumers_conf_version into a
common::raw_conf_version(field: &str) helper in tests/common/mod.rs. Have the
helper construct the request and return the requested JSON field as Option<i64>,
then replace both resource-specific readers with calls using their respective
field names and remove the duplicated implementations.
rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs (1)

25-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the adc::Upstream and adc::Service fixture builders. Four test files each spell out every field of adc::Upstream and adc::Service. The shared root cause is that tests/common/mod.rs provides event helpers but no resource fixture builders. Each new field added to either SDK struct now forces four edits.

  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs#L25-L64: move base_upstream and base_service into tests/common/mod.rs as pub fn, and import them here.
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_cache.rs#L36-L75: delete the local copies and use the common versions.
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_inline_upstream.rs#L38-L77: delete the local copies; set the pinned SERVICE_NAME with struct update syntax on common::base_service().
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_validate.rs#L49-L99: replace both inline adc::Service/adc::Upstream literals with common::base_service() and common::base_upstream() plus the fields each test actually sets.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs`
around lines 25 - 64, Centralize the adc::Upstream and adc::Service fixture
builders in tests/common/mod.rs as public base_upstream and base_service
functions, then import and reuse them. In
rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs#L25-L64,
move the local builders and import the common versions; in
rust/crates/adc-backend-apisix-standalone/tests/e2e_cache.rs#L36-L75 and
rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_inline_upstream.rs#L38-L77,
remove local copies and use common::base_* (setting SERVICE_NAME via struct
update in the latter); in
rust/crates/adc-backend-apisix-standalone/tests/e2e_validate.rs#L49-L99, replace
inline literals with common::base_service() and common::base_upstream() while
overriding only test-specific fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/adc-backend-apisix-standalone/src/backend.rs`:
- Around line 183-192: Update ApisixStandalone::sync to handle a missing
Cache::global().raw_config entry by re-fetching the current raw document through
the existing Fetcher::dump flow before constructing Operator, rather than using
unwrap_or_default. Preserve the cached document path when present, and propagate
any re-fetch failure as the sync error so an empty base is never PUT to the
servers.
- Around line 127-138: Define a shared constant for the unknown APISIX version
sentinel instead of repeating Version::new(999, 999, 999). Update the
request-selection logic to use GET whenever the parsed version equals that
sentinel, while retaining the existing HEAD behavior for known versions; also
use the constant in the cache guard near the version-fetch flow.

In `@rust/crates/adc-backend-apisix-standalone/src/cache.rs`:
- Around line 80-89: Update Cache::get_live so expired-entry cleanup uses the
concurrent map’s remove_if operation, rechecking the entry’s expiry predicate at
removal time instead of unconditionally deleting by key. Preserve returning None
for the expired lookup and returning a cloned live entry, while ensuring a
concurrently refreshed entry is retained.

In `@rust/crates/adc-backend-apisix-standalone/src/operator.rs`:
- Around line 541-555: Update the Upstream update and delete branches in the
event handling logic to use config.upstreams.as_mut() instead of
get_or_insert_with(Vec::new), returning early when upstreams is None. Preserve
the existing replacement/removal and version-increment behavior when the
collection exists, while keeping absent upstreams as None for serialization and
raw-cache storage.
- Around line 81-105: Update the exit_on_failure branch in the sync method
around concurrent_map_until_err so any error path invalidates the affected cache
entry before propagating the error. Ensure in-flight PUT failures cannot leave
the previous configuration, raw config, or version available to later
non-bypassing dump calls, while preserving the existing successful-result cache
updates.

In `@rust/crates/adc-backend-apisix-standalone/src/typing.rs`:
- Around line 29-35: Correct the documentation for ADC_UPSTREAM_SERVICE_ID_LABEL
to acknowledge this crate’s dependency on adc-backend-apisix, while explaining
that the constant remains duplicated because the wire shapes differ and shared
code is not suitable. Leave the constant and its behavior unchanged.
- Around line 76-90: Update deserialize_upstream_nodes to accept non-empty JSON
objects representing map-form nodes, converting each map entry into the
corresponding UpstreamNode collection format used by the regular APISIX backend.
Preserve the existing None/null handling, normalize an empty object to an empty
vector, and retain array-form deserialization.

---

Nitpick comments:
In @.github/workflows/e2e.yaml:
- Around line 144-152: Move the existing rustup update stable and rustup default
stable commands from the Run Rust E2E tests step to a setup step before
Swatinem/rust-cache. Keep the cache action after the stable toolchain is
installed and retain the cargo test command unchanged.

In `@rust/crates/adc-backend-apisix-standalone/Cargo.toml`:
- Around line 25-26: Move the version declarations for dashmap and indexmap from
this crate’s dependencies into the root [workspace.dependencies] table, then
update the manifest entries for dashmap and indexmap to use workspace = true,
matching the existing workspace dependency pattern.

In `@rust/crates/adc-backend-apisix-standalone/src/backend.rs`:
- Around line 69-82: Update the token validation in the server construction flow
around paired_tokens to accept only one token or exactly servers_count tokens;
reject all other counts with a BackendError before mapping servers. Preserve
positional token selection for the exact-length case and shared-token behavior
for the single-token case.

In `@rust/crates/adc-backend-apisix-standalone/src/fetcher.rs`:
- Around line 83-91: Update find_latest to tolerate individual probe failures by
skipping Err results from concurrent_map and considering only successful
(server, timestamp) pairs. Track whether any probe succeeded and return an error
only when all probes fail, while preserving the latest-timestamp selection and
the documented behavior consistent with the TypeScript backend.

In `@rust/crates/adc-backend-apisix-standalone/src/operator.rs`:
- Around line 403-407: Update the EventType::Create branch in the event handling
match to replace an existing collection entry with the same identity instead of
unconditionally appending build()?; retain the append behavior when no matching
entry exists and preserve the existing Ok(true) result.

In `@rust/crates/adc-backend-apisix-standalone/src/transformer.rs`:
- Around line 180-227: Refactor to_adc so the input.upstreams, input.routes, and
input.stream_routes collections are grouped by owning service ID once before the
service map, rather than filtered inside it. Within the service closure, look up
each precomputed grouping using service.id and preserve the existing upstream
conversion, label handling, and route mapping behavior.
- Around line 145-164: Compute the credentials prefix once per consumer at the
call site and pass it into credential_to_adc instead of constructing it inside
that function. Update credential_to_adc’s signature and use the supplied prefix
for strip_prefix, while reusing the same prefix in the credential-selection
filter so both paths remain consistent and avoid per-pair allocations.

In `@rust/crates/adc-backend-apisix-standalone/src/typing.rs`:
- Around line 245-257: Add serde default handling for the status field in the
SSL model so entries that omit status deserialize successfully, matching the
optional treatment used by Route.status. Update the status declaration near
certs, keys, client, and ssl_protocols while preserving the existing status type
and serialization behavior.

In `@rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_global_rule.rs`:
- Around line 85-96: Move the shared admin API reading logic from
raw_global_rules_conf_version and raw_consumers_conf_version into a
common::raw_conf_version(field: &str) helper in tests/common/mod.rs. Have the
helper construct the request and return the requested JSON field as Option<i64>,
then replace both resource-specific readers with calls using their respective
field names and remove the duplicated implementations.

In
`@rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_upstream.rs`:
- Around line 85-91: Replace positional indexing in the upstream assertions with
name-based lookups, covering both the `upstreams` collection and the ADC-facing
`named` list. Update the checks around the existing upstream assertions and
node-host validation to first locate `test`, `nd-upstream1`, and `nd-upstream2`
by name, then assert their labels and nodes without relying on event ordering.

In `@rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs`:
- Around line 25-64: Centralize the adc::Upstream and adc::Service fixture
builders in tests/common/mod.rs as public base_upstream and base_service
functions, then import and reuse them. In
rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs#L25-L64,
move the local builders and import the common versions; in
rust/crates/adc-backend-apisix-standalone/tests/e2e_cache.rs#L36-L75 and
rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_inline_upstream.rs#L38-L77,
remove local copies and use common::base_* (setting SERVICE_NAME via struct
update in the latter); in
rust/crates/adc-backend-apisix-standalone/tests/e2e_validate.rs#L49-L99, replace
inline literals with common::base_service() and common::base_upstream() while
overriding only test-specific fields.

In `@rust/crates/adc-sdk/src/backend/mod.rs`:
- Around line 44-48: Update the Backend::sync documentation to state that
results may be emitted per batch or server rather than per input event, and
clarify that the server field identifies the target. Keep the existing optional
event contract in BackendSyncResult consistent with this description.
🪄 Autofix

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: 99cc9298-3030-46c7-841e-57cc5cbda928

📥 Commits

Reviewing files that changed from the base of the PR and between b110bfa and c25cd37.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • .github/workflows/e2e.yaml
  • rust/Cargo.toml
  • rust/crates/adc-backend-api7/src/operator.rs
  • rust/crates/adc-backend-apisix-standalone/Cargo.toml
  • rust/crates/adc-backend-apisix-standalone/src/backend.rs
  • rust/crates/adc-backend-apisix-standalone/src/cache.rs
  • rust/crates/adc-backend-apisix-standalone/src/fetcher.rs
  • rust/crates/adc-backend-apisix-standalone/src/lib.rs
  • rust/crates/adc-backend-apisix-standalone/src/operator.rs
  • rust/crates/adc-backend-apisix-standalone/src/transformer.rs
  • rust/crates/adc-backend-apisix-standalone/src/typing.rs
  • rust/crates/adc-backend-apisix-standalone/src/utils.rs
  • rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_cache.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_consumer.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_global_rule.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_inline_upstream.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_upstream.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_validate.rs
  • rust/crates/adc-backend-apisix/src/lib.rs
  • rust/crates/adc-backend-apisix/src/operator.rs
  • rust/crates/adc-backend-apisix/tests/e2e_apisix.rs
  • rust/crates/adc-backend-apisix/tests/e2e_operator.rs
  • rust/crates/adc-backend-apisix/tests/e2e_resource_service_upstream.rs
  • rust/crates/adc-backend-apisix/tests/e2e_sync_and_dump.rs
  • rust/crates/adc-cli/src/main.rs
  • rust/crates/adc-sdk/src/backend/mod.rs

Comment thread rust/crates/adc-backend-apisix-standalone/src/backend.rs
Comment thread rust/crates/adc-backend-apisix-standalone/src/backend.rs
Comment thread rust/crates/adc-backend-apisix-standalone/src/cache.rs
Comment thread rust/crates/adc-backend-apisix-standalone/src/operator.rs
Comment thread rust/crates/adc-backend-apisix-standalone/src/operator.rs Outdated
Comment thread rust/crates/adc-backend-apisix-standalone/src/typing.rs
Comment thread rust/crates/adc-backend-apisix-standalone/src/typing.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs (1)

63-68: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail closed when BACKEND_APISIX_VERSION is unset. CI sets the variable, but the standalone compose file defaults only BACKEND_APISIX_IMAGE to dev. Local runs can therefore use an image whose version does not match 999.999.999, bypassing skip_below_3_17_0!().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs` around lines
63 - 68, Update apisix_version so an unset BACKEND_APISIX_VERSION fails closed
instead of returning semver::Version::new(999, 999, 999). Preserve the existing
invalid-value panic for configured versions, and make the missing-variable path
fail explicitly so skip_below_3_17_0!() cannot be bypassed.
🧹 Nitpick comments (1)
rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs (1)

144-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Complete the shared utility extraction.

rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs still defines local copies of wait_until_ready, raw_conf_version, base_upstream, and base_service at Lines [159]-[188], [214]-[225], [230]-[253], and [257]-[271]. Update that test to use common::* and remove the local copies. Otherwise utility fixes can diverge between test binaries.

Also applies to: 210-271

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs` around lines
144 - 188, Update e2e_resource_service.rs to import and use the shared utilities
from common::* instead of defining local copies of wait_until_ready,
raw_conf_version, base_upstream, and base_service. Remove those four local
definitions while preserving all existing call sites and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_global_rule.rs`:
- Around line 55-58: Update the version assertions in the unchanged global-rules
resync test to require both raw_conf_version results to be present before
comparing values. Unwrap or otherwise validate version_before and version_after,
then compare the resulting i64 values while preserving the existing no-bump
assertion message.

---

Outside diff comments:
In `@rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs`:
- Around line 63-68: Update apisix_version so an unset BACKEND_APISIX_VERSION
fails closed instead of returning semver::Version::new(999, 999, 999). Preserve
the existing invalid-value panic for configured versions, and make the
missing-variable path fail explicitly so skip_below_3_17_0!() cannot be
bypassed.

---

Nitpick comments:
In `@rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs`:
- Around line 144-188: Update e2e_resource_service.rs to import and use the
shared utilities from common::* instead of defining local copies of
wait_until_ready, raw_conf_version, base_upstream, and base_service. Remove
those four local definitions while preserving all existing call sites and
behavior.
🪄 Autofix

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: 995729f0-f575-4e44-84ad-add3e41ccf48

📥 Commits

Reviewing files that changed from the base of the PR and between c25cd37 and bdf1629.

📒 Files selected for processing (13)
  • rust/crates/adc-backend-apisix-standalone/src/backend.rs
  • rust/crates/adc-backend-apisix-standalone/src/cache.rs
  • rust/crates/adc-backend-apisix-standalone/src/operator.rs
  • rust/crates/adc-backend-apisix-standalone/src/transformer.rs
  • rust/crates/adc-backend-apisix-standalone/src/typing.rs
  • rust/crates/adc-backend-apisix-standalone/tests/common/mod.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_cache.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_consumer.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_global_rule.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_inline_upstream.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_upstream.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_validate.rs
🚧 Files skipped from review as they are similar to previous changes (11)
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_validate.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_cache.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_inline_upstream.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_consumer.rs
  • rust/crates/adc-backend-apisix-standalone/src/cache.rs
  • rust/crates/adc-backend-apisix-standalone/src/backend.rs
  • rust/crates/adc-backend-apisix-standalone/src/typing.rs
  • rust/crates/adc-backend-apisix-standalone/src/transformer.rs
  • rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_service_upstream.rs
  • rust/crates/adc-backend-apisix-standalone/src/operator.rs

Comment thread rust/crates/adc-backend-apisix-standalone/tests/e2e_resource_global_rule.rs Outdated
@bzp2010
bzp2010 merged commit 4302449 into rust-next Aug 6, 2026
32 checks passed
@bzp2010
bzp2010 deleted the bzp/feat-rust-backend-apisix-standalone branch August 6, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test/apisix-standalone Trigger the APISIX standalone test on the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant