1use std::{sync::OnceLock, time::Duration};
32
33use anyhow::Context;
34use api::read::GetVersion;
35use serde::Deserialize;
36
37pub mod api;
38pub mod busy;
39pub mod deserializers;
40pub mod entities;
41pub mod parsers;
42pub mod terminal;
43pub mod ws;
44
45mod request;
46
47pub fn komodo_client() -> &'static KomodoClient {
49 static KOMODO_CLIENT: OnceLock<KomodoClient> = OnceLock::new();
50 KOMODO_CLIENT.get_or_init(|| {
51 KomodoClient::new_from_env()
52 .context("Missing KOMODO_ADDRESS, KOMODO_API_KEY, KOMODO_API_SECRET from env")
53 .unwrap()
54 })
55}
56
57#[derive(Deserialize)]
59pub struct KomodoEnv {
60 pub komodo_address: String,
62 pub komodo_api_key: String,
64 pub komodo_api_secret: String,
66}
67
68#[derive(Clone)]
70pub struct KomodoClient {
71 #[cfg(not(feature = "blocking"))]
72 reqwest: reqwest::Client,
73 #[cfg(feature = "blocking")]
74 reqwest: reqwest::blocking::Client,
75 address: String,
76 key: String,
77 secret: String,
78}
79
80impl KomodoClient {
81 pub fn new(
83 address: impl Into<String>,
84 key: impl Into<String>,
85 secret: impl Into<String>,
86 ) -> KomodoClient {
87 KomodoClient {
88 reqwest: Default::default(),
89 address: address.into(),
90 key: key.into(),
91 secret: secret.into(),
92 }
93 }
94
95 pub fn new_from_env() -> anyhow::Result<KomodoClient> {
97 let KomodoEnv {
98 komodo_address,
99 komodo_api_key,
100 komodo_api_secret,
101 } = envy::from_env()
102 .context("failed to parse environment for komodo client")?;
103 Ok(KomodoClient::new(
104 komodo_address,
105 komodo_api_key,
106 komodo_api_secret,
107 ))
108 }
109
110 #[cfg(not(feature = "blocking"))]
117 pub async fn with_healthcheck(self) -> anyhow::Result<Self> {
118 self.health_check().await?;
119 Ok(self)
120 }
121
122 #[cfg(feature = "blocking")]
129 pub fn with_healthcheck(self) -> anyhow::Result<Self> {
130 self.health_check()?;
131 Ok(self)
132 }
133
134 #[cfg(not(feature = "blocking"))]
136 pub async fn core_version(&self) -> anyhow::Result<String> {
137 self.read(GetVersion {}).await.map(|r| r.version)
138 }
139
140 #[cfg(feature = "blocking")]
142 pub fn core_version(&self) -> anyhow::Result<String> {
143 self.read(GetVersion {}).map(|r| r.version)
144 }
145
146 #[cfg(not(feature = "blocking"))]
148 pub async fn health_check(&self) -> anyhow::Result<()> {
149 self.read(GetVersion {}).await.map(|_| ())
150 }
151
152 #[cfg(feature = "blocking")]
154 pub fn health_check(&self) -> anyhow::Result<()> {
155 self.read(GetVersion {}).map(|_| ())
156 }
157
158 #[cfg(not(feature = "blocking"))]
160 pub fn set_reqwest(mut self, reqwest: reqwest::Client) -> Self {
161 self.reqwest = reqwest;
162 self
163 }
164
165 #[cfg(feature = "blocking")]
167 pub fn set_reqwest(
168 mut self,
169 reqwest: reqwest::blocking::Client,
170 ) -> Self {
171 self.reqwest = reqwest;
172 self
173 }
174
175 #[cfg(not(feature = "blocking"))]
178 pub async fn poll_update_until_complete(
179 &self,
180 update_id: impl Into<String>,
181 ) -> anyhow::Result<entities::update::Update> {
182 let update_id = update_id.into();
183 loop {
184 let update = self
185 .read(api::read::GetUpdate {
186 id: update_id.clone(),
187 })
188 .await?;
189 if update.status == entities::update::UpdateStatus::Complete {
190 return Ok(update);
191 }
192 tokio::time::sleep(Duration::from_millis(500)).await;
193 }
194 }
195
196 #[cfg(feature = "blocking")]
199 pub fn poll_update_until_complete(
200 &self,
201 update_id: impl Into<String>,
202 ) -> anyhow::Result<entities::update::Update> {
203 let update_id = update_id.into();
204 loop {
205 let update = self.read(api::read::GetUpdate {
206 id: update_id.clone(),
207 })?;
208 if update.status == entities::update::UpdateStatus::Complete {
209 return Ok(update);
210 }
211 }
212 }
213}