diff --git a/Cargo.lock b/Cargo.lock index eda1f29..82ff06e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" dependencies = [ "axum-core", + "axum-macros", "bytes", "form_urlencoded", "futures-util", @@ -71,6 +72,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -92,6 +104,24 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bytes" version = "1.10.1" @@ -104,6 +134,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "fnv" version = "1.0.7" @@ -152,6 +203,16 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.3.2" @@ -321,6 +382,7 @@ name = "offline-web" version = "0.1.0" dependencies = [ "axum", + "blake2", "rand", "serde", "tokio", @@ -501,6 +563,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.100" @@ -592,12 +660,24 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 73259d1..3dac612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ name = "example" path = "src/main.rs" [dependencies] -axum = "0.8.3" +axum = { version = "0.8.3", features = ["macros"] } +blake2 = "0.10.6" rand = "0.9.0" serde = { version = "1.0.219", features = ["derive", "rc"] } tokio = "1.44.1" diff --git a/src/datamodel/mod.rs b/src/datamodel/mod.rs index e8016a5..ab22961 100644 --- a/src/datamodel/mod.rs +++ b/src/datamodel/mod.rs @@ -1,14 +1,14 @@ -use std::rc::Rc; +use std::sync::Arc; use serde::{Serialize, Deserialize}; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct Reference { - object_id: String, - content_address: String, - path: String, + pub object_id: String, + pub content_address: String, + pub path: String, #[serde(skip_serializing_if = "Vec::is_empty")] - dependents: Vec>, + pub dependents: Vec>, } impl Reference { @@ -21,12 +21,16 @@ impl Reference { } } - pub fn add_dep(mut self, dep: Rc) -> Self { + pub fn add_dep(mut self, dep: Arc) -> Self { self.dependents.push(dep); self } - pub fn to_rc(self) -> Rc { - Rc::new(self) + pub fn to_arc(self) -> Arc { + Arc::new(self) + } + + pub fn is_leaf(&self) -> bool { + return self.dependents.is_empty(); } } diff --git a/src/lib.rs b/src/lib.rs index cc5732b..6bf8767 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,72 +1,97 @@ -use std::rc::Rc; +use std::{collections::HashMap, sync::Arc}; use axum::{extract::Path, http, response::{Html, IntoResponse}, routing::get, Json, Router}; -use datamodel::Reference; +use blake2::{Blake2b512, Digest}; +use rand::Rng; +use datamodel::Reference; mod datamodel; -async fn all_references() -> Json> { - let path_root = String::from("ref/0"); - let mut root_ref = Reference::new( - "username:0".to_string(), - String::from("0"), - path_root.clone(), - ); - for i in 1..=10 { - let mut item_ref = Reference::new( - format!("item:{}", i), - format!("0:{}", i), - format!("{}/item/{}", path_root, i), - ); - for j in 1..=10 { - item_ref = item_ref.add_dep(Rc::new(Reference::new( - format!("item:{}:subitem:{}", i, j), - format!("0:{}:{}", i, j), - format!("{}/item/{}/subitem/{}", path_root, i, j), - ))); - } - root_ref = root_ref.add_dep(Rc::new(item_ref)); - } - Json(root_ref.to_rc()) +#[derive(Debug)] +pub struct AddressableObject { + pub address: String, + pub content: String, } -async fn dummy_item_ref(Path(i): Path) -> Json> { - let path_root = String::from("ref/0"); - let mut item_ref = Reference::new( - format!("item:{}", i), - format!("0:{}", i), - format!("{}/item/{}", path_root, i), - ); - for j in 1..=10 { - item_ref = item_ref.add_dep(Rc::new(Reference::new( - format!("item:{}:subitem:{}", i, j), - format!("0:{}:{}", i, j), - format!("{}/item/{}/subitem/{}", path_root, i, j), - ))); - } - Json(item_ref.to_rc()) -} - -async fn dummy_subitem_ref(Path(i): Path, Path(j): Path) -> Json> { - let path_root = String::from("ref/0"); - Json( - Reference::new( - format!("item:{}:subitem:{}", i, j), - format!("0:{}:{}", i, j), - format!("{}/item/{}/subitem/{}", path_root, i, j), - ) - .to_rc(), - ) -} - -async fn dummy_object(Path(addr): Path) -> String { - use rand::Rng; +fn random_object() -> AddressableObject { let mut rng = rand::rng(); let random_size = rng.random_range(50..=4096); let random_string: String = (0..random_size) .map(|_| rng.sample(rand::distr::Alphanumeric) as char) .collect(); - format!("I am object {} -- {}", addr, random_string) + + let mut hasher = Blake2b512::new(); + hasher.update(&random_string); + let hash = format!("{:x}", hasher.finalize()); + + AddressableObject { + address: hash, + content: random_string, + } +} + +fn random_references_and_objects() -> (Arc, Arc>>, Arc>) { + let path_root = String::from("ref/0"); + let mut objects = HashMap::new(); + let mut refs = HashMap::new(); + let mut root_ref = Reference::new( + "username:0".to_string(), + String::from("0"), + path_root.clone(), + ); + let mut root_hasher = Blake2b512::new(); + for i in 1..=10 { + let mut item_ref = Reference::new( + format!("item:{}", i), + format!("0:{}", i), + format!("/item/{}", i), + ); + let mut hasher = Blake2b512::new(); + for j in 1..=10 { + let object = random_object(); + hasher.update(&object.content); + let leaf_ref = Reference::new( + format!("item:{}:subitem:{}", i, j), + format!("{}", object.address), + format!("/item/{}/subitem/{}", i, j), + ).to_arc(); + item_ref = item_ref.add_dep(leaf_ref.clone()); + objects.insert(object.address.clone(), object); + hasher.update(&leaf_ref.content_address); + refs.insert(leaf_ref.path.clone(), leaf_ref); + } + let hash = format!("{:x}", hasher.finalize()); + item_ref.content_address = hash; + root_hasher.update(&item_ref.content_address); + let rc_ref = item_ref.to_arc(); + root_ref = root_ref.add_dep(rc_ref.clone()); + refs.insert(rc_ref.path.clone(), rc_ref); + } + root_ref.content_address = format!("{:x}", root_hasher.finalize()); + let rc_root = root_ref.to_arc(); + refs.insert(rc_root.path.clone(), rc_root.clone()); + dbg!(&objects); + (rc_root, Arc::new(refs), Arc::new(objects)) +} + +async fn all_references(root_ref: Arc) -> Json> { + Json(root_ref) +} + +async fn ref_path(refs: Arc>>, Path(path): Path) -> Json> { + let path = format!("/item/{}", path); + match refs.get(&path) { + Some(r) => Json(r.clone()), + None => todo!("Return a 404?"), + } +} + +async fn object_path(objects: Arc>, Path(path): Path) -> String { + dbg!(&path); + match objects.get(&path) { + Some(o) => o.content.clone(), + None => todo!("Return a 404?"), + } } async fn get_client_js() -> impl IntoResponse { @@ -76,19 +101,27 @@ async fn get_client_js() -> impl IntoResponse { ) } -pub fn endpoints() -> Router { +pub fn endpoints(root_ref: Arc, refs: Arc>>, objects: Arc>) -> Router { Router::new().nest( "/api/v1", Router::new().nest( "/ref", Router::new() - .route("/all/username", get(all_references)) - .route("/item/{i}", get(dummy_item_ref)) - .route("/item/{i}/subitem/{j}", get(dummy_subitem_ref)) + .route("/all/username", get({ + let state = root_ref.clone(); + move || all_references(state) + })) + .route("/item/{*path}", get({ + let refs = refs.clone(); + move |path| ref_path(refs, path) + })) ).nest( "/object", Router::new() - .route("/{addr}", get(dummy_object)) + .route("/{addr}", get({ + let objects = objects.clone(); + move |path| object_path(objects, path) + })) ), ) .route("/lib/client.js", get(get_client_js)) @@ -98,8 +131,9 @@ pub fn endpoints() -> Router { // TODO(jwall): Javascript test script pub async fn serve() { // run our app with hyper, listening globally on port 3000 + let (root_ref, refs, objects) = random_references_and_objects(); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); - axum::serve(listener, endpoints()).await.unwrap(); + axum::serve(listener, endpoints(root_ref, refs, objects)).await.unwrap(); } diff --git a/static/client.js b/static/client.js index b140133..82de876 100644 --- a/static/client.js +++ b/static/client.js @@ -95,6 +95,9 @@ async function load_objects_and_store(db, references, storeName) { let objects = [] for (var ref of references) { /** @type {Response} */ + if (ref.dependents && ref.dependents.length != 0) { + continue; // not a leaf object + } let response = await fetch("/api/v1/object/" + ref.content_address); if (!response.ok) { throw new Error("Network response was not ok: " + response.statusText);