diff --git a/hive-forge/src/client.rs b/hive-forge/src/client.rs index cff4c592..173a1ce7 100644 --- a/hive-forge/src/client.rs +++ b/hive-forge/src/client.rs @@ -93,6 +93,17 @@ impl Client { decode_json(resp, &format!("GET {url}")) } + /// GET `/` and decode JSON. Returns `None` if the + /// server responds with 404 (resource absent rather than an error). + pub fn get_json_optional(&self, path: &str) -> Result> { + let url = format!("{}{}", self.api(), path); + let resp = self.http.get(&url).send().context("GET")?; + if resp.status() == reqwest::StatusCode::NOT_FOUND { + return Ok(None); + } + decode_json(resp, &format!("GET {url}")).map(Some) + } + /// GET a paginated list endpoint and concatenate all pages. /// `path` should NOT include `page=` (we own it); other query /// params (`?limit=N&state=open&...`) are preserved. Pages drain @@ -160,6 +171,19 @@ impl Client { decode_json(resp, &format!("PATCH {url}")) } + /// PUT a JSON body to `/` and decode the response. + pub fn put_json(&self, path: &str, body: &B) -> Result { + let url = format!("{}{}", self.api(), path); + let resp = self + .http + .put(&url) + .header(CONTENT_TYPE, "application/json") + .json(body) + .send() + .context("PUT")?; + decode_json(resp, &format!("PUT {url}")) + } + /// DELETE `/`. Optional JSON body for endpoints that /// need it (Forgejo's subscription unwatch uses bodyless DELETE). pub fn delete(&self, path: &str, body: Option<&Value>) -> Result<()> { diff --git a/hive-forge/src/verbs/subscription.rs b/hive-forge/src/verbs/subscription.rs index f204f10f..451275db 100644 --- a/hive-forge/src/verbs/subscription.rs +++ b/hive-forge/src/verbs/subscription.rs @@ -3,7 +3,7 @@ use anyhow::Result; use clap::Args as ClapArgs; -use serde_json::{Value, json}; +use serde_json::json; use crate::client::Client; use crate::verbs::print_json; @@ -34,7 +34,8 @@ pub fn run(client: &Client, args: Args) -> Result<()> { } else { (true, false) }; - let resp = client.post_json( + // Forgejo requires PUT (not POST) for this endpoint. + let resp = client.put_json( &format!("/repos/{repo}/subscription"), &json!({ "subscribed": subscribed, "ignored": ignored }), )?; @@ -43,9 +44,13 @@ pub fn run(client: &Client, args: Args) -> Result<()> { "ignored": resp.get("ignored"), })); } - let resp: Value = client.get_json(&format!("/repos/{repo}/subscription"))?; - print_json(&json!({ - "subscribed": resp.get("subscribed"), - "ignored": resp.get("ignored"), - })) + // Forgejo returns 404 when the current user is not watching the repo + // (rather than a response with subscribed=false). + match client.get_json_optional(&format!("/repos/{repo}/subscription"))? { + Some(resp) => print_json(&json!({ + "subscribed": resp.get("subscribed"), + "ignored": resp.get("ignored"), + })), + None => print_json(&json!({ "subscribed": false, "ignored": false })), + } }