cli_engine/auth/
dispatcher.rs1use std::sync::{Arc, RwLock};
2
3use async_trait::async_trait;
4
5use super::{AuthProvider, Credential};
6use crate::{CliCoreError, Result};
7
8#[derive(Clone, Debug, Default)]
13pub struct Dispatcher {
14 inner: Arc<RwLock<DispatcherInner>>,
15}
16
17#[derive(Clone, Debug, Default)]
18struct DispatcherInner {
19 providers: Vec<(String, Arc<dyn AuthProvider>)>,
20}
21
22#[derive(Clone, Debug)]
24pub struct StatusEntry {
25 pub provider: String,
27 pub env: String,
29 pub credential: Option<Credential>,
31 pub error: Option<String>,
33}
34
35impl Dispatcher {
36 #[must_use]
38 pub fn new() -> Self {
39 Self {
40 inner: Arc::new(RwLock::new(DispatcherInner::default())),
41 }
42 }
43
44 pub fn register(&mut self, provider: Arc<dyn AuthProvider>) {
46 let name = provider.name().to_owned();
47 let mut inner = self.write_inner();
48 if let Some((_, existing)) = inner
49 .providers
50 .iter_mut()
51 .find(|(existing_name, _)| existing_name == &name)
52 {
53 *existing = provider;
54 return;
55 }
56 inner.providers.push((name, provider));
57 }
58
59 #[must_use]
61 pub fn registered_names(&self) -> Vec<String> {
62 self.read_inner()
63 .providers
64 .iter()
65 .map(|(name, _)| name.clone())
66 .collect()
67 }
68
69 pub async fn get_credential(
71 &self,
72 name: &str,
73 env: &str,
74 command: &str,
75 tier: &str,
76 ) -> Result<Credential> {
77 self.get(name)?.get_credential(env, command, tier).await
78 }
79
80 pub async fn login(&self, name: &str, env: &str) -> Result<Credential> {
82 let provider = self.get(name)?;
83 if let Err(err) = provider.logout(env).await {
84 tracing::debug!(provider = name, error = %err, "ignoring logout error before login");
85 }
86 provider.get_credential(env, "", "").await
87 }
88
89 pub async fn status(&self, name: &str, env: &str) -> Result<Credential> {
91 self.get(name)?.status(env).await
92 }
93
94 pub async fn logout(&self, name: &str, env: &str) -> Result<()> {
96 self.get(name)?.logout(env).await
97 }
98
99 pub async fn all_statuses(&self) -> Vec<StatusEntry> {
101 let mut entries = Vec::new();
102 let providers = self.read_inner().providers.clone();
103 for (name, provider) in providers {
104 let Ok(envs) = provider.list_environments().await else {
105 continue;
106 };
107 for env in envs {
108 match provider.status(&env).await {
109 Ok(credential) => entries.push(StatusEntry {
110 provider: name.clone(),
111 env,
112 credential: Some(credential),
113 error: None,
114 }),
115 Err(err) => entries.push(StatusEntry {
116 provider: name.clone(),
117 env,
118 credential: None,
119 error: Some(err.to_string()),
120 }),
121 }
122 }
123 }
124 entries
125 }
126
127 #[must_use]
129 pub fn for_provider(&self, name: impl Into<String>) -> SingleProvider {
130 SingleProvider {
131 dispatcher: self.clone(),
132 name: name.into(),
133 }
134 }
135
136 fn get(&self, name: &str) -> Result<Arc<dyn AuthProvider>> {
137 self.read_inner()
138 .providers
139 .iter()
140 .find(|(existing_name, _)| existing_name == name)
141 .map(|(_, provider)| Arc::clone(provider))
142 .ok_or_else(|| CliCoreError::MissingAuthProvider(name.to_owned()))
143 }
144
145 fn read_inner(&self) -> std::sync::RwLockReadGuard<'_, DispatcherInner> {
146 match self.inner.read() {
147 Ok(guard) => guard,
148 Err(poisoned) => poisoned.into_inner(),
149 }
150 }
151
152 fn write_inner(&self) -> std::sync::RwLockWriteGuard<'_, DispatcherInner> {
153 match self.inner.write() {
154 Ok(guard) => guard,
155 Err(poisoned) => poisoned.into_inner(),
156 }
157 }
158}
159
160#[derive(Clone, Debug)]
162pub struct SingleProvider {
163 dispatcher: Dispatcher,
164 name: String,
165}
166
167#[async_trait]
168impl AuthProvider for SingleProvider {
169 fn name(&self) -> &str {
170 &self.name
171 }
172
173 async fn get_credential(&self, env: &str, command: &str, tier: &str) -> Result<Credential> {
174 self.dispatcher
175 .get_credential(&self.name, env, command, tier)
176 .await
177 }
178
179 async fn status(&self, env: &str) -> Result<Credential> {
180 self.dispatcher.status(&self.name, env).await
181 }
182
183 async fn logout(&self, env: &str) -> Result<()> {
184 self.dispatcher.logout(&self.name, env).await
185 }
186
187 async fn list_environments(&self) -> Result<Vec<String>> {
188 self.dispatcher.get(&self.name)?.list_environments().await
189 }
190}