Skip to main content

wekan_cli/
app.rs

1use crate::{
2    command_result::CommandSuccess,
3    commands::{
4        self, PreparedCommand, RootCommand,
5        auth::{AuthCommand, PreparedAuthCommand},
6    },
7    config::{ServerSelection, TargetResolver, profiles::FileProfileStore},
8    credentials::{
9        CredentialStore, KeyringCredentialStore, SecretInputProvider, SystemSecretInputProvider,
10    },
11    error::AppError,
12    input::{ConfirmationProvider, SystemConfirmationProvider},
13};
14
15pub struct App<S, P, F = FileProfileStore, C = SystemConfirmationProvider> {
16    target_resolver: TargetResolver,
17    credential_store: S,
18    secret_input: P,
19    profile_store: F,
20    confirmation: C,
21}
22
23impl
24    App<
25        KeyringCredentialStore,
26        SystemSecretInputProvider,
27        FileProfileStore,
28        SystemConfirmationProvider,
29    >
30{
31    pub fn production(selection: ServerSelection) -> Self {
32        Self::production_with_confirmation_interactivity(selection, true)
33    }
34
35    pub fn production_with_confirmation_interactivity(
36        selection: ServerSelection,
37        allow_interactive_confirmation: bool,
38    ) -> Self {
39        Self::with_dependencies(
40            selection,
41            KeyringCredentialStore,
42            SystemSecretInputProvider,
43            FileProfileStore::production(),
44            SystemConfirmationProvider::for_output(allow_interactive_confirmation),
45        )
46    }
47}
48
49impl<S, P, F> App<S, P, F, SystemConfirmationProvider>
50where
51    S: CredentialStore,
52    P: SecretInputProvider,
53    F: crate::config::profiles::ProfileStore,
54{
55    pub const fn with_profile_store(
56        selection: ServerSelection,
57        credential_store: S,
58        secret_input: P,
59        profile_store: F,
60    ) -> Self {
61        Self::with_dependencies(
62            selection,
63            credential_store,
64            secret_input,
65            profile_store,
66            SystemConfirmationProvider::interactive(),
67        )
68    }
69}
70
71impl<S, P, F, C> App<S, P, F, C>
72where
73    S: CredentialStore,
74    P: SecretInputProvider,
75    F: crate::config::profiles::ProfileStore,
76    C: ConfirmationProvider,
77{
78    pub const fn with_dependencies(
79        selection: ServerSelection,
80        credential_store: S,
81        secret_input: P,
82        profile_store: F,
83        confirmation: C,
84    ) -> Self {
85        Self {
86            target_resolver: TargetResolver::new(selection),
87            credential_store,
88            secret_input,
89            profile_store,
90            confirmation,
91        }
92    }
93
94    pub fn with_confirmation_provider<C2>(self, confirmation: C2) -> App<S, P, F, C2>
95    where
96        C2: ConfirmationProvider,
97    {
98        App {
99            target_resolver: self.target_resolver,
100            credential_store: self.credential_store,
101            secret_input: self.secret_input,
102            profile_store: self.profile_store,
103            confirmation,
104        }
105    }
106
107    pub const fn credential_store(&self) -> &S {
108        &self.credential_store
109    }
110
111    pub async fn execute(&self, command: RootCommand) -> Result<CommandSuccess, AppError> {
112        match command {
113            RootCommand::Api(args) => self.execute_api(args).await,
114            RootCommand::Auth(args) => self.execute_auth(args).await,
115            RootCommand::User(args) => self.execute_user(args).await,
116            RootCommand::Board(args) => self.execute_board(args).await,
117            RootCommand::List(args) => self.execute_list(args).await,
118            RootCommand::Card(args) => self.execute_card(args).await,
119            RootCommand::Comment(args) => self.execute_comment(args).await,
120            RootCommand::Swimlane(args) => self.execute_swimlane(args).await,
121            RootCommand::Profile(args) => {
122                if self.target_resolver.has_explicit_target() {
123                    return Err(AppError::invalid_input(
124                        "--server and --profile cannot be used with profile-management commands",
125                    ));
126                }
127                commands::dispatch(
128                    PreparedCommand::Profile { args },
129                    &self.credential_store,
130                    &self.secret_input,
131                    &self.profile_store,
132                    &self.confirmation,
133                )
134                .await
135            }
136        }
137    }
138
139    async fn execute_api(
140        &self,
141        args: crate::commands::api::ApiArgs,
142    ) -> Result<CommandSuccess, AppError> {
143        let target = self.target_resolver.resolve(
144            &self.profile_store,
145            crate::config::MissingProfileResolution::Reject,
146        )?;
147        let profile = target.profile().to_owned();
148        commands::dispatch(
149            PreparedCommand::Api {
150                args,
151                client_factory: target.client_factory(),
152            },
153            &self.credential_store,
154            &self.secret_input,
155            &self.profile_store,
156            &self.confirmation,
157        )
158        .await
159        .map_err(|error| error.with_profile_context(profile))
160    }
161
162    async fn execute_auth(
163        &self,
164        args: crate::commands::auth::AuthArgs,
165    ) -> Result<CommandSuccess, AppError> {
166        let missing_profile_resolution = args.command.missing_profile_resolution();
167        let allow_initialization = missing_profile_resolution.allows_initialization();
168        let mut target = self
169            .target_resolver
170            .resolve(&self.profile_store, missing_profile_resolution)?;
171        let profile = target.profile().to_owned();
172        let prepared = match args.command {
173            AuthCommand::Login(args) => Ok(PreparedAuthCommand::Login {
174                args,
175                target: &mut target,
176            }),
177            AuthCommand::Logout(args) => Ok(PreparedAuthCommand::Logout {
178                args,
179                client_factory: target.client_factory(),
180            }),
181            AuthCommand::Register(args) => Ok(PreparedAuthCommand::Register {
182                args,
183                target: &mut target,
184            }),
185            AuthCommand::Status(args) => Ok(PreparedAuthCommand::Status {
186                args,
187                client_factory: target.client_factory(),
188            }),
189        };
190        let result = match prepared {
191            Ok(command) => {
192                commands::dispatch(
193                    PreparedCommand::Auth { command },
194                    &self.credential_store,
195                    &self.secret_input,
196                    &self.profile_store,
197                    &self.confirmation,
198                )
199                .await
200            }
201            Err(error) => Err(error),
202        };
203        let profile_created = target.profile_created();
204        let profile_active = target.profile_active();
205        result.map_err(|error| {
206            let error = error.with_profile_context(profile);
207            if allow_initialization {
208                error.with_profile_state(profile_created, profile_active)
209            } else {
210                error
211            }
212        })
213    }
214
215    async fn execute_user(
216        &self,
217        args: crate::commands::users::UserArgs,
218    ) -> Result<CommandSuccess, AppError> {
219        args.command.validate()?;
220        let missing_profile_resolution = args.command.missing_profile_resolution();
221        let target = self
222            .target_resolver
223            .resolve(&self.profile_store, missing_profile_resolution)?;
224        let profile = target.profile().to_owned();
225        commands::dispatch(
226            PreparedCommand::User {
227                args,
228                client_factory: target.client_factory(),
229            },
230            &self.credential_store,
231            &self.secret_input,
232            &self.profile_store,
233            &self.confirmation,
234        )
235        .await
236        .map_err(|error| error.with_profile_context(profile))
237    }
238
239    async fn execute_board(
240        &self,
241        args: crate::commands::boards::BoardArgs,
242    ) -> Result<CommandSuccess, AppError> {
243        let missing_profile_resolution = args.command.missing_profile_resolution();
244        let target = self
245            .target_resolver
246            .resolve(&self.profile_store, missing_profile_resolution)?;
247        let profile = target.profile().to_owned();
248        commands::dispatch(
249            PreparedCommand::Board {
250                args,
251                client_factory: target.client_factory(),
252            },
253            &self.credential_store,
254            &self.secret_input,
255            &self.profile_store,
256            &self.confirmation,
257        )
258        .await
259        .map_err(|error| error.with_profile_context(profile))
260    }
261
262    async fn execute_list(
263        &self,
264        args: crate::commands::lists::ListArgs,
265    ) -> Result<CommandSuccess, AppError> {
266        let missing_profile_resolution = args.command.missing_profile_resolution();
267        let target = self
268            .target_resolver
269            .resolve(&self.profile_store, missing_profile_resolution)?;
270        let profile = target.profile().to_owned();
271        commands::dispatch(
272            PreparedCommand::List {
273                args,
274                client_factory: target.client_factory(),
275            },
276            &self.credential_store,
277            &self.secret_input,
278            &self.profile_store,
279            &self.confirmation,
280        )
281        .await
282        .map_err(|error| error.with_profile_context(profile))
283    }
284
285    async fn execute_card(
286        &self,
287        args: Box<crate::commands::cards::CardArgs>,
288    ) -> Result<CommandSuccess, AppError> {
289        let missing_profile_resolution = args.command.missing_profile_resolution();
290        let target = self
291            .target_resolver
292            .resolve(&self.profile_store, missing_profile_resolution)?;
293        let profile = target.profile().to_owned();
294        commands::dispatch(
295            PreparedCommand::Card {
296                args,
297                client_factory: target.client_factory(),
298            },
299            &self.credential_store,
300            &self.secret_input,
301            &self.profile_store,
302            &self.confirmation,
303        )
304        .await
305        .map_err(|error| error.with_profile_context(profile))
306    }
307
308    async fn execute_swimlane(
309        &self,
310        args: crate::commands::swimlanes::SwimlaneArgs,
311    ) -> Result<CommandSuccess, AppError> {
312        let missing_profile_resolution = args.command.missing_profile_resolution();
313        let target = self
314            .target_resolver
315            .resolve(&self.profile_store, missing_profile_resolution)?;
316        let profile = target.profile().to_owned();
317        commands::dispatch(
318            PreparedCommand::Swimlane {
319                args,
320                client_factory: target.client_factory(),
321            },
322            &self.credential_store,
323            &self.secret_input,
324            &self.profile_store,
325            &self.confirmation,
326        )
327        .await
328        .map_err(|error| error.with_profile_context(profile))
329    }
330
331    async fn execute_comment(
332        &self,
333        args: crate::commands::comments::CommentArgs,
334    ) -> Result<CommandSuccess, AppError> {
335        let missing_profile_resolution = args.command.missing_profile_resolution();
336        let target = self
337            .target_resolver
338            .resolve(&self.profile_store, missing_profile_resolution)?;
339        let profile = target.profile().to_owned();
340        commands::dispatch(
341            PreparedCommand::Comment {
342                args,
343                client_factory: target.client_factory(),
344            },
345            &self.credential_store,
346            &self.secret_input,
347            &self.profile_store,
348            &self.confirmation,
349        )
350        .await
351        .map_err(|error| error.with_profile_context(profile))
352    }
353}
354
355#[cfg(test)]
356mod tests;