diff --git a/Cargo.lock b/Cargo.lock index 9e3f7ea..23af39d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,7 @@ version = "0.1.0" dependencies = [ "petgraph", "rand", + "thiserror", ] [[package]] @@ -77,6 +78,24 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.5" @@ -107,6 +126,43 @@ dependencies = [ "getrandom", ] +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 4f1e248..b36d734 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] petgraph = "0.6.4" rand = "0.8.5" +thiserror = "1.0.56" diff --git a/src/atoms.rs b/src/atoms.rs index 2d66447..b6f437a 100644 --- a/src/atoms.rs +++ b/src/atoms.rs @@ -13,6 +13,7 @@ pub struct ActorId(String); impl RoomId { // maybe use the initial create event hash/id as the room id? + // ie. !foobar refers to the room while $foobar refers to the event pub fn random() -> Self { let rng = rand::thread_rng() .sample_iter(Alphanumeric) @@ -53,7 +54,7 @@ macro_rules! impl_display { write!(f, $fmt, self.0) } } - + impl Debug for $Thing { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Display::fmt(self, f) diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..40b899b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,4 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error {} diff --git a/src/event.rs b/src/event.rs index e57c914..13ac137 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,12 +1,11 @@ use std::fmt::Debug; - -use crate::{atoms::EventId, resolver::Resolver}; +use crate::atoms::EventId; #[derive(Debug)] /// An event, is the basic building block/envolope containing all data. -pub struct Event { +pub struct Event { pub id: EventId, - pub content: CoreContent, + pub content: CoreContent, pub references: Vec, // pub author: ActorId, // pub signature: ActorSignature, @@ -14,13 +13,12 @@ pub struct Event { #[derive(Debug)] /// This defines content of an event as either a builtin event or application-specific event. -pub enum CoreContent { - Create(CreateContent), +pub enum CoreContent { + Create(CreateContent), Custom(T), - /* custom events from other places, for inspiration - + ufh (my failed project): - x.tag{.local} to add tags to events - x.annotate{.local} to add custom data to events @@ -43,14 +41,6 @@ pub enum CoreContent { #[derive(Debug)] /// Every room has exactly one immutable create event as the single root of the event dag. -pub struct CreateContent { - pub resolver: Box>, -} - -impl CreateContent { - pub fn new(resolver: Box>) -> Self { - Self { - resolver, - } - } +pub struct CreateContent { + pub resolver: String, } diff --git a/src/main.rs b/src/main.rs index 48358a5..c1599fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,115 +1,23 @@ #![allow(dead_code)] // TODO: remove this later! -use event::Event; -use std::{ - cmp::Ordering, - collections::{BTreeMap, HashMap}, - path::{Path, PathBuf}, +use crate::{ + event::CoreContent, + resolvers::{KVEventContent, PremadeResolver}, + room::Room, }; -use crate::{event::CoreContent, resolver::Resolver, room::Room}; - mod atoms; +mod error; mod event; mod resolver; +mod resolvers; mod room; -#[derive(Debug)] -/// A basic key value store -enum KVEventContent { - Set(String, String), -} - -#[derive(Debug)] -/// A basic key filesystem-like thing -enum FSEventContent { - Put(PathBuf, Vec), - Delete(PathBuf), -} - -struct KVResolver; -struct FSResolver; - -#[derive(Debug)] -struct FSState { - files: BTreeMap>, -} - -impl Resolver for KVResolver { - type Type = KVEventContent; - type State = HashMap; - - fn resolve(&self, events: &[&Event]) -> Self::State { - dbg!(events); - let mut kv = HashMap::new(); - for event in events { - match &event.content { - CoreContent::Create(_) => {} - CoreContent::Custom(KVEventContent::Set(k, v)) => { - kv.insert(k.clone(), v.clone()); - } - } - } - kv - } - - fn tiebreak( - &self, - _a: &Event, - _b: &Event, - ) -> Ordering { - Ordering::Equal - } -} - -impl Resolver for FSResolver { - type Type = FSEventContent; - type State = FSState; - - fn resolve(&self, events: &[&Event]) -> Self::State { - dbg!(events); - let mut files = BTreeMap::new(); - for event in events { - match &event.content { - CoreContent::Create(_) => {} - CoreContent::Custom(FSEventContent::Put(path, data)) => { - files.insert(path.clone(), data.clone()); - } - CoreContent::Custom(FSEventContent::Delete(match_path)) => { - files.retain(|file_path, _| !file_path.starts_with(match_path)) - } - } - } - FSState { files } - } - - fn tiebreak( - &self, - _a: &Event, - _b: &Event, - ) -> Ordering { - Ordering::Equal - } -} - -impl FSState { - fn readstr(&self, path: &Path) -> Option { - self.files - .get(path) - .map(|b| String::from_utf8_lossy(b).to_string()) - } - - fn list(&self, path: &Path) -> Vec<&Path> { - self.files - .keys() - .filter(|file_path| file_path.starts_with(path)) - .map(|p| p.as_path()) - .collect() - } -} - fn main() { - let mut room = Room::new(Box::new(KVResolver)); + let resolver = match PremadeResolver::get("kv") { + PremadeResolver::KVResolver(resolver) => resolver, + }; + let mut room = Room::new("kv", Box::new(resolver)); room.create_event(CoreContent::Custom(KVEventContent::Set( "foo".into(), "apple".into(), @@ -124,22 +32,4 @@ fn main() { ))); dbg!(&room); dbg!(&room.get_state()); - - let mut room = Room::new(Box::new(FSResolver)); - room.create_event(CoreContent::Custom(FSEventContent::Put( - "/readme.md".into(), - b"Hello, world!".into(), - ))); - room.create_event(CoreContent::Custom(FSEventContent::Put( - "/remove/me.txt".into(), - b"oops".into(), - ))); - room.create_event(CoreContent::Custom(FSEventContent::Delete( - "/remove".into(), - ))); - let state = room.get_state(); - dbg!(&room); - dbg!(&state); - dbg!(&state.list(&Path::new("/"))); - dbg!(&state.readstr(&Path::new("/readme.md"))); } diff --git a/src/resolver.rs b/src/resolver.rs index 78f9f5e..bdadd44 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -1,26 +1,29 @@ use crate::{atoms::EventId, event::Event}; use std::{cmp::Ordering, collections::HashSet, fmt::Debug}; +///! small shards of code designed to resolve state + pub trait Resolver { type Type; type State; /// Given a set of ordered events, resolve the final state - fn resolve(&self, events: &[&Event]) -> Self::State; + fn resolve(&self, events: &[&Event]) -> Self::State; /// Given two events, decide which one comes first + /// if Ordering::Equal is returned, the event id is used fn tiebreak( &self, - a: &Event, - b: &Event, + a: &Event, + b: &Event, ) -> Ordering; } /// topologically sort a list of events -pub fn sort( - tiebreak: impl Fn(&Event, &Event) -> Ordering, - events: &[Event], -) -> Vec<&Event> { +pub fn sort( + tiebreak: impl Fn(&Event, &Event) -> Ordering, + events: &[Event], +) -> Vec<&Event> { let mut references: HashSet<(EventId, EventId)> = events .iter() .flat_map(|event| { diff --git a/src/resolvers.rs b/src/resolvers.rs new file mode 100644 index 0000000..e0de391 --- /dev/null +++ b/src/resolvers.rs @@ -0,0 +1,60 @@ +use crate::{ + event::{CoreContent, Event}, resolver::Resolver +}; +use std::{cmp::Ordering, collections::BTreeMap}; + +///! contains premade resolvers, for testing + +// type KVRoom = Room; + +#[derive(Debug)] +#[non_exhaustive] +pub enum PremadeResolver { + KVResolver(KVResolver), +} + +impl PremadeResolver { + pub fn get(name: &str) -> PremadeResolver { + match name { + "kv" => PremadeResolver::KVResolver(KVResolver), + _ => unimplemented!("resolver does not exist"), + } + } +} + +#[derive(Debug)] +/// A basic key-value store +pub struct KVResolver; + +#[derive(Debug)] +pub enum KVEventContent { + Set(String, String), +} + +#[derive(Debug)] +pub struct KVState { + entries: BTreeMap, +} + +impl Resolver for KVResolver { + type Type = KVEventContent; + type State = KVState; + + fn resolve(&self, events: &[&Event]) -> Self::State { + dbg!(events); + let mut kv = BTreeMap::new(); + for event in events { + match &event.content { + CoreContent::Create(_) => {} + CoreContent::Custom(KVEventContent::Set(k, v)) => { + kv.insert(k.clone(), v.clone()); + } + } + } + KVState { entries: kv } + } + + fn tiebreak(&self, _a: &Event, _b: &Event) -> Ordering { + Ordering::Equal + } +} diff --git a/src/room.rs b/src/room.rs index e6d41d7..b713081 100644 --- a/src/room.rs +++ b/src/room.rs @@ -1,35 +1,40 @@ +use crate::{ + atoms::EventId, + event::{CoreContent, CreateContent, Event}, + resolver::{sort, Resolver}, +}; use std::fmt::Debug; -use crate::{atoms::EventId, event::{CoreContent, CreateContent, Event}, resolver::{sort, Resolver}}; #[derive(Debug)] pub struct Room { - pub(super) events: Vec>, - pub(super) heads: Vec, + pub events: Vec>, + pub heads: Vec, + pub resolver: Box>, } impl Room { - pub fn new(resolver: Box>) -> Self { + pub fn new(resolver_name: impl Into, resolver: Box>) -> Self { let event_id = EventId::random(); let base_event = Event { id: event_id.clone(), - content: CoreContent::Create(CreateContent::new(resolver)), + content: CoreContent::Create(CreateContent { + resolver: resolver_name.into(), + }), references: vec![], }; Self { events: vec![base_event], heads: vec![event_id], + resolver, } } - pub fn get_root(&self) -> &Event { + pub fn get_root(&self) -> &Event { self.events.first().as_ref().unwrap() } pub fn get_resolver(&self) -> &dyn Resolver { - match &self.get_root().content { - CoreContent::Create(create) => create.resolver.as_ref(), - _ => unreachable!("the root event is always a create event"), - } + self.resolver.as_ref() } pub fn get_state(&mut self) -> S { @@ -38,7 +43,7 @@ impl Room { resolver.resolve(&sorted) } - pub fn create_event(&mut self, event_content: CoreContent) { + pub fn create_event(&mut self, event_content: CoreContent) { let event = Event { id: EventId::random(), content: event_content, @@ -47,7 +52,7 @@ impl Room { self.append_event(event); } - pub fn append_event(&mut self, event: Event) { + pub fn append_event(&mut self, event: Event) { // let parents: Vec<_> = self // .events // .iter()