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 if let RootCommand::User(args) = &command {
123 args.command.validate()?;
124 }
125 let policy = match &command {
126 RootCommand::Auth(args) => args.command.missing_profile_resolution(),
127 _ => crate::config::MissingProfileResolution::Reject,
128 };
129 let mut target = self.target_resolver.resolve(&self.profile_store, policy)?;
130 let profile = target.profile().to_owned();
131 let result = match command {
132 RootCommand::Api(args) => {
133 commands::api::dispatch(
134 args.command,
135 target.client_factory(),
136 &self.credential_store,
137 &self.confirmation,
138 )
139 .await
140 }
141 RootCommand::User(args) => {
142 commands::users::dispatch(
143 args.command,
144 target.client_factory(),
145 &self.credential_store,
146 &self.secret_input,
147 &self.confirmation,
148 )
149 .await
150 }
151 RootCommand::Board(args) => {
152 commands::boards::dispatch(
153 args.command,
154 target.client_factory(),
155 &self.credential_store,
156 &self.confirmation,
157 )
158 .await
159 }
160 RootCommand::List(args) => {
161 commands::lists::dispatch(
162 args.command,
163 target.client_factory(),
164 &self.credential_store,
165 &self.confirmation,
166 )
167 .await
168 }
169 RootCommand::Card(args) => {
170 commands::cards::dispatch(
171 args.command,
172 target.client_factory(),
173 &self.credential_store,
174 &self.confirmation,
175 )
176 .await
177 }
178 RootCommand::Comment(args) => {
179 commands::comments::dispatch(
180 args.command,
181 target.client_factory(),
182 &self.credential_store,
183 &self.confirmation,
184 )
185 .await
186 }
187 RootCommand::Swimlane(args) => {
188 commands::swimlanes::dispatch(
189 args.command,
190 target.client_factory(),
191 &self.credential_store,
192 &self.confirmation,
193 )
194 .await
195 }
196 RootCommand::Auth(args) => {
197 commands::auth::dispatch(
198 args.command,
199 &mut target,
200 &self.credential_store,
201 &self.secret_input,
202 &self.confirmation,
203 )
204 .await
205 }
206 RootCommand::Profile(_) => {
207 unreachable!("local commands return before target resolution")
208 }
209 };
210 result.map_err(|error| {
211 let error = error.with_profile_context(profile);
212 if policy.allows_initialization() {
213 error.with_profile_state(target.profile_created(), target.profile_active())
214 } else {
215 error
216 }
217 })
218 }
219}
220
221#[cfg(test)]
222mod tests;