Skip to main content

wekan_cli/
app.rs

1use crate::{
2    command_result::CommandSuccess,
3    commands::{self, RootCommand},
4    config::{ServerSelection, TargetResolver, profiles::FileProfileStore},
5    credentials::{
6        CredentialStore, KeyringCredentialStore, SecretInputProvider, SystemSecretInputProvider,
7    },
8    error::AppError,
9    input::{ConfirmationProvider, SystemConfirmationProvider},
10};
11
12pub struct App<S, P, F = FileProfileStore, C = SystemConfirmationProvider> {
13    target_resolver: TargetResolver,
14    credential_store: S,
15    secret_input: P,
16    profile_store: F,
17    confirmation: C,
18}
19
20impl
21    App<
22        KeyringCredentialStore,
23        SystemSecretInputProvider,
24        FileProfileStore,
25        SystemConfirmationProvider,
26    >
27{
28    pub fn production(selection: ServerSelection) -> Self {
29        Self::production_with_confirmation_interactivity(selection, true)
30    }
31
32    pub fn production_with_confirmation_interactivity(
33        selection: ServerSelection,
34        allow_interactive_confirmation: bool,
35    ) -> Self {
36        Self::with_dependencies(
37            selection,
38            KeyringCredentialStore,
39            SystemSecretInputProvider,
40            FileProfileStore::production(),
41            SystemConfirmationProvider::for_output(allow_interactive_confirmation),
42        )
43    }
44}
45
46impl<S, P, F> App<S, P, F, SystemConfirmationProvider>
47where
48    S: CredentialStore,
49    P: SecretInputProvider,
50    F: crate::config::profiles::ProfileStore,
51{
52    pub const fn with_profile_store(
53        selection: ServerSelection,
54        credential_store: S,
55        secret_input: P,
56        profile_store: F,
57    ) -> Self {
58        Self::with_dependencies(
59            selection,
60            credential_store,
61            secret_input,
62            profile_store,
63            SystemConfirmationProvider::interactive(),
64        )
65    }
66}
67
68impl<S, P, F, C> App<S, P, F, C>
69where
70    S: CredentialStore,
71    P: SecretInputProvider,
72    F: crate::config::profiles::ProfileStore,
73    C: ConfirmationProvider,
74{
75    pub const fn with_dependencies(
76        selection: ServerSelection,
77        credential_store: S,
78        secret_input: P,
79        profile_store: F,
80        confirmation: C,
81    ) -> Self {
82        Self {
83            target_resolver: TargetResolver::new(selection),
84            credential_store,
85            secret_input,
86            profile_store,
87            confirmation,
88        }
89    }
90
91    pub fn with_confirmation_provider<C2>(self, confirmation: C2) -> App<S, P, F, C2>
92    where
93        C2: ConfirmationProvider,
94    {
95        App {
96            target_resolver: self.target_resolver,
97            credential_store: self.credential_store,
98            secret_input: self.secret_input,
99            profile_store: self.profile_store,
100            confirmation,
101        }
102    }
103
104    pub const fn credential_store(&self) -> &S {
105        &self.credential_store
106    }
107
108    pub async fn execute(&self, command: RootCommand) -> Result<CommandSuccess, AppError> {
109        if let RootCommand::Skill(args) = command {
110            return commands::skill::dispatch(args.command);
111        }
112        if let RootCommand::Profile(args) = command {
113            if self.target_resolver.has_explicit_target() {
114                return Err(AppError::invalid_input(
115                    "--server and --profile cannot be used with profile-management commands",
116                ));
117            }
118            return commands::profile::dispatch(
119                args.command,
120                &self.profile_store,
121                &self.credential_store,
122                &self.confirmation,
123            );
124        }
125        command.validate_input()?;
126        let policy = match &command {
127            RootCommand::Auth(args) => args.command.missing_profile_resolution(),
128            RootCommand::System(_) => crate::config::MissingProfileResolution::AnonymousRequest,
129            _ => crate::config::MissingProfileResolution::Reject,
130        };
131        let mut target = self.target_resolver.resolve(&self.profile_store, policy)?;
132        let profile = target.profile().to_owned();
133        let result = match command {
134            RootCommand::System(args) => {
135                commands::system::dispatch(args.command, target.client_factory()).await
136            }
137            RootCommand::Admin(args) => {
138                commands::admin::dispatch(
139                    args.command,
140                    target.client_factory(),
141                    &self.credential_store,
142                    &self.secret_input,
143                    &self.confirmation,
144                )
145                .await
146            }
147            RootCommand::Rule(args) => {
148                commands::rules::dispatch(
149                    args.command,
150                    target.client_factory(),
151                    &self.credential_store,
152                    &self.confirmation,
153                )
154                .await
155            }
156            RootCommand::Integration(args) => {
157                commands::integrations::dispatch(
158                    args.command,
159                    target.client_factory(),
160                    &self.credential_store,
161                    &self.secret_input,
162                    &self.confirmation,
163                )
164                .await
165            }
166            RootCommand::Dependency(args) => {
167                commands::dependencies::dispatch(
168                    args.command,
169                    target.client_factory(),
170                    &self.credential_store,
171                    &self.confirmation,
172                )
173                .await
174            }
175            RootCommand::Attachment(args) => {
176                commands::attachments::dispatch(
177                    args.command,
178                    target.client_factory(),
179                    &self.credential_store,
180                    &self.confirmation,
181                )
182                .await
183            }
184            RootCommand::Api(args) => {
185                commands::api::dispatch(
186                    args.command,
187                    target.client_factory(),
188                    &self.credential_store,
189                    &self.confirmation,
190                )
191                .await
192            }
193            RootCommand::User(args) => {
194                commands::users::dispatch(
195                    args.command,
196                    target.client_factory(),
197                    &self.credential_store,
198                    &self.secret_input,
199                    &self.confirmation,
200                )
201                .await
202            }
203            RootCommand::Board(args) => {
204                commands::boards::dispatch(
205                    args.command,
206                    target.client_factory(),
207                    &self.credential_store,
208                    &self.confirmation,
209                )
210                .await
211            }
212            RootCommand::List(args) => {
213                commands::lists::dispatch(
214                    args.command,
215                    target.client_factory(),
216                    &self.credential_store,
217                    &self.confirmation,
218                )
219                .await
220            }
221            RootCommand::Card(args) => {
222                commands::cards::dispatch(
223                    args.command,
224                    target.client_factory(),
225                    &self.credential_store,
226                    &self.confirmation,
227                )
228                .await
229            }
230            RootCommand::Checklist(args) => {
231                commands::checklists::dispatch(
232                    args.command,
233                    target.client_factory(),
234                    &self.credential_store,
235                    &self.confirmation,
236                )
237                .await
238            }
239            RootCommand::CustomField(args) => {
240                commands::custom_fields::dispatch(
241                    args.command,
242                    target.client_factory(),
243                    &self.credential_store,
244                    &self.confirmation,
245                )
246                .await
247            }
248            RootCommand::Comment(args) => {
249                commands::comments::dispatch(
250                    args.command,
251                    target.client_factory(),
252                    &self.credential_store,
253                    &self.confirmation,
254                )
255                .await
256            }
257            RootCommand::Swimlane(args) => {
258                commands::swimlanes::dispatch(
259                    args.command,
260                    target.client_factory(),
261                    &self.credential_store,
262                    &self.confirmation,
263                )
264                .await
265            }
266            RootCommand::Auth(args) => {
267                commands::auth::dispatch(
268                    args.command,
269                    &mut target,
270                    &self.credential_store,
271                    &self.secret_input,
272                    &self.confirmation,
273                )
274                .await
275            }
276            RootCommand::Profile(_) | RootCommand::Skill(_) => {
277                unreachable!("local commands return before target resolution")
278            }
279        };
280        result.map_err(|error| {
281            let error = error.with_profile_context(profile);
282            if policy.allows_initialization() {
283                error.with_profile_state(target.profile_created(), target.profile_active())
284            } else {
285                error
286            }
287        })
288    }
289}
290
291#[cfg(test)]
292mod tests;