beginnings of acl
This commit is contained in:
parent
bd0b350d11
commit
9100867f51
11 changed files with 275 additions and 53 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -421,6 +421,18 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"dag-resolve",
|
||||
"dag-resolve-impls",
|
||||
"rusqlite",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "4.5.0"
|
||||
|
@ -689,18 +701,6 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dag-resolve-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"dag-resolve",
|
||||
"dag-resolve-impls",
|
||||
"rusqlite",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dag-resolve-impls"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "dag-resolve-cli"
|
||||
name = "cli"
|
||||
description = "a basic cli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -127,7 +127,7 @@ impl<R: Resolver> State<R> {
|
|||
fn open(path: impl AsRef<Path>) -> Result<Opened> {
|
||||
// restore repo
|
||||
let db = rusqlite::Connection::open(path)?;
|
||||
let actor: ActorId =
|
||||
let _actor: ActorId =
|
||||
db.query_row("SELECT value FROM _config WHERE key='actor_id'", [], |row| {
|
||||
row.get(0).map(|s: String| s.parse())
|
||||
})??;
|
||||
|
@ -136,7 +136,6 @@ fn open(path: impl AsRef<Path>) -> Result<Opened> {
|
|||
[],
|
||||
|row| row.get(0).map(|s: String| serde_json::from_str(&s)),
|
||||
)??;
|
||||
dbg!(&actor, &secret);
|
||||
let mut q = db.prepare("SELECT json FROM _events")?;
|
||||
let mut rows = q.query([])?;
|
||||
|
||||
|
@ -154,7 +153,6 @@ fn open(path: impl AsRef<Path>) -> Result<Opened> {
|
|||
drop(event);
|
||||
drop(rows);
|
||||
drop(q);
|
||||
dbg!(&room);
|
||||
let db = Rc::new(db);
|
||||
let store = Database::from_conn(db.clone())
|
||||
.init(room.get_resolver().get_state_config())?;
|
||||
|
@ -175,7 +173,6 @@ fn open(path: impl AsRef<Path>) -> Result<Opened> {
|
|||
drop(event);
|
||||
drop(rows);
|
||||
drop(q);
|
||||
dbg!(&room);
|
||||
let db = Rc::new(db);
|
||||
let store = Database::from_conn(db.clone())
|
||||
.init(room.get_resolver().get_state_config())?;
|
||||
|
@ -226,14 +223,12 @@ fn sync_state<R: Resolver + Debug>(from: &mut State<R>, to: &mut State<R>) -> Re
|
|||
}
|
||||
from.room.resolve_state(&mut *from.store);
|
||||
to.room.resolve_state(&mut *to.store);
|
||||
dbg!(&from.store, &to.store);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_event<R: Resolver + Debug>(state: &mut State<R>, data: &str) -> Result<()> {
|
||||
state.create_event(dbg!(serde_json::from_str(dbg!(data))?))?;
|
||||
dbg!(&state.room);
|
||||
dbg!(&state.room.resolve_state(&mut *state.store));
|
||||
state.room.resolve_state(&mut *state.store);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -112,8 +112,8 @@ impl Application for Main {
|
|||
let mut column = Column::new();
|
||||
column = column.push(text(format!("{} keys", rows.len())).size(24));
|
||||
for row in rows {
|
||||
let key = row.values.get("key").unwrap().as_text().unwrap();
|
||||
let value = row.values.get("value").unwrap().as_text().unwrap();
|
||||
let key = row.values.get("key").unwrap().as_string().unwrap();
|
||||
let value = row.values.get("value").unwrap().as_string().unwrap();
|
||||
column = column.push(text(format!("{key}: {value}")));
|
||||
}
|
||||
column = column.push(row![
|
||||
|
|
|
@ -5,41 +5,160 @@ use std::cmp::Ordering;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use dag_resolve::{
|
||||
actor::ActorId,
|
||||
event::{CoreContent, Event, EventId},
|
||||
proto::table::{
|
||||
ColumnType, IndexType, State, StateConfig, TableConfig, TableRow, Text,
|
||||
ColumnType, IndexType, State, StateConfig, Table as _, TableConfig, TableRow,
|
||||
Text,
|
||||
},
|
||||
resolver::{Resolver, ResolverEffect},
|
||||
resolver::{Command, Resolver, Verification},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
/// A basic key-value store
|
||||
/// An example of how a basic forum could look like
|
||||
///
|
||||
/// This is designed to be the "more fully fledged" example and will try to show off a decent number of features
|
||||
pub struct ForumResolver;
|
||||
|
||||
// TODO: document this better - this is reference for me, i should decide on actual names and make things less ambiguous
|
||||
// events are "actions", "commands", "updates", or whatever you want to call them
|
||||
// the resolver, "reducer", "reduxer", etc takes the events and the current state and returns how the new state should be mutated
|
||||
// the state is currently runtime-defined but it should be possible to statically type it to some degree (similar to eventcontent)
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// all possible actions someone could take in a forum
|
||||
pub enum ForumEventContent {
|
||||
Post {
|
||||
subject: Text,
|
||||
body: Text,
|
||||
},
|
||||
/// create a new post
|
||||
Post { subject: Text, body: Text },
|
||||
|
||||
/// reply to an existing post
|
||||
Reply {
|
||||
post: EventId,
|
||||
reference: EventId,
|
||||
body: Text,
|
||||
},
|
||||
|
||||
/// set configuration
|
||||
Config {
|
||||
name: Text,
|
||||
topic: Text,
|
||||
acl: ForumRoomAcl,
|
||||
},
|
||||
|
||||
/// Add or remove a member
|
||||
Member { id: ActorId, acl: ForumMemberAcl },
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum ForumRoomAcl {
|
||||
#[default]
|
||||
/// Everyone can send messages by default. Probably a bad idea.
|
||||
Public,
|
||||
|
||||
/// Requires people to have "voice" to send messages.
|
||||
Voice,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum ForumMemberAcl {
|
||||
/// The member is banned/muted (only meaningful in public rooms)
|
||||
Mute,
|
||||
|
||||
#[default]
|
||||
/// The member is not part of the room
|
||||
None,
|
||||
|
||||
/// The member can send messages (only meaningful in voice rooms)
|
||||
Voice,
|
||||
|
||||
/// The member can configure the room
|
||||
Operator,
|
||||
}
|
||||
|
||||
fn can_send_event(
|
||||
room_acl: ForumRoomAcl,
|
||||
author_acl: ForumMemberAcl,
|
||||
event: &ForumEventContent,
|
||||
) -> Verification {
|
||||
match (room_acl, author_acl, event) {
|
||||
// operators can do everything
|
||||
(_, ForumMemberAcl::Operator, _) => Verification::Valid,
|
||||
|
||||
// muted users can't do anything
|
||||
(_, ForumMemberAcl::Mute, _) => Verification::Unauthorized,
|
||||
|
||||
// Voice | None users can post in public rooms
|
||||
(
|
||||
ForumRoomAcl::Public,
|
||||
ForumMemberAcl::Voice | ForumMemberAcl::None,
|
||||
ForumEventContent::Post { .. } | ForumEventContent::Reply { .. },
|
||||
) => Verification::Valid,
|
||||
|
||||
// Voice users can post in voice rooms
|
||||
(
|
||||
ForumRoomAcl::Voice,
|
||||
ForumMemberAcl::Voice,
|
||||
ForumEventContent::Post { .. } | ForumEventContent::Reply { .. },
|
||||
) => Verification::Valid,
|
||||
|
||||
// otherwise, deny
|
||||
(_, ForumMemberAcl::None | ForumMemberAcl::Voice, _) => Verification::Unauthorized,
|
||||
}
|
||||
}
|
||||
|
||||
impl ForumMemberAcl {
|
||||
fn to_str(&self) -> &str {
|
||||
match self {
|
||||
ForumMemberAcl::Mute => "mute",
|
||||
ForumMemberAcl::None => "none",
|
||||
ForumMemberAcl::Voice => "voice",
|
||||
ForumMemberAcl::Operator => "operator",
|
||||
}
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"mute" => ForumMemberAcl::Mute,
|
||||
"none" => ForumMemberAcl::None,
|
||||
"voice" => ForumMemberAcl::Voice,
|
||||
"operator" => ForumMemberAcl::Operator,
|
||||
_ => unreachable!("bad data in database"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ForumRoomAcl {
|
||||
fn to_str(&self) -> &str {
|
||||
match self {
|
||||
ForumRoomAcl::Public => "public",
|
||||
ForumRoomAcl::Voice => "voice",
|
||||
}
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"public" => Self::Public,
|
||||
"voice" => Self::Voice,
|
||||
_ => unreachable!("bad data in database"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ?
|
||||
// pub struct ForumState
|
||||
|
||||
impl Resolver for ForumResolver {
|
||||
type EventType = ForumEventContent;
|
||||
|
||||
fn resolve<S: State>(&self, _state: &S, event: &Event<Self::EventType>) -> Vec<ResolverEffect> {
|
||||
fn resolve<S: State>(&self, _state: &S, event: &Event<Self::EventType>) -> Vec<Command> {
|
||||
let row = match event.content() {
|
||||
CoreContent::Create(_) => return vec![],
|
||||
CoreContent::Custom(ForumEventContent::Post { .. }) => ResolverEffect::insert(
|
||||
CoreContent::Create(_) => Command::insert(
|
||||
"members",
|
||||
TableRow::new()
|
||||
.with("actor", event.author().to_owned())
|
||||
.with("acl", ForumMemberAcl::Operator.to_str()),
|
||||
),
|
||||
CoreContent::Custom(ForumEventContent::Post { .. }) => Command::insert(
|
||||
"replies",
|
||||
TableRow::new()
|
||||
.with("event", event.id().to_owned())
|
||||
|
@ -47,16 +166,26 @@ impl Resolver for ForumResolver {
|
|||
),
|
||||
CoreContent::Custom(ForumEventContent::Reply {
|
||||
post, reference, ..
|
||||
}) => ResolverEffect::insert(
|
||||
}) => Command::insert(
|
||||
"replies",
|
||||
TableRow::new()
|
||||
.with("event", event.id().to_owned())
|
||||
.with("post", post.to_owned())
|
||||
.with("reference", reference.to_owned()),
|
||||
),
|
||||
CoreContent::Custom(ForumEventContent::Config { name, topic }) => {
|
||||
ResolverEffect::insert("config", TableRow::new().with("name", name.to_owned()).with("topic", topic.to_owned()))
|
||||
}
|
||||
CoreContent::Custom(ForumEventContent::Config { name, topic, acl }) => Command::insert(
|
||||
"config",
|
||||
TableRow::new()
|
||||
.with("name", name.to_owned())
|
||||
.with("topic", topic.to_owned())
|
||||
.with("acl", acl.to_str()),
|
||||
),
|
||||
CoreContent::Custom(ForumEventContent::Member { id, acl }) => Command::insert(
|
||||
"members",
|
||||
TableRow::new()
|
||||
.with("actor", id.to_owned())
|
||||
.with("acl", acl.to_str()),
|
||||
),
|
||||
};
|
||||
vec![row]
|
||||
}
|
||||
|
@ -75,6 +204,10 @@ impl Resolver for ForumResolver {
|
|||
.with_column("topic", ColumnType::Text)
|
||||
.with_index("name", IndexType::LookupUnique)
|
||||
.with_index("topic", IndexType::LookupUnique);
|
||||
let members = TableConfig::new()
|
||||
.with_column("actor", ColumnType::Actor)
|
||||
.with_column("acl", ColumnType::String)
|
||||
.with_index("actor", IndexType::LookupUnique);
|
||||
let posts = TableConfig::new()
|
||||
.with_column("event", ColumnType::Event)
|
||||
.with_column("time", ColumnType::Integer)
|
||||
|
@ -85,9 +218,37 @@ impl Resolver for ForumResolver {
|
|||
.with_column("parent", ColumnType::Event)
|
||||
.with_index("post", IndexType::Lookup);
|
||||
StateConfig::new()
|
||||
.with_table("posts", posts)
|
||||
.with_table("config", config)
|
||||
.with_table("members", members)
|
||||
.with_table("posts", posts)
|
||||
.with_table("replies", replies)
|
||||
.verify()
|
||||
}
|
||||
|
||||
fn verify<S: State>(&self, state: &S, event: &Event<Self::EventType>) -> Verification {
|
||||
let member_entry = state
|
||||
.table("members")
|
||||
.unwrap()
|
||||
.lookup_optional("actor", event.author().to_owned())
|
||||
.unwrap()
|
||||
.and_then(|r| r.values.get("acl").cloned())
|
||||
.and_then(|d| d.as_string().map(ToOwned::to_owned))
|
||||
.map(|s| ForumMemberAcl::from_str(&s))
|
||||
.unwrap_or_default();
|
||||
let room_entry = state
|
||||
.table("config")
|
||||
.unwrap()
|
||||
.lookup_one("key", "acl")
|
||||
.unwrap()
|
||||
.values
|
||||
.get("acl")
|
||||
.and_then(|d| d.as_string())
|
||||
.map(|s| ForumRoomAcl::from_str(s))
|
||||
.unwrap_or_default();
|
||||
|
||||
match event.content() {
|
||||
CoreContent::Create(_) => panic!("create shouldn't be handled by this"),
|
||||
CoreContent::Custom(c) => can_send_event(room_entry, member_entry, c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,15 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use dag_resolve::{
|
||||
event::{CoreContent, Event},
|
||||
proto::table::{ColumnType, IndexType, State, StateConfig, TableConfig, TableRow},
|
||||
resolver::{Resolver, ResolverEffect},
|
||||
proto::table::{ColumnType, IndexType, State, StateConfig, Table, TableConfig, TableRow},
|
||||
resolver::{Command, Resolver, Verification},
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
/// A basic key-value store
|
||||
///
|
||||
/// This is designed to be the "extremely basic and minimalistic" example
|
||||
pub struct KVResolver;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -27,15 +29,19 @@ impl KVResolver {
|
|||
impl Resolver for KVResolver {
|
||||
type EventType = KVEventContent;
|
||||
|
||||
fn resolve<S: State>(&self, _state: &S, event: &Event<KVEventContent>) -> Vec<ResolverEffect> {
|
||||
dbg!(event);
|
||||
fn resolve<S: State>(&self, _state: &S, event: &Event<KVEventContent>) -> Vec<Command> {
|
||||
match &event.content() {
|
||||
CoreContent::Create(_) => vec![],
|
||||
CoreContent::Create(_) => {
|
||||
let row = TableRow::new()
|
||||
.with("key", "owner".to_owned())
|
||||
.with("value", event.author().to_owned());
|
||||
vec![Command::insert("meta", row)]
|
||||
},
|
||||
CoreContent::Custom(KVEventContent::Set(k, v)) => {
|
||||
let row = TableRow::new()
|
||||
.with("key", k.to_owned())
|
||||
.with("value", v.to_owned());
|
||||
vec![ResolverEffect::insert("kv", row)]
|
||||
vec![Command::insert("kv", row)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,10 +55,22 @@ impl Resolver for KVResolver {
|
|||
}
|
||||
|
||||
fn get_state_config(&self) -> StateConfig {
|
||||
let meta = TableConfig::new()
|
||||
.with_column("key", ColumnType::String)
|
||||
.with_column("value", ColumnType::String)
|
||||
.with_index("key", IndexType::LookupUnique);
|
||||
let table = TableConfig::new()
|
||||
.with_column("key", ColumnType::String)
|
||||
.with_column("value", ColumnType::String)
|
||||
.with_index("key", IndexType::LookupUnique);
|
||||
StateConfig::new().with_table("kv", table).verify()
|
||||
StateConfig::new().with_table("meta", meta).with_table("kv", table).verify()
|
||||
}
|
||||
|
||||
fn verify<S: State>(&self, state: &S, event: &Event<Self::EventType>) -> Verification {
|
||||
let entry = state.table("meta").unwrap().lookup_optional("owner", event.author().to_owned()).unwrap();
|
||||
match entry {
|
||||
Some(_) => Verification::Valid,
|
||||
None => Verification::Unauthorized,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ impl table::State for Database {
|
|||
table::ColumnType::Integer => " INT",
|
||||
table::ColumnType::Float => " REAL",
|
||||
table::ColumnType::Event => " TEXT",
|
||||
table::ColumnType::Actor => " TEXT",
|
||||
});
|
||||
}
|
||||
sql.push_str(");\n");
|
||||
|
@ -114,6 +115,7 @@ impl table::Table for Table {
|
|||
table::ColumnType::Float => {
|
||||
table::ColumnValue::Float(sql_row.get(column_name.as_str()).unwrap())
|
||||
}
|
||||
table::ColumnType::Actor => todo!(),
|
||||
table::ColumnType::Event => todo!(),
|
||||
};
|
||||
map.insert(column_name.to_string(), val);
|
||||
|
@ -175,6 +177,7 @@ impl table::Table for Table {
|
|||
table::ColumnValue::Float(sql_row.get(column_name.as_str()).unwrap())
|
||||
}
|
||||
table::ColumnType::Event => todo!(),
|
||||
table::ColumnType::Actor => todo!(),
|
||||
};
|
||||
map.insert(column_name.to_string(), val);
|
||||
}
|
||||
|
@ -242,6 +245,9 @@ impl table::Table for Table {
|
|||
}
|
||||
sql.push('?');
|
||||
match (column_type, row.values.remove(name).unwrap()) {
|
||||
(table::ColumnType::Text, table::ColumnValue::Text(t)) => {
|
||||
params.push(Value::Text(t.0))
|
||||
}
|
||||
(table::ColumnType::String, table::ColumnValue::String(s)) => {
|
||||
params.push(Value::Text(s))
|
||||
}
|
||||
|
@ -251,7 +257,13 @@ impl table::Table for Table {
|
|||
(table::ColumnType::Float, table::ColumnValue::Float(r)) => {
|
||||
params.push(Value::Real(r))
|
||||
}
|
||||
_ => todo!(),
|
||||
(table::ColumnType::Event, table::ColumnValue::Event(event_id)) => {
|
||||
params.push(Value::Text(event_id.to_string()))
|
||||
}
|
||||
(table::ColumnType::Actor, table::ColumnValue::Actor(actor_id)) => {
|
||||
params.push(Value::Text(actor_id.to_string()))
|
||||
}
|
||||
(column_type, column_value) => todo!("column_type={column_type:?} with value {column_value:?}"),
|
||||
};
|
||||
}
|
||||
sql.push(')');
|
||||
|
|
|
@ -140,7 +140,7 @@ impl<T: Debug + Serialize + Clone> Event<T> {
|
|||
|
||||
// verify signature
|
||||
let value = serde_json::to_value(&shallow)?;
|
||||
let data = dbg!(canonical_json::to_string(&value)?);
|
||||
let data = canonical_json::to_string(&value)?;
|
||||
self.author.verify(data.as_bytes(), &self.signature)?;
|
||||
if self.author.get_type() != self.signature.get_type() {
|
||||
return Err(Error::MismatchedSigner {
|
||||
|
|
|
@ -9,12 +9,15 @@ pub trait Resolver {
|
|||
type EventType: Clone + Debug + Serialize + for<'a> Deserialize<'a>;
|
||||
|
||||
/// Given a set of ordered events, resolve the final state
|
||||
fn resolve<S: State>(&self, state: &S, event: &Event<Self::EventType>) -> Vec<ResolverEffect>;
|
||||
fn resolve<S: State>(&self, state: &S, event: &Event<Self::EventType>) -> Vec<Command>;
|
||||
|
||||
/// Given two events, decide which one comes first
|
||||
/// if Ordering::Equal is returned, the timestamp then event id is used
|
||||
fn tiebreak(&self, a: &Event<Self::EventType>, b: &Event<Self::EventType>) -> Ordering;
|
||||
|
||||
/// Verify if an event can be sent or not
|
||||
fn verify<S: State>(&self, state: &S, event: &Event<Self::EventType>) -> Verification;
|
||||
|
||||
/// TEMP: Get the name/id of this resolver
|
||||
fn name(&self) -> &str;
|
||||
|
||||
|
@ -22,9 +25,21 @@ pub trait Resolver {
|
|||
fn get_state_config(&self) -> StateConfig;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Verification {
|
||||
/// This event is valid
|
||||
Valid,
|
||||
|
||||
/// This event's data makes sense, but the sender doesn't have permission to send it
|
||||
Unauthorized,
|
||||
|
||||
/// This event contains invalid data
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Effects a resolver can produce
|
||||
pub enum ResolverEffect {
|
||||
pub enum Command {
|
||||
/// Insert a new row into the database
|
||||
Insert {
|
||||
table: String,
|
||||
|
@ -46,7 +61,7 @@ pub enum ResolverEffect {
|
|||
// Notify {},
|
||||
}
|
||||
|
||||
impl ResolverEffect {
|
||||
impl Command {
|
||||
pub fn insert(table: impl Into<String>, row: TableRow) -> Self {
|
||||
Self::Insert { table: table.into(), row }
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
event::EventId,
|
||||
event::{CoreContent, CreateContent, Event, HashType, SignatureType},
|
||||
proto,
|
||||
resolver::{sort, Resolver, ResolverEffect},
|
||||
resolver::{sort, Resolver, Command},
|
||||
Error, Result,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
@ -76,7 +76,7 @@ impl<R: Resolver> Room<R> {
|
|||
let effects = resolver.resolve(state, event);
|
||||
for effect in effects {
|
||||
match effect {
|
||||
ResolverEffect::Insert { table, row } => {
|
||||
Command::Insert { table, row } => {
|
||||
state.table(&table).unwrap().insert(row).unwrap();
|
||||
},
|
||||
}
|
||||
|
|
|
@ -6,10 +6,14 @@
|
|||
//! This is a work in progress; the goal is trying to find a good
|
||||
//! effort/payoff ratio in the database schema definition.
|
||||
|
||||
// TODO: properly type things
|
||||
// maybe replace TableRow with impl Serialize?
|
||||
// and make schema a trait instead of builder?
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::event::EventId;
|
||||
use crate::{actor::ActorId, event::EventId};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
// TODO: actually implement Text
|
||||
|
@ -57,6 +61,12 @@ pub enum ColumnType {
|
|||
|
||||
/// event reference
|
||||
Event,
|
||||
|
||||
/// actor reference
|
||||
Actor,
|
||||
|
||||
// TODO: enum?
|
||||
// Enum(...)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -78,6 +88,7 @@ pub struct IndexConfig {
|
|||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TableRow {
|
||||
pub id: u64,
|
||||
pub values: HashMap<String, ColumnValue>,
|
||||
}
|
||||
|
||||
|
@ -97,6 +108,9 @@ pub enum ColumnValue {
|
|||
|
||||
/// event reference
|
||||
Event(EventId),
|
||||
|
||||
/// actor reference
|
||||
Actor(ActorId),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -198,6 +212,12 @@ impl From<EventId> for ColumnValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ActorId> for ColumnValue {
|
||||
fn from(value: ActorId) -> Self {
|
||||
ColumnValue::Actor(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Text> for ColumnValue {
|
||||
fn from(value: Text) -> Self {
|
||||
ColumnValue::Text(value)
|
||||
|
@ -206,7 +226,7 @@ impl From<Text> for ColumnValue {
|
|||
|
||||
// TODO: was there a macro crate for this?
|
||||
impl ColumnValue {
|
||||
pub fn as_text(&self) -> Option<&str> {
|
||||
pub fn as_string(&self) -> Option<&str> {
|
||||
match self {
|
||||
ColumnValue::String(s) => Some(s),
|
||||
_ => None
|
||||
|
@ -244,6 +264,7 @@ impl StateConfig {
|
|||
IndexType::Ordered => match column {
|
||||
ColumnType::String | ColumnType::Integer | ColumnType::Float => {}
|
||||
ColumnType::Event => {} // does topological sorting
|
||||
ColumnType::Actor => {} // is this allowed?
|
||||
ColumnType::Text => {
|
||||
panic!("IndexType::Ordered can not be aplied to ColumnType::Text")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue