use typestate

This commit is contained in:
Edward Shen 2021-10-24 13:12:14 -07:00
parent 23b90ebfe4
commit a509ff08b4
Signed by: edward
GPG key ID: 19182661E818369F
3 changed files with 130 additions and 113 deletions

1
Cargo.lock generated
View file

@ -1082,6 +1082,7 @@ dependencies = [
name = "omegaupload-web" name = "omegaupload-web"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"byte-unit", "byte-unit",
"bytes", "bytes",
"getrandom", "getrandom",

View file

@ -10,6 +10,7 @@ omegaupload-common = { path = "../common" }
# Enables wasm support # Enables wasm support
getrandom = { version = "*", features = ["js"] } getrandom = { version = "*", features = ["js"] }
anyhow = "1"
bytes = "1" bytes = "1"
byte-unit = "4" byte-unit = "4"
gloo-console = "0.1" gloo-console = "0.1"

View file

@ -1,7 +1,9 @@
#![warn(clippy::nursery, clippy::pedantic)] #![warn(clippy::nursery, clippy::pedantic)]
use std::marker::PhantomData;
use std::str::FromStr; use std::str::FromStr;
use anyhow::{anyhow, Result};
use byte_unit::Byte; use byte_unit::Byte;
use decrypt::DecryptedData; use decrypt::DecryptedData;
use gloo_console::log; use gloo_console::log;
@ -39,12 +41,14 @@ fn main() {
if window.location().pathname().unwrap() == "/" { if window.location().pathname().unwrap() == "/" {
} else { } else {
spawn_local(a(request_uri, url)); spawn_local(async {
a(request_uri, url).await;
});
} }
} }
#[allow(clippy::future_not_send)] #[allow(clippy::future_not_send)]
async fn a(request_uri: Uri, url: String) { async fn a(request_uri: Uri, url: String) -> Result<()> {
match Request::get(&request_uri.to_string()).send().await { match Request::get(&request_uri.to_string()).send().await {
Ok(resp) if resp.status() == StatusCode::OK => { Ok(resp) if resp.status() == StatusCode::OK => {
let expires = resp let expires = resp
@ -61,20 +65,24 @@ async fn a(request_uri: Uri, url: String) {
); );
let data = { let data = {
Uint8Array::new( let data_fut = resp
&JsFuture::from(resp.as_raw().array_buffer().unwrap()) .as_raw()
.await .array_buffer()
.unwrap(), .expect("Failed to get raw bytes from response");
) let data = JsFuture::from(data_fut)
.to_vec() .await
.expect("Failed to result array buffer future");
Uint8Array::new(&data).to_vec()
}; };
let info = url let info = url
.split_once('#') .split_once('#')
.map(|(_, fragment)| PartialParsedUrl::from(fragment)) .map(|(_, fragment)| PartialParsedUrl::from(fragment))
.unwrap_or_default(); .unwrap_or_default();
let key = info.decryption_key.unwrap(); let key = info
let nonce = info.nonce.unwrap(); .decryption_key
.expect("missing key should be handled in the future");
let nonce = info.nonce.expect("missing nonce be handled in the future");
let result = decrypt(data, key, nonce, None); let result = decrypt(data, key, nonce, None);
@ -105,111 +113,40 @@ async fn a(request_uri: Uri, url: String) {
.object_store("decrypted data") .object_store("decrypted data")
.unwrap(); .unwrap();
let decrypted_object = Array::new(); let decrypted_object = match &decrypted {
match &decrypted { DecryptedData::String(s) => IdbObject::new()
DecryptedData::String(s) => { .string()
let entry = Array::new(); .expiration_text(&expires)
entry.push(&JsString::from("data")); .data(&JsValue::from_str(s)),
entry.push(&JsValue::from_str(s));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("type"));
entry.push(&JsString::from("string"));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("expiration"));
entry.push(&JsString::from(expires.to_string()));
decrypted_object.push(&entry);
}
DecryptedData::Blob(blob) => { DecryptedData::Blob(blob) => {
let entry = Array::new(); IdbObject::new().blob().expiration_text(&expires).data(blob)
entry.push(&JsString::from("data"));
entry.push(blob);
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("type"));
entry.push(&JsString::from("blob"));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("expiration"));
entry.push(&JsString::from(expires.to_string()));
decrypted_object.push(&entry);
} }
DecryptedData::Image(blob, (width, height), size) => { DecryptedData::Image(blob, (width, height), size) => IdbObject::new()
let entry = Array::new(); .image()
entry.push(&JsString::from("data")); .expiration_text(&expires)
entry.push(blob); .data(blob)
decrypted_object.push(&entry); .extra("width", *width)
.extra("height", *height)
.extra(
"button",
&format!(
"Download {} \u{2014} {} by {}",
Byte::from_bytes(*size as u128).get_appropriate_unit(true),
width,
height,
),
),
DecryptedData::Audio(blob) => IdbObject::new()
.audio()
.expiration_text(&expires)
.data(blob),
DecryptedData::Video(blob) => IdbObject::new()
.video()
.expiration_text(&expires)
.data(blob),
};
let entry = Array::new(); let db_entry = Object::from_entries(decrypted_object.as_ref()).unwrap();
entry.push(&JsString::from("type"));
entry.push(&JsString::from("image"));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("width"));
entry.push(&JsValue::from(*width));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("height"));
entry.push(&JsValue::from(*height));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("button"));
entry.push(&JsString::from(format!(
"Download {} \u{2014} {} by {}",
Byte::from_bytes(*size as u128).get_appropriate_unit(true),
width,
height,
)));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("expiration"));
entry.push(&JsString::from(expires.to_string()));
decrypted_object.push(&entry);
}
DecryptedData::Audio(blob) => {
let entry = Array::new();
entry.push(&JsString::from("data"));
entry.push(blob);
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("type"));
entry.push(&JsString::from("audio"));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("expiration"));
entry.push(&JsString::from(expires.to_string()));
decrypted_object.push(&entry);
}
DecryptedData::Video(blob) => {
let entry = Array::new();
entry.push(&JsString::from("data"));
entry.push(blob);
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("type"));
entry.push(&JsString::from("video"));
decrypted_object.push(&entry);
let entry = Array::new();
entry.push(&JsString::from("expiration"));
entry.push(&JsString::from(expires.to_string()));
decrypted_object.push(&entry);
}
}
let db_entry = Object::from_entries(&decrypted_object).unwrap();
transaction transaction
.put_with_key( .put_with_key(
&db_entry, &db_entry,
@ -244,5 +181,83 @@ async fn a(request_uri: Uri, url: String) {
Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {} Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {}
Ok(err) => {} Ok(err) => {}
Err(err) => {} Err(err) => {}
}; }
Ok(())
} }
struct IdbObject<State>(Array, PhantomData<State>);
impl<State: IdbObjectState> IdbObject<State> {
fn add_tuple<NextState>(self, key: &str, value: &JsValue) -> IdbObject<NextState> {
let array = Array::new();
array.push(&JsString::from(key));
array.push(value);
self.0.push(&array);
IdbObject(self.0, PhantomData)
}
}
impl IdbObject<NeedsType> {
fn new() -> Self {
Self(Array::new(), PhantomData)
}
fn video(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("video"))
}
fn audio(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("audio"))
}
fn image(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("image"))
}
fn blob(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("blob"))
}
fn string(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("string"))
}
}
impl IdbObject<NeedsExpiration> {
fn expiration_text(self, expires: &str) -> IdbObject<NeedsData> {
self.add_tuple("expiration", &JsString::from(expires))
}
}
impl IdbObject<NeedsData> {
fn data(self, value: &JsValue) -> IdbObject<Ready> {
self.add_tuple("data", value)
}
}
impl IdbObject<Ready> {
fn extra(self, key: &str, value: impl Into<JsValue>) -> Self {
self.add_tuple(key, &value.into())
}
}
impl AsRef<JsValue> for IdbObject<Ready> {
fn as_ref(&self) -> &JsValue {
self.0.as_ref()
}
}
trait IdbObjectState {}
enum NeedsType {}
impl IdbObjectState for NeedsType {}
enum NeedsExpiration {}
impl IdbObjectState for NeedsExpiration {}
enum NeedsData {}
impl IdbObjectState for NeedsData {}
enum Ready {}
impl IdbObjectState for Ready {}