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;