1use std::{
6 fmt::{self, Debug, Display, Formatter},
7 str::FromStr,
8};
9
10use crate::oauth10a::{
11 ClientError, Request, RestClient,
12 reqwest::{
13 self, Body, Method,
14 header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderValue},
15 },
16 url,
17};
18use chrono::{DateTime, Utc};
19#[cfg(feature = "logging")]
20use log::{Level, debug, log_enabled};
21use serde::{Deserialize, Serialize};
22
23use crate::Client;
24
25pub const MIME_APPLICATION_WASM: &str = "application/wasm";
29
30#[derive(thiserror::Error, Debug)]
34pub enum Error {
35 #[error(
36 "failed to parse the webassembly platform '{0}', available values are 'rust', 'javascript' ('js'), 'tiny_go' ('go') and 'assemblyscript'"
37 )]
38 ParsePlatform(String),
39 #[error(
40 "failed to parse the status '{0}', available values are 'waiting_for_upload', 'deploying', 'packaging', 'ready' and 'error'"
41 )]
42 ParseStatus(String),
43 #[error("failed to parse endpoint '{0}', {1}")]
44 ParseUrl(String, url::ParseError),
45 #[error("failed to list deployments for function '{0}' of organisation '{1}', {2}")]
46 List(String, String, ClientError),
47 #[error("failed to create deployment for function '{0}' on organisation '{1}', {2}")]
48 Create(String, String, ClientError),
49 #[error("failed to get deployment '{0}' of function '{1}' on organisation '{2}', {3}")]
50 Get(String, String, String, ClientError),
51 #[error("failed to trigger deployment '{0}' of function '{1}' on organisation '{2}', {3}")]
52 Trigger(String, String, String, ClientError),
53 #[error("failed to delete deployment '{0}' of function '{1}' on organisation '{2}', {3}")]
54 Delete(String, String, String, ClientError),
55 #[error("failed to create request, {0}")]
56 Request(reqwest::Error),
57 #[error("failed to execute request, {0}")]
58 Execute(ClientError),
59 #[error("failed to execute request, got status code {0}")]
60 StatusCode(u16),
61}
62
63#[derive(Serialize, Deserialize, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
67pub enum Platform {
68 #[serde(rename = "RUST")]
69 Rust,
70 #[serde(rename = "ASSEMBLY_SCRIPT")]
71 AssemblyScript,
72 #[serde(rename = "TINY_GO")]
73 TinyGo,
74 #[serde(rename = "JAVA_SCRIPT")]
75 JavaScript,
76}
77
78impl Display for Platform {
79 fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
80 match self {
81 Self::Rust => write!(f, "RUST"),
82 Self::AssemblyScript => write!(f, "ASSEMBLY_SCRIPT"),
83 Self::JavaScript => write!(f, "JAVA_SCRIPT"),
84 Self::TinyGo => write!(f, "TINY_GO"),
85 }
86 }
87}
88
89impl FromStr for Platform {
90 type Err = Error;
91
92 fn from_str(s: &str) -> Result<Self, Self::Err> {
93 match s.to_lowercase().trim().replace('_', "").as_str() {
94 "rust" => Ok(Self::Rust),
95 "javascript" | "js" => Ok(Self::JavaScript),
96 "tinygo" | "go" => Ok(Self::TinyGo),
97 "assemblyscript" => Ok(Self::AssemblyScript),
98 _ => Err(Error::ParsePlatform(s.to_string())),
99 }
100 }
101}
102
103#[derive(Serialize, Deserialize, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
107pub enum Status {
108 #[serde(rename = "WAITING_FOR_UPLOAD")]
109 WaitingForUpload,
110 #[serde(rename = "PACKAGING")]
111 Packaging,
112 #[serde(rename = "DEPLOYING")]
113 Deploying,
114 #[serde(rename = "READY")]
115 Ready,
116 #[serde(rename = "ERROR")]
117 Error,
118}
119
120impl Display for Status {
121 fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
122 match self {
123 Self::WaitingForUpload => write!(f, "WAITING_FOR_UPLOAD"),
124 Self::Packaging => write!(f, "PACKAGING"),
125 Self::Deploying => write!(f, "DEPLOYING"),
126 Self::Ready => write!(f, "READY"),
127 Self::Error => write!(f, "ERROR"),
128 }
129 }
130}
131
132impl FromStr for Status {
133 type Err = Error;
134
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
136 match s.to_lowercase().trim().replace('_', "").as_str() {
137 "waitingforupload" => Ok(Self::WaitingForUpload),
138 "packaging" => Ok(Self::Packaging),
139 "deploying" => Ok(Self::Deploying),
140 "ready" => Ok(Self::Ready),
141 "error" => Ok(Self::Error),
142 _ => Err(Error::ParseStatus(s.to_string())),
143 }
144 }
145}
146
147#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
151pub struct Opts {
152 #[serde(rename = "name")]
153 pub name: Option<String>,
154 #[serde(rename = "description")]
155 pub description: Option<String>,
156 #[serde(rename = "tag")]
157 pub tag: Option<String>,
158 #[serde(rename = "platform")]
159 pub platform: Platform,
160}
161
162#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
166pub struct DeploymentCreation {
167 #[serde(rename = "id")]
168 pub id: String,
169 #[serde(rename = "functionId")]
170 pub function_id: String,
171 #[serde(rename = "name")]
172 pub name: Option<String>,
173 #[serde(rename = "description")]
174 pub description: Option<String>,
175 #[serde(rename = "tag")]
176 pub tag: Option<String>,
177 #[serde(rename = "platform")]
178 pub platform: Platform,
179 #[serde(rename = "status")]
180 pub status: Status,
181 #[serde(rename = "errorReason")]
182 pub reason: Option<String>,
183 #[serde(rename = "uploadUrl")]
184 pub upload_url: String,
185 #[serde(rename = "createdAt")]
186 pub created_at: DateTime<Utc>,
187 #[serde(rename = "updatedAt")]
188 pub updated_at: DateTime<Utc>,
189}
190
191#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
195pub struct Deployment {
196 #[serde(rename = "id")]
197 pub id: String,
198 #[serde(rename = "functionId")]
199 pub function_id: String,
200 #[serde(rename = "name")]
201 pub name: Option<String>,
202 #[serde(rename = "description")]
203 pub description: Option<String>,
204 #[serde(rename = "tag")]
205 pub tag: Option<String>,
206 #[serde(rename = "platform")]
207 pub platform: Platform,
208 #[serde(rename = "status")]
209 pub status: Status,
210 #[serde(rename = "errorReason")]
211 pub reason: Option<String>,
212 #[serde(rename = "url")]
213 pub url: Option<String>,
214 #[serde(rename = "createdAt")]
215 pub created_at: DateTime<Utc>,
216 #[serde(rename = "updatedAt")]
217 pub updated_at: DateTime<Utc>,
218}
219
220#[cfg_attr(feature = "tracing", tracing::instrument)]
224pub async fn list(
226 client: &Client,
227 organisation_id: &str,
228 function_id: &str,
229) -> Result<Vec<Deployment>, Error> {
230 let path = format!(
231 "{}/v4/functions/organisations/{organisation_id}/functions/{function_id}/deployments",
232 client.endpoint
233 );
234
235 #[cfg(feature = "logging")]
236 if log_enabled!(Level::Debug) {
237 debug!(
238 "execute a request to list deployments for functions, path: '{path}', organisation: '{organisation_id}', function_id: '{function_id}'"
239 );
240 }
241
242 client
243 .get(&path)
244 .await
245 .map_err(|err| Error::List(function_id.to_string(), organisation_id.to_string(), err))
246}
247
248#[cfg_attr(feature = "tracing", tracing::instrument)]
249pub async fn create(
251 client: &Client,
252 organisation_id: &str,
253 function_id: &str,
254 opts: &Opts,
255) -> Result<DeploymentCreation, Error> {
256 let path = format!(
257 "{}/v4/functions/organisations/{organisation_id}/functions/{function_id}/deployments",
258 client.endpoint
259 );
260
261 #[cfg(feature = "logging")]
262 if log_enabled!(Level::Debug) {
263 debug!(
264 "execute a request to create deployment, path: '{path}', organisation: {organisation_id}, function_id: '{function_id}'"
265 );
266 }
267
268 client
269 .post(&path, opts)
270 .await
271 .map_err(|err| Error::Create(function_id.to_string(), organisation_id.to_string(), err))
272}
273
274#[cfg_attr(feature = "tracing", tracing::instrument)]
275pub async fn get(
277 client: &Client,
278 organisation_id: &str,
279 function_id: &str,
280 deployment_id: &str,
281) -> Result<Deployment, Error> {
282 let path = format!(
283 "{}/v4/functions/organisations/{organisation_id}/functions/{function_id}/deployments/{deployment_id}",
284 client.endpoint
285 );
286
287 #[cfg(feature = "logging")]
288 if log_enabled!(Level::Debug) {
289 debug!(
290 "execute a request to get deployment, path: '{path}', organisation: {organisation_id}, function: {function_id}, deployment: {deployment_id}"
291 );
292 }
293
294 client.get(&path).await.map_err(|err| {
295 Error::Get(
296 deployment_id.to_string(),
297 function_id.to_string(),
298 organisation_id.to_string(),
299 err,
300 )
301 })
302}
303
304#[cfg_attr(feature = "tracing", tracing::instrument)]
305pub async fn trigger(
307 client: &Client,
308 organisation_id: &str,
309 function_id: &str,
310 deployment_id: &str,
311) -> Result<(), Error> {
312 let path = format!(
313 "{}/v4/functions/organisations/{organisation_id}/functions/{function_id}/deployments/{deployment_id}/trigger",
314 client.endpoint
315 );
316
317 #[cfg(feature = "logging")]
318 if log_enabled!(Level::Debug) {
319 debug!(
320 "execute a request to get deployment, path: '{path}', organisation: {organisation_id}, function: {function_id}, deployment: {deployment_id}"
321 );
322 }
323
324 let req = reqwest::Request::new(
325 Method::POST,
326 path.parse().map_err(|err| Error::ParseUrl(path, err))?,
327 );
328
329 let res = client.execute(req).await.map_err(Error::Execute)?;
330 let status = res.status();
331 if !status.is_success() {
332 return Err(Error::StatusCode(status.as_u16()));
333 }
334
335 Ok(())
336}
337
338#[cfg_attr(feature = "tracing", tracing::instrument)]
339pub async fn upload(client: &Client, endpoint: &str, buf: Vec<u8>) -> Result<(), Error> {
341 let mut req = reqwest::Request::new(
342 Method::PUT,
343 endpoint
344 .parse()
345 .map_err(|err| Error::ParseUrl(endpoint.to_string(), err))?,
346 );
347
348 req.headers_mut().insert(
349 CONTENT_TYPE,
350 HeaderValue::from_static(MIME_APPLICATION_WASM),
351 );
352 req.headers_mut()
353 .insert(CONTENT_LENGTH, HeaderValue::from(buf.len()));
354 *req.body_mut() = Some(Body::from(buf));
355
356 #[cfg(feature = "logging")]
357 if log_enabled!(Level::Debug) {
358 debug!("execute a request to upload webassembly, endpoint: '{endpoint}'");
359 }
360
361 let res = client
362 .inner()
363 .execute(req)
364 .await
365 .map_err(|err| Error::Execute(ClientError::Request(err)))?;
366
367 let status = res.status();
368 if !status.is_success() {
369 return Err(Error::StatusCode(status.as_u16()));
370 }
371
372 Ok(())
373}
374
375#[cfg_attr(feature = "tracing", tracing::instrument)]
376pub async fn delete(
378 client: &Client,
379 organisation_id: &str,
380 function_id: &str,
381 deployment_id: &str,
382) -> Result<(), Error> {
383 let path = format!(
384 "{}/v4/functions/organisations/{organisation_id}/functions/{function_id}/deployments/{deployment_id}",
385 client.endpoint
386 );
387
388 #[cfg(feature = "logging")]
389 if log_enabled!(Level::Debug) {
390 debug!(
391 "execute a request to delete deployment, path: '{path}', organisation: {organisation_id}, function: {function_id}, deployment: {deployment_id}"
392 );
393 }
394
395 client.delete(&path).await.map_err(|err| {
396 Error::Delete(
397 deployment_id.to_string(),
398 function_id.to_string(),
399 organisation_id.to_string(),
400 err,
401 )
402 })
403}