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;