Skip to main content

wekan_cli/commands/profile/
remove.rs

1use clap::Args;
2
3use crate::{
4    command_result::{
5        CancellationSuccess, CommandSuccess, DestructiveOperation, ProfileRemoveSuccess,
6    },
7    config::profiles::{ProfileStore, parse_profile_name},
8    credentials::CredentialStore,
9    error::{AppError, ErrorCode},
10    input::{
11        ConfirmationArgs, ConfirmationDecision, ConfirmationProvider, ConfirmationRequest,
12        confirm_or_skip,
13    },
14};
15
16use super::{ensure_logged_out, profile_error, profile_not_found};
17
18#[derive(Debug, Args)]
19pub struct RemoveArgs {
20    #[arg(value_parser = parse_profile_name)]
21    pub name: String,
22    /// Remove an active profile and clear the active selection.
23    #[arg(long)]
24    pub force: bool,
25
26    #[command(flatten)]
27    pub confirmation: ConfirmationArgs,
28}
29
30struct RemovalPreview {
31    revision: u64,
32    server: String,
33    active: bool,
34}
35
36pub(super) fn execute(
37    args: RemoveArgs,
38    profile_store: &dyn ProfileStore,
39    credential_store: &dyn CredentialStore,
40    confirmation: &dyn ConfirmationProvider,
41) -> Result<CommandSuccess, AppError> {
42    let preview = preview_removal(&args, profile_store, credential_store)?;
43    let prompt = if preview.active {
44        format!(
45            "Remove active profile `{}` for {} and clear the active selection?",
46            args.name, preview.server
47        )
48    } else {
49        format!("Remove profile `{}` for {}?", args.name, preview.server)
50    };
51    let request = ConfirmationRequest::new(DestructiveOperation::ProfileRemove, prompt);
52    match confirm_or_skip(&args.confirmation, confirmation, &request)? {
53        ConfirmationDecision::Proceed => {}
54        ConfirmationDecision::Cancelled => {
55            return Ok(CommandSuccess::Cancelled(CancellationSuccess::new(
56                DestructiveOperation::ProfileRemove,
57            )));
58        }
59    }
60
61    let mut mutation = profile_store.lock_mutation().map_err(AppError::from)?;
62    let credential_namespace = profile_store
63        .credential_namespace()
64        .map_err(AppError::from)?;
65    let profile = mutation
66        .document()
67        .profile(&args.name)
68        .ok_or_else(|| profile_not_found(&args.name))?;
69    let server = profile.server().to_owned();
70    let active = mutation.document().active_profile() == Some(args.name.as_str());
71    if preview.revision != mutation.document().revision()
72        || preview.server != server
73        || preview.active != active
74    {
75        return Err(AppError::configuration(format!(
76            "profile `{}` changed while awaiting confirmation; retry the removal",
77            args.name
78        )));
79    }
80    if active && !args.force {
81        return Err(profile_error(
82            ErrorCode::ProfileInUse,
83            &args.name,
84            format!(
85                "profile `{}` is active; switch profiles first or pass --force to remove it and clear the active selection",
86                args.name
87            ),
88        ));
89    }
90
91    ensure_logged_out(credential_store, &args.name, &server, &credential_namespace)?;
92    let mut document = mutation.document().clone();
93    document.remove(&args.name);
94    if active {
95        document.set_active_profile(None);
96    }
97    let active_profile = document.active_profile().map(str::to_owned);
98    mutation.save(document)?;
99    Ok(CommandSuccess::ProfileRemoved(ProfileRemoveSuccess {
100        name: args.name,
101        server,
102        removed: true,
103        active_profile,
104    }))
105}
106
107fn preview_removal(
108    args: &RemoveArgs,
109    profile_store: &dyn ProfileStore,
110    credential_store: &dyn CredentialStore,
111) -> Result<RemovalPreview, AppError> {
112    let snapshot = profile_store.read().map_err(AppError::from)?;
113    let credential_namespace = profile_store
114        .credential_namespace()
115        .map_err(AppError::from)?;
116    let profile = snapshot
117        .document()
118        .profile(&args.name)
119        .ok_or_else(|| profile_not_found(&args.name))?;
120    let server = profile.server().to_owned();
121    let active = snapshot.document().active_profile() == Some(args.name.as_str());
122    if active && !args.force {
123        return Err(profile_error(
124            ErrorCode::ProfileInUse,
125            &args.name,
126            format!(
127                "profile `{}` is active; switch profiles first or pass --force to remove it and clear the active selection",
128                args.name
129            ),
130        ));
131    }
132    ensure_logged_out(credential_store, &args.name, &server, &credential_namespace)?;
133
134    Ok(RemovalPreview {
135        revision: snapshot.document().revision(),
136        server,
137        active,
138    })
139}