Skip to main content

wekan_cli/commands/
profile.rs

1mod add;
2mod list;
3mod remove;
4mod show;
5mod update;
6mod use_profile;
7
8#[cfg(test)]
9mod tests;
10
11use clap::{Args, Subcommand};
12
13pub use self::{
14    add::AddArgs, list::ListArgs, remove::RemoveArgs, show::ShowArgs, update::UpdateArgs,
15    use_profile::UseArgs,
16};
17use crate::{
18    command_result::CommandSuccess,
19    config::profiles::ProfileStore,
20    credentials::{CredentialError, CredentialStore, CredentialTarget},
21    error::{AppError, ErrorCode, ErrorDetails},
22    exit_code::StableExitCode,
23    input::ConfirmationProvider,
24};
25
26#[derive(Debug, Args)]
27pub struct ProfileArgs {
28    #[command(subcommand)]
29    pub command: ProfileCommand,
30}
31
32#[derive(Debug, Subcommand)]
33pub enum ProfileCommand {
34    /// Add a named Wekan server profile.
35    Add(AddArgs),
36    /// List configured Wekan server profiles.
37    List(ListArgs),
38    /// Show one configured Wekan server profile.
39    Show(ShowArgs),
40    /// Select the active Wekan server profile.
41    Use(UseArgs),
42    /// Change a profile's Wekan server URL.
43    Update(UpdateArgs),
44    /// Remove a Wekan server profile.
45    Remove(RemoveArgs),
46}
47
48pub(crate) fn dispatch(
49    command: ProfileCommand,
50    profile_store: &dyn ProfileStore,
51    credential_store: &dyn CredentialStore,
52    confirmation: &dyn ConfirmationProvider,
53) -> Result<CommandSuccess, AppError> {
54    match command {
55        ProfileCommand::Add(args) => {
56            let name = args.name.clone();
57            with_profile_context(name, add::execute(args, profile_store))
58        }
59        ProfileCommand::List(args) => list::execute(args, profile_store),
60        ProfileCommand::Show(args) => {
61            let name = args.name.clone();
62            with_profile_context(name, show::execute(args, profile_store))
63        }
64        ProfileCommand::Use(args) => {
65            let name = args.name.clone();
66            with_profile_context(name, use_profile::execute(args, profile_store))
67        }
68        ProfileCommand::Update(args) => {
69            let name = args.name.clone();
70            with_profile_context(name, update::execute(args, profile_store, credential_store))
71        }
72        ProfileCommand::Remove(args) => {
73            let name = args.name.clone();
74            with_profile_context(
75                name,
76                remove::execute(args, profile_store, credential_store, confirmation),
77            )
78        }
79    }
80}
81
82fn with_profile_context(
83    name: String,
84    result: Result<CommandSuccess, AppError>,
85) -> Result<CommandSuccess, AppError> {
86    result.map_err(|error| error.with_profile_context(name))
87}
88
89fn ensure_logged_out(
90    credential_store: &dyn CredentialStore,
91    name: &str,
92    server: &str,
93    credential_namespace: &str,
94) -> Result<(), AppError> {
95    let target = CredentialTarget::profile_in_store(name, credential_namespace, server.to_owned());
96    match credential_store.exists(&target) {
97        Ok(false) => Ok(()),
98        Ok(true) => Err(profile_error(
99            ErrorCode::ProfileHasCredential,
100            name,
101            format!(
102                "profile `{name}` has a stored credential; run `wekan --profile {name} auth logout` or `wekan --profile {name} auth logout --local-only` first"
103            ),
104        )),
105        Err(CredentialError::Unavailable(message)) => Err(AppError::new(
106            ErrorCode::CredentialStoreUnavailable,
107            format!("the operating-system credential store is unavailable: {message}"),
108            StableExitCode::Credential,
109        )
110        .with_profile_context(name.to_owned())),
111        Err(error) => Err(AppError::new(
112            ErrorCode::CredentialStoreFailed,
113            format!("the stored credential could not be checked: {error}"),
114            StableExitCode::Credential,
115        )
116        .with_profile_context(name.to_owned())),
117    }
118}
119
120fn profile_not_found(name: &str) -> AppError {
121    profile_error(
122        ErrorCode::ProfileNotFound,
123        name,
124        format!("profile `{name}` does not exist"),
125    )
126}
127
128fn profile_error(code: ErrorCode, name: &str, message: String) -> AppError {
129    AppError::new(code, message, StableExitCode::Configuration).with_details(ErrorDetails {
130        profile: Some(name.to_owned()),
131        ..ErrorDetails::default()
132    })
133}