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