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
References
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)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 fieldsImpact
Each will throw
RestError::Deserialization(or similar) on real responses with data. Affects: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 bothbootstrap_statusand the optionallocal_node_info(split into a singleBootstrapResponse { status, local_node_info }).For
alerts().list_cluster_alerts(), change return toHashMap<String, AlertState>and reworkAlertStateto match the real shape:{ enabled: bool, severity: String, state: bool, change_time: Option<String>, change_value: Option<Value> }.Add
lib_tests.rsfixture-replay tests for each:let resp: T = serde_json::from_str(include_str!("../tests/fixtures/...json"))?;— would have caught all six.Acceptance criteria
lib_tests.rsReferences