Add blueprint for JavaScript Web APIs

This commit is contained in:
Tobie Morgan Hitchcock 2023-02-13 17:47:09 +00:00
parent d17b658163
commit 0e8866b4e3
6 changed files with 389 additions and 0 deletions

View file

@ -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<u8>,
}
impl Blob {
// ------------------------------
// Constructor
// ------------------------------
#[quickjs(constructor)]
pub fn new(args: Rest<Value>) -> 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]")
}
}
}

View file

@ -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<HeaderName, Vec<String>>,
}
impl Headers {
// ------------------------------
// Constructor
// ------------------------------
#[quickjs(constructor)]
pub fn new(args: Rest<Value>) -> 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<Value>) -> 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<Value>) -> 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<Value>) -> Vec<(String, String)> {
self.inner
.iter()
.map(|(k, v)| {
(
k.as_str().to_owned(),
v.iter().map(|v| v.as_str()).collect::<Vec<&str>>().join(","),
)
})
.collect::<Vec<(String, String)>>()
}
// Returns all values of a header in the header set
pub fn get(&self, key: String, args: Rest<Value>) -> js::Result<Option<String>> {
// 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::<Vec<&str>>().join(",")))
}
// Checks to see if the header set contains a header
pub fn has(&self, key: String, args: Rest<Value>) -> js::Result<bool> {
// 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<Value>) -> Vec<String> {
self.inner.keys().map(|v| v.as_str().to_owned()).collect::<Vec<String>>()
}
// Sets a new value or adds a header to the header set
pub fn set(&mut self, key: String, val: String, args: Rest<Value>) -> 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<Value>) -> Vec<String> {
self.inner
.values()
.map(|v| v.iter().map(|v| v.as_str()).collect::<Vec<&str>>().join(","))
.collect::<Vec<String>>()
}
}
}

View file

@ -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;

View file

@ -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<String>,
pub(crate) credentials: Option<String>,
pub(crate) headers: Option<String>,
pub(crate) method: Option<String>,
pub(crate) mode: Option<String>,
pub(crate) referrer: Option<String>,
}
impl Request {
// ------------------------------
// Constructor
// ------------------------------
#[quickjs(constructor)]
pub fn new(args: Rest<Value>) -> 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<Value>) -> Request {
self.clone()
}
// Returns a promise with the request body as a Blob
pub async fn blob(self, args: Rest<Value>) -> js::Result<Blob> {
Err(throw!("Not yet implemented"))
}
// Returns a promise with the request body as FormData
pub async fn formData(self, args: Rest<Value>) -> js::Result<Value> {
Err(throw!("Not yet implemented"))
}
// Returns a promise with the request body as JSON
pub async fn json(self, args: Rest<Value>) -> js::Result<Value> {
Err(throw!("Not yet implemented"))
}
// Returns a promise with the request body as text
pub async fn text(self, args: Rest<Value>) -> js::Result<Value> {
Err(throw!("Not yet implemented"))
}
}
}

View file

@ -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<String>,
pub(crate) credentials: Option<String>,
pub(crate) headers: Option<String>,
pub(crate) method: Option<String>,
pub(crate) mode: Option<String>,
pub(crate) referrer: Option<String>,
}
impl Response {
// ------------------------------
// Constructor
// ------------------------------
#[quickjs(constructor)]
pub fn new(args: Rest<Value>) -> 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<Value>) -> Response {
self.clone()
}
// Returns a promise with the response body as a Blob
pub async fn blob(self, args: Rest<Value>) -> js::Result<Blob> {
Err(throw!("Not yet implemented"))
}
// Returns a promise with the response body as FormData
pub async fn formData(self, args: Rest<Value>) -> js::Result<Value> {
Err(throw!("Not yet implemented"))
}
// Returns a promise with the response body as JSON
pub async fn json(self, args: Rest<Value>) -> js::Result<Value> {
Err(throw!("Not yet implemented"))
}
// Returns a promise with the response body as text
pub async fn text(self, args: Rest<Value>) -> js::Result<Value> {
Err(throw!("Not yet implemented"))
}
// ------------------------------
// Static methods
// ------------------------------
// Returns a new response representing a network error
pub fn error(args: Rest<Value>) -> js::Result<Response> {
Err(throw!("Not yet implemented"))
}
// Creates a new response with a different URL
pub fn redirect(args: Rest<Value>) -> js::Result<Response> {
Err(throw!("Not yet implemented"))
}
}
}

View file

@ -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(),
}
};
}