ref:6f41f8e660a56b309339422198e69ce8c01a8c30

fix: parse API response keys correctly for all commands

Anvil API returns resource-specific JSON keys (e.g. "branches", "pull_requests", "ci_runs") not a generic "data" wrapper. Updated all command parsers to try resource-specific keys first, then "data", then flat parse. Also fixed Branch struct to accept "sha" field via serde alias. Verified working against live Anvil instance for branch list, PR list, CI list, issue list, release list, and repo view. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SHA: 6f41f8e660a56b309339422198e69ce8c01a8c30
Author: Cole Christensen <cole.christensen@macmillan.com>
Date: 2026-03-11 02:44
Parents: 54545c1
8 files changed +83 -37
Type
src/commands/agent.rs +18 −7
@@ -89,8 +89,10 @@
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/agents")).await?;
let agents: Vec<serde_json::Value> = if let Some(arr) = resp.get("agents") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
let agents: Vec<serde_json::Value> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -130,7 +132,10 @@
.get(&format!("/{org}/{name}/agents/{agent_name}"))
.await?;
let agent = resp
let agent = resp.get("data").unwrap_or(&resp);
.get("agent")
.or_else(|| resp.get("data"))
.unwrap_or(&resp);
output::header(&format!(
"Agent: {}",
@@ -171,7 +176,8 @@
.await?;
let session_id = resp
.pointer("/session/id")
.or_else(|| resp.pointer("/data/session_id"))
.pointer("/data/session_id")
.or_else(|| resp.get("session_id"))
.and_then(|v| v.as_str())
.unwrap_or("?");
@@ -190,8 +196,10 @@
.get(&format!("/{org}/{name}/agents/{agent_name}/sessions"))
.await?;
let sessions: Vec<serde_json::Value> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
let sessions: Vec<serde_json::Value> = if let Some(arr) = resp.get("sessions") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -230,6 +238,9 @@
.get(&format!("/{org}/{name}/agents/sessions/{id}"))
.await?;
let session = resp
let session = resp.get("data").unwrap_or(&resp);
.get("session")
.or_else(|| resp.get("data"))
.unwrap_or(&resp);
output::header(&format!("Session {}", &id[..8.min(id.len())]));
src/commands/branch.rs +5 −2
@@ -22,5 +22,6 @@
#[derive(Debug, Deserialize)]
struct Branch {
name: Option<String>,
#[serde(alias = "sha")]
target: Option<String>,
}
@@ -37,8 +38,10 @@
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/branches")).await?;
let branches: Vec<Branch> = if let Some(arr) = resp.get("branches") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
let branches: Vec<Branch> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
src/commands/ci.rs +10 −5
@@ -94,8 +94,10 @@
.get_with_query(&format!("/{org}/{name}/ci/runs"), &query_refs)
.await?;
let runs: Vec<CiRun> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
let runs: Vec<CiRun> = if let Some(arr) = resp.get("ci_runs") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -144,8 +146,10 @@
let resp: serde_json::Value = client.get(&format!("/ci/runs/{id}")).await?;
let run: CiRun = if let Some(obj) = resp.get("ci_run") {
serde_json::from_value(obj.clone())?
let run: CiRun = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone())?
} else if let Some(obj) = resp.get("data") {
serde_json::from_value(obj.clone())?
} else {
serde_json::from_value(resp)?
};
@@ -239,7 +243,8 @@
.await?;
let run_id = resp
.pointer("/data/id")
.pointer("/ci_run/id")
.or_else(|| resp.pointer("/data/id"))
.or_else(|| resp.get("id"))
.and_then(|v| v.as_str())
.unwrap_or("?");
src/commands/deploy.rs +8 −4
@@ -87,8 +87,10 @@
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/deployments")).await?;
let deployments: Vec<serde_json::Value> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
let deployments: Vec<serde_json::Value> = if let Some(arr) = resp.get("deployments") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -167,8 +169,10 @@
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/environments")).await?;
let envs: Vec<serde_json::Value> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
let envs: Vec<serde_json::Value> = if let Some(arr) = resp.get("environments") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
src/commands/issue.rs +10 −5
@@ -99,8 +99,10 @@
)
.await?;
let issues: Vec<Issue> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
let issues: Vec<Issue> = if let Some(arr) = resp.get("issues") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -147,8 +149,10 @@
.get(&format!("/{org}/{name}/issues/{number}"))
.await?;
let issue: Issue = if let Some(obj) = resp.get("issue") {
serde_json::from_value(obj.clone())?
let issue: Issue = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone())?
} else if let Some(obj) = resp.get("data") {
serde_json::from_value(obj.clone())?
} else {
serde_json::from_value(resp)?
};
@@ -206,7 +210,8 @@
.await?;
let number = resp
.pointer("/data/number")
.pointer("/issue/number")
.or_else(|| resp.pointer("/data/number"))
.or_else(|| resp.get("number"))
.and_then(|v| v.as_u64())
.unwrap_or(0);
src/commands/pr.rs +10 −5
@@ -104,8 +104,10 @@
)
.await?;
let prs: Vec<PullRequest> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
let prs: Vec<PullRequest> = if let Some(arr) = resp.get("pull_requests") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -143,8 +145,10 @@
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/pulls/{number}")).await?;
let pr: PullRequest = if let Some(obj) = resp.get("pull_request") {
serde_json::from_value(obj.clone())?
let pr: PullRequest = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone())?
} else if let Some(obj) = resp.get("data") {
serde_json::from_value(obj.clone())?
} else {
serde_json::from_value(resp)?
};
@@ -220,7 +224,8 @@
.await?;
let number = resp
.pointer("/data/number")
.pointer("/pull_request/number")
.or_else(|| resp.pointer("/data/number"))
.or_else(|| resp.get("number"))
.and_then(|v| v.as_u64())
.unwrap_or(0);
src/commands/release.rs +8 −4
@@ -104,8 +104,10 @@
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/releases")).await?;
let releases: Vec<Release> = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone()).unwrap_or_default()
let releases: Vec<Release> = if let Some(arr) = resp.get("releases") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -147,8 +149,10 @@
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/releases/{tag}")).await?;
let rel: Release = if let Some(data) = resp.get("data") {
serde_json::from_value(data.clone())?
let rel: Release = if let Some(obj) = resp.get("release") {
serde_json::from_value(obj.clone())?
} else if let Some(obj) = resp.get("data") {
serde_json::from_value(obj.clone())?
} else {
serde_json::from_value(resp)?
};
src/commands/runner.rs +14 −5
@@ -238,7 +238,10 @@
}
let body: serde_json::Value = resp.json().await?;
let data = body.get("data").unwrap_or(&body);
let data = body
.get("runner")
.or_else(|| body.get("data"))
.unwrap_or(&body);
let runner_id = data
.get("runner_id")
.or_else(|| data.get("id"))
@@ -570,8 +573,10 @@
let resp: serde_json::Value = client.get(&path).await?;
let runners: Vec<serde_json::Value> = if let Some(data) = resp.get("data") {
let runners: Vec<serde_json::Value> = if let Some(arr) = resp.get("runners") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
serde_json::from_value(data.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
@@ -619,7 +624,10 @@
let resp: serde_json::Value = client.get(&format!("/runners/{id}")).await?;
let runner = resp
.get("runner")
.or_else(|| resp.get("data"))
.unwrap_or(&resp);
let runner = resp.get("data").unwrap_or(&resp);
output::header(&format!(
"Runner: {}",
@@ -697,7 +705,8 @@
let resp: serde_json::Value = client.post(&path, &serde_json::json!({})).await?;
let token = resp
.pointer("/data/token")
.pointer("/token/value")
.or_else(|| resp.pointer("/data/token"))
.or_else(|| resp.get("token"))
.and_then(|v| v.as_str())
.unwrap_or("?");