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