Skip to content

fix(types): wrapper-object decode bugs on actions/crdb/bdb_groups/bootstrap/proxies/alerts list #62

@joshrotenberg

Description

@joshrotenberg

Summary

Six list/get methods are typed as bare Vec<T> or struct, but the API actually wraps responses in an outer object. They will deserialize-fail on any non-empty real response.

Affected endpoints (verified against tests/fixtures/*.json)

Method Returns today Real shape
actions().list() Vec<Action> {actions: [...], state-machines: [...]}
crdb().list() Vec<Crdb> {crdbs: [...]}
bdb_groups().list() Vec<BdbGroup> {bdb_groups: [...]}
bootstrap().status() BootstrapStatus (flat) {bootstrap_status: {...}, local_node_info: {...}}
alerts().list_cluster_alerts() Vec<Alert> (or similar) HashMap<String, AlertState> (map keyed by alert name)
proxies().list() Vec<Proxy> with required fields wrapper + many fields nullable

Impact

Each will throw RestError::Deserialization (or similar) on real responses with data. Affects:

  • list/inventory operations on the most common entity types
  • bootstrap status checks
  • cluster-wide alerting
  • active-active database management

Static analysis (against fixtures) only — needs live validation, but the fixtures were captured from a real cluster so this is high-confidence.

Suggested fix

Introduce wrapper response types and have the public list()/status() methods unwrap them so the public signature stays ergonomic:

```rust
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct ActionsResponse {
pub actions: Vec,
#[serde(rename = "state-machines")]
pub state_machines: Vec,
}

impl ActionHandler {
pub async fn list(&self) -> Result<Vec> {
let resp: ActionsResponse = self.client.get("/v1/actions").await?;
Ok(resp.actions)
}
}
```

For bootstrap().status(), expose both bootstrap_status and the optional local_node_info (split into a single BootstrapResponse { status, local_node_info }).

For alerts().list_cluster_alerts(), change return to HashMap<String, AlertState> and rework AlertState to match the real shape: { enabled: bool, severity: String, state: bool, change_time: Option<String>, change_value: Option<Value> }.

Add lib_tests.rs fixture-replay tests for each: let resp: T = serde_json::from_str(include_str!("../tests/fixtures/...json"))?; — would have caught all six.

Acceptance criteria

  • All six methods decode their respective fixtures successfully
  • Wrapper types are private to the crate; public signatures unchanged where possible
  • Fixture-replay unit test for each in lib_tests.rs
  • Live validation against a real cluster

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions