//! `issue-edit [--title ] [body sources] [--state s] //! [--milestone id] [repo]` — partial update of an issue. Fields not //! provided are left unchanged. use anyhow::Result; use clap::{Args as ClapArgs, ValueEnum}; use serde_json::{Map, Value, json}; use crate::body; use crate::client::Client; use crate::verbs::print_json; #[derive(Copy, Clone, ValueEnum)] pub enum StateArg { Open, Closed, } impl StateArg { fn as_str(self) -> &'static str { match self { Self::Open => "open", Self::Closed => "closed", } } } #[derive(ClapArgs)] pub struct Args { /// Issue number. number: u64, /// New title (omit to leave unchanged). #[arg(long)] title: Option, /// Inline body text (omit to leave unchanged). #[arg(long, conflicts_with = "body_file")] body: Option, /// Read body from a file. `-` means stdin. #[arg(long = "body-file")] body_file: Option, /// New state. #[arg(long, value_enum)] state: Option, /// Milestone id (0 to unset). #[arg(long)] milestone: Option, } /// # Errors /// /// Propagates any I/O error from the body input (`--body-file`, /// stdin), any transport error from the Forgejo REST call (network /// unreachable, 4xx/5xx response, token missing/invalid), and any /// I/O error from writing the response to stdout. pub fn run(client: &Client, args: Args) -> Result<()> { // Body is partial: only update the body field if a source was // actually given. Piped stdin without --body/--body-file leaves // body alone (the partial-update contract). let body_explicit = args.body.is_some() || args.body_file.is_some(); let body = if body_explicit { body::resolve(args.body.as_deref(), args.body_file.as_deref())? } else { None }; let mut payload = Map::new(); if let Some(t) = args.title { payload.insert("title".into(), Value::String(t)); } if let Some(b) = body { payload.insert("body".into(), Value::String(b)); } if let Some(s) = args.state { payload.insert("state".into(), Value::String(s.as_str().to_owned())); } if let Some(m) = args.milestone { payload.insert("milestone".into(), Value::Number(m.into())); } let repo = client.repo(); let resp = client.patch_json( &format!("/repos/{repo}/issues/{}", args.number), &Value::Object(payload), )?; print_json(&json!({ "number": resp.get("number"), "title": resp.get("title"), "state": resp.get("state"), "milestone": resp.get("milestone").and_then(|m| m.get("title")), })) }