Skip to main content

ironflow_cli/commands/
delegation.rs

1//! Approval delegation subcommands: list, create, delete.
2
3use anyhow::{Context, Result};
4use chrono::{DateTime, Utc};
5use clap::{Args, Subcommand};
6use comfy_table::{ContentArrangement, Table};
7use ironflow_sdk::IronflowClient;
8use ironflow_sdk::client::ListApprovalDelegationsFilter;
9use ironflow_sdk::types::{ApprovalDelegationResponse, CreateApprovalDelegationRequest};
10use uuid::Uuid;
11
12use crate::confirm::confirm;
13use crate::output;
14
15/// Arguments for the `delegation` command group.
16#[derive(Debug, Args)]
17pub struct DelegationArgs {
18    /// Delegation subcommand.
19    #[command(subcommand)]
20    pub command: DelegationCommands,
21}
22
23/// Available delegation subcommands.
24#[derive(Debug, Subcommand)]
25pub enum DelegationCommands {
26    /// List the active approval delegations visible to you.
27    List {
28        /// Only delegations granted by this user (admin only).
29        #[arg(long)]
30        from_user: Option<Uuid>,
31        /// Only delegations received by this user (admin only).
32        #[arg(long)]
33        to_user: Option<Uuid>,
34        /// Page number (1-based).
35        #[arg(long)]
36        page: Option<u32>,
37        /// Items per page (max 100).
38        #[arg(long)]
39        per_page: Option<u32>,
40    },
41    /// Delegate your approval power to another user.
42    Create {
43        /// User receiving the delegated approval power.
44        to_user: Uuid,
45        /// End of the window, as an RFC 3339 timestamp.
46        #[arg(long)]
47        until: String,
48        /// Start of the window, as an RFC 3339 timestamp. Defaults to now.
49        #[arg(long)]
50        from: Option<String>,
51        /// Glob restricting the delegation to matching workflow names.
52        #[arg(long)]
53        workflow: Option<String>,
54    },
55    /// Revoke an approval delegation.
56    Delete {
57        /// Delegation ID.
58        id: Uuid,
59        /// Skip the interactive confirmation.
60        #[arg(long)]
61        yes: bool,
62    },
63}
64
65/// Parse an RFC 3339 timestamp, naming the flag it came from on failure.
66fn parse_timestamp(raw: &str, flag: &str) -> Result<DateTime<Utc>> {
67    DateTime::parse_from_rfc3339(raw)
68        .map(|d| d.with_timezone(&Utc))
69        .with_context(|| format!("--{flag} must be an RFC 3339 timestamp, got '{raw}'"))
70}
71
72fn delegations_table(items: &[ApprovalDelegationResponse]) -> Table {
73    let mut table = Table::new();
74    table.set_content_arrangement(ContentArrangement::Dynamic);
75    table.set_header(vec![
76        "ID",
77        "FROM",
78        "TO",
79        "FROM DATE",
80        "UNTIL",
81        "WORKFLOW FILTER",
82    ]);
83    for d in items {
84        let filter = d.workflow_filter.as_deref().unwrap_or("-");
85        table.add_row(vec![
86            d.id.to_string(),
87            d.from_user_id.to_string(),
88            d.to_user_id.to_string(),
89            d.valid_from.to_string(),
90            d.valid_until.to_string(),
91            filter.to_string(),
92        ]);
93    }
94    table
95}
96
97/// Execute a delegation subcommand.
98///
99/// # Errors
100///
101/// Returns an error on API failure, a malformed timestamp, or an unconfirmed
102/// destructive command.
103pub async fn execute(
104    client: &IronflowClient,
105    args: &DelegationArgs,
106    json_mode: bool,
107) -> Result<()> {
108    match &args.command {
109        DelegationCommands::List {
110            from_user,
111            to_user,
112            page,
113            per_page,
114        } => {
115            let filter = ListApprovalDelegationsFilter {
116                from_user_id: *from_user,
117                to_user_id: *to_user,
118                page: *page,
119                per_page: *per_page,
120            };
121            let response = client.list_approval_delegations_filtered(&filter).await?;
122            output::print_output(json_mode, &response, || delegations_table(&response.data))
123        }
124        DelegationCommands::Create {
125            to_user,
126            until,
127            from,
128            workflow,
129        } => {
130            let valid_until = parse_timestamp(until, "until")?;
131            let valid_from = from
132                .as_deref()
133                .map(|raw| parse_timestamp(raw, "from"))
134                .transpose()?;
135            let response = client
136                .create_approval_delegation(&CreateApprovalDelegationRequest {
137                    to_user_id: *to_user,
138                    valid_from,
139                    valid_until,
140                    workflow_filter: workflow.clone(),
141                })
142                .await?;
143            if json_mode {
144                output::print_json(&response)?;
145            } else {
146                println!("Delegation {} created", response.data.id);
147            }
148            Ok(())
149        }
150        DelegationCommands::Delete { id, yes } => {
151            let prompt = format!("Revoke delegation {id}?");
152            confirm(&prompt, *yes)?;
153            client.delete_approval_delegation(*id).await?;
154            output::report_deletion(json_mode, "delegation", id.to_string())
155        }
156    }
157}