From 0e8866b4e36801c679e2cd7f74b42af58d150eca Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Mon, 13 Feb 2023 17:47:09 +0000 Subject: [PATCH] Add blueprint for JavaScript Web APIs --- lib/src/fnc/script/classes/blob.rs | 57 +++++++++++ lib/src/fnc/script/classes/headers.rs | 126 +++++++++++++++++++++++++ lib/src/fnc/script/classes/mod.rs | 4 + lib/src/fnc/script/classes/request.rs | 84 +++++++++++++++++ lib/src/fnc/script/classes/response.rs | 98 +++++++++++++++++++ lib/src/mac/mod.rs | 20 ++++ 6 files changed, 389 insertions(+) create mode 100644 lib/src/fnc/script/classes/blob.rs create mode 100644 lib/src/fnc/script/classes/headers.rs create mode 100644 lib/src/fnc/script/classes/request.rs create mode 100644 lib/src/fnc/script/classes/response.rs diff --git a/lib/src/fnc/script/classes/blob.rs b/lib/src/fnc/script/classes/blob.rs new file mode 100644 index 00000000..ad808fd2 --- /dev/null +++ b/lib/src/fnc/script/classes/blob.rs @@ -0,0 +1,57 @@ +#[js::bind(object, public)] +#[quickjs(bare)] +#[allow(non_snake_case)] +#[allow(unused_variables)] +#[allow(clippy::module_inception)] +pub mod blob { + + use js::Rest; + use js::Value; + + #[derive(Clone)] + #[quickjs(class)] + #[quickjs(cloneable)] + pub struct Blob { + #[quickjs(hide)] + pub(crate) mime: String, + #[quickjs(hide)] + pub(crate) data: Vec, + } + + impl Blob { + // ------------------------------ + // Constructor + // ------------------------------ + + #[quickjs(constructor)] + pub fn new(args: Rest) -> Self { + Self { + data: vec![], + mime: String::new(), + } + } + + // ------------------------------ + // Instance properties + // ------------------------------ + + #[quickjs(get)] + pub fn size(&self) -> usize { + self.data.len() + } + + #[quickjs(get)] + pub fn r#type(&self) -> &str { + &self.mime + } + + // ------------------------------ + // Instance methods + // ------------------------------ + + // Convert the object to a string + pub fn toString(&self) -> String { + String::from("[object Blob]") + } + } +} diff --git a/lib/src/fnc/script/classes/headers.rs b/lib/src/fnc/script/classes/headers.rs new file mode 100644 index 00000000..1d28f097 --- /dev/null +++ b/lib/src/fnc/script/classes/headers.rs @@ -0,0 +1,126 @@ +#[js::bind(object, public)] +#[quickjs(bare)] +#[allow(non_snake_case)] +#[allow(unused_variables)] +#[allow(clippy::module_inception)] +pub mod headers { + + use js::Rest; + use js::Value; + use reqwest::header::HeaderName; + use std::collections::HashMap; + use std::str::FromStr; + + #[derive(Clone)] + #[quickjs(class)] + #[quickjs(cloneable)] + #[allow(dead_code)] + pub struct Headers { + #[quickjs(hide)] + pub(crate) inner: HashMap>, + } + + impl Headers { + // ------------------------------ + // Constructor + // ------------------------------ + + #[quickjs(constructor)] + pub fn new(args: Rest) -> Self { + Self { + inner: HashMap::new(), + } + } + + // ------------------------------ + // Instance methods + // ------------------------------ + + // Convert the object to a string + pub fn toString(&self) -> String { + String::from("[object Header]") + } + + // Adds or appends a new value to a header + pub fn append(&mut self, key: String, val: String, args: Rest) -> js::Result<()> { + // Process and check the header name is valid + let key = HeaderName::from_str(&key).map_err(|e| throw!(e))?; + // Insert and overwrite the header entry + match self.inner.get_mut(&key) { + Some(v) => { + v.push(val); + } + None => { + self.inner.insert(key, vec![val]); + } + } + // Everything ok + Ok(()) + } + + // Deletes a header from the header set + pub fn delete(&mut self, key: String, args: Rest) -> js::Result<()> { + // Process and check the header name is valid + let key = HeaderName::from_str(&key).map_err(|e| throw!(e))?; + // Remove the header entry from the map + self.inner.remove(&key); + // Everything ok + Ok(()) + } + + // Returns all header entries in the header set + pub fn entries(&self, args: Rest) -> Vec<(String, String)> { + self.inner + .iter() + .map(|(k, v)| { + ( + k.as_str().to_owned(), + v.iter().map(|v| v.as_str()).collect::>().join(","), + ) + }) + .collect::>() + } + + // Returns all values of a header in the header set + pub fn get(&self, key: String, args: Rest) -> js::Result> { + // Process and check the header name is valid + let key = HeaderName::from_str(&key).map_err(|e| throw!(e))?; + // Convert the header values to strings + Ok(self + .inner + .get(&key) + .map(|v| v.iter().map(|v| v.as_str()).collect::>().join(","))) + } + + // Checks to see if the header set contains a header + pub fn has(&self, key: String, args: Rest) -> js::Result { + // Process and check the header name is valid + let key = HeaderName::from_str(&key).map_err(|e| throw!(e))?; + // Check if the header entry exists + Ok(self.inner.contains_key(&key)) + } + + // Returns all header keys contained in the header set + pub fn keys(&self, args: Rest) -> Vec { + self.inner.keys().map(|v| v.as_str().to_owned()).collect::>() + } + + // Sets a new value or adds a header to the header set + pub fn set(&mut self, key: String, val: String, args: Rest) -> js::Result<()> { + // Process and check the header name is valid + let key = HeaderName::from_str(&key).map_err(|e| throw!(e))?; + // Insert and overwrite the header entry + self.inner.insert(key, vec![val]); + // Everything ok + Ok(()) + } + + // Returns all header values contained in the header set + pub fn values(&self, args: Rest) -> Vec { + self.inner + .values() + .map(|v| v.iter().map(|v| v.as_str()).collect::>().join(",")) + .collect::>() + } + } +} diff --git a/lib/src/fnc/script/classes/mod.rs b/lib/src/fnc/script/classes/mod.rs index 23ba11a6..8c5402f3 100644 --- a/lib/src/fnc/script/classes/mod.rs +++ b/lib/src/fnc/script/classes/mod.rs @@ -1,3 +1,7 @@ +pub mod blob; pub mod duration; +pub mod headers; pub mod record; +pub mod request; +pub mod response; pub mod uuid; diff --git a/lib/src/fnc/script/classes/request.rs b/lib/src/fnc/script/classes/request.rs new file mode 100644 index 00000000..8b5f1eed --- /dev/null +++ b/lib/src/fnc/script/classes/request.rs @@ -0,0 +1,84 @@ +#[js::bind(object, public)] +#[quickjs(bare)] +#[allow(non_snake_case)] +#[allow(unused_variables)] +#[allow(clippy::module_inception)] +pub mod request { + + use super::super::blob::blob::Blob; + use crate::sql::value::Value; + use js::Rest; + + #[derive(Clone)] + #[quickjs(class)] + #[quickjs(cloneable)] + #[allow(dead_code)] + pub struct Request { + #[quickjs(hide)] + pub(crate) url: Option, + pub(crate) credentials: Option, + pub(crate) headers: Option, + pub(crate) method: Option, + pub(crate) mode: Option, + pub(crate) referrer: Option, + } + + impl Request { + // ------------------------------ + // Constructor + // ------------------------------ + + #[quickjs(constructor)] + pub fn new(args: Rest) -> Self { + Self { + url: None, + credentials: None, + headers: None, + method: None, + mode: None, + referrer: None, + } + } + + // ------------------------------ + // Instance properties + // ------------------------------ + + // TODO + + // ------------------------------ + // Instance methods + // ------------------------------ + + // Convert the object to a string + pub fn toString(&self) -> String { + String::from("[object Request]") + } + + // Creates a copy of the request object + #[quickjs(rename = "clone")] + pub fn copy(&self, args: Rest) -> Request { + self.clone() + } + + // Returns a promise with the request body as a Blob + pub async fn blob(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // Returns a promise with the request body as FormData + pub async fn formData(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // Returns a promise with the request body as JSON + pub async fn json(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // Returns a promise with the request body as text + pub async fn text(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + } +} diff --git a/lib/src/fnc/script/classes/response.rs b/lib/src/fnc/script/classes/response.rs new file mode 100644 index 00000000..3b2336f3 --- /dev/null +++ b/lib/src/fnc/script/classes/response.rs @@ -0,0 +1,98 @@ +#[js::bind(object, public)] +#[quickjs(bare)] +#[allow(non_snake_case)] +#[allow(unused_variables)] +#[allow(clippy::module_inception)] +pub mod response { + + use super::super::blob::blob::Blob; + use crate::sql::value::Value; + use js::Rest; + + #[derive(Clone)] + #[quickjs(class)] + #[quickjs(cloneable)] + #[allow(dead_code)] + pub struct Response { + #[quickjs(hide)] + pub(crate) url: Option, + pub(crate) credentials: Option, + pub(crate) headers: Option, + pub(crate) method: Option, + pub(crate) mode: Option, + pub(crate) referrer: Option, + } + + impl Response { + // ------------------------------ + // Constructor + // ------------------------------ + + #[quickjs(constructor)] + pub fn new(args: Rest) -> Self { + Self { + url: None, + credentials: None, + headers: None, + method: None, + mode: None, + referrer: None, + } + } + + // ------------------------------ + // Instance properties + // ------------------------------ + + // TODO + + // ------------------------------ + // Instance methods + // ------------------------------ + + // Convert the object to a string + pub fn toString(&self) -> String { + String::from("[object Response]") + } + + // Creates a copy of the request object + #[quickjs(rename = "clone")] + pub fn copy(&self, args: Rest) -> Response { + self.clone() + } + + // Returns a promise with the response body as a Blob + pub async fn blob(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // Returns a promise with the response body as FormData + pub async fn formData(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // Returns a promise with the response body as JSON + pub async fn json(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // Returns a promise with the response body as text + pub async fn text(self, args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // ------------------------------ + // Static methods + // ------------------------------ + + // Returns a new response representing a network error + pub fn error(args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + + // Creates a new response with a different URL + pub fn redirect(args: Rest) -> js::Result { + Err(throw!("Not yet implemented")) + } + } +} diff --git a/lib/src/mac/mod.rs b/lib/src/mac/mod.rs index e1311a75..1bdf8bcf 100644 --- a/lib/src/mac/mod.rs +++ b/lib/src/mac/mod.rs @@ -20,3 +20,23 @@ macro_rules! get_cfg { let $i = || { $( if cfg!($i=$s) { return $s; } );+ "unknown"}; ) } + +#[cfg(feature = "scripting")] +macro_rules! throw { + ($e:ident) => { + js::Error::Exception { + line: line!() as i32, + message: $e.to_string(), + file: file!().to_owned(), + stack: "".to_owned(), + } + }; + ($str:expr) => { + js::Error::Exception { + line: line!() as i32, + message: $str.to_owned(), + file: file!().to_owned(), + stack: "".to_owned(), + } + }; +}