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::Profile(args) = command {
110            if self.target_resolver.has_explicit_target() {
111                return Err(AppError::invalid_input(
112                    "--server and --profile cannot be used with profile-management commands",
113                ));
114            }
115            return commands::profile::dispatch(
116                args.command,
117                &self.profile_store,
118                &self.credential_store,
119                &self.confirmation,
120            );
121        }
122        command.validate_input()?;
123        let policy = match &command {
124            RootCommand::Auth(args) => args.command.missing_profile_resolution(),
125            RootCommand::System(_) => crate::config::MissingProfileResolution::AnonymousRequest,
126            _ => crate::config::MissingProfileResolution::Reject,
127        };
128        let mut target = self.target_resolver.resolve(&self.profile_store, policy)?;
129        let profile = target.profile().to_owned();
130        let result = match command {
131            RootCommand::System(args) => {
132                commands::system::dispatch(args.command, target.client_factory()).await
133            }
134            RootCommand::Admin(args) => {
135                commands::admin::dispatch(
136                    args.command,
137                    target.client_factory(),
138                    &self.credential_store,
139                    &self.secret_input,
140                    &self.confirmation,
141                )
142                .await
143            }
144            RootCommand::Rule(args) => {
145                commands::rules::dispatch(
146                    args.command,
147                    target.client_factory(),
148                    &self.credential_store,
149                    &self.confirmation,
150                )
151                .await
152            }
153            RootCommand::Integration(args) => {
154                commands::integrations::dispatch(
155                    args.command,
156                    target.client_factory(),
157                    &self.credential_store,
158                    &self.secret_input,
159                    &self.confirmation,
160                )
161                .await
162            }
163            RootCommand::Dependency(args) => {
164                commands::dependencies::dispatch(
165                    args.command,
166                    target.client_factory(),
167                    &self.credential_store,
168                    &self.confirmation,
169                )
170                .await
171            }
172            RootCommand::Attachment(args) => {
173                commands::attachments::dispatch(
174                    args.command,
175                    target.client_factory(),
176                    &self.credential_store,
177                    &self.confirmation,
178                )
179                .await
180            }
181            RootCommand::Api(args) => {
182                commands::api::dispatch(
183                    args.command,
184                    target.client_factory(),
185                    &self.credential_store,
186                    &self.confirmation,
187                )
188                .await
189            }
190            RootCommand::User(args) => {
191                commands::users::dispatch(
192                    args.command,
193                    target.client_factory(),
194                    &self.credential_store,
195                    &self.secret_input,
196                    &self.confirmation,
197                )
198                .await
199            }
200            RootCommand::Board(args) => {
201                commands::boards::dispatch(
202                    args.command,
203                    target.client_factory(),
204                    &self.credential_store,
205                    &self.confirmation,
206                )
207                .await
208            }
209            RootCommand::List(args) => {
210                commands::lists::dispatch(
211                    args.command,
212                    target.client_factory(),
213                    &self.credential_store,
214                    &self.confirmation,
215                )
216                .await
217            }
218            RootCommand::Card(args) => {
219                commands::cards::dispatch(
220                    args.command,
221                    target.client_factory(),
222                    &self.credential_store,
223                    &self.confirmation,
224                )
225                .await
226            }
227            RootCommand::Checklist(args) => {
228                commands::checklists::dispatch(
229                    args.command,
230                    target.client_factory(),
231                    &self.credential_store,
232                    &self.confirmation,
233                )
234                .await
235            }
236            RootCommand::CustomField(args) => {
237                commands::custom_fields::dispatch(
238                    args.command,
239                    target.client_factory(),
240                    &self.credential_store,
241                    &self.confirmation,
242                )
243                .await
244            }
245            RootCommand::Comment(args) => {
246                commands::comments::dispatch(
247                    args.command,
248                    target.client_factory(),
249                    &self.credential_store,
250                    &self.confirmation,
251                )
252                .await
253            }
254            RootCommand::Swimlane(args) => {
255                commands::swimlanes::dispatch(
256                    args.command,
257                    target.client_factory(),
258                    &self.credential_store,
259                    &self.confirmation,
260                )
261                .await
262            }
263            RootCommand::Auth(args) => {
264                commands::auth::dispatch(
265                    args.command,
266                    &mut target,
267                    &self.credential_store,
268                    &self.secret_input,
269                    &self.confirmation,
270                )
271                .await
272            }
273            RootCommand::Profile(_) => {
274                unreachable!("local commands return before target resolution")
275            }
276        };
277        result.map_err(|error| {
278            let error = error.with_profile_context(profile);
279            if policy.allows_initialization() {
280                error.with_profile_state(target.profile_created(), target.profile_active())
281            } else {
282                error
283            }
284        })
285    }
286}
287
288#[cfg(test)]
289mod tests;