Split State
in separate views for Api
and CommHandle
- State changes can now be handled differently, depending on whether they were caused locallly (API) or by a remote peer (Comm) - State functions have more readable names (`write...` and `update...` have similar meanings, but using different names helps readability in the respective (API/Comm) context)
This commit is contained in:
parent
32bbe8a8ce
commit
98393b9bf6
16 changed files with 326 additions and 138 deletions
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
use axum::Router;
|
||||
use tokio::{net::TcpListener, task::JoinHandle};
|
||||
|
||||
use crate::{state::State, config::ApiConfig};
|
||||
use crate::{state::{State, ApiState}, config::ApiConfig};
|
||||
|
||||
mod v0;
|
||||
|
||||
|
@ -40,11 +40,11 @@ impl From<ApiConfig> for ApiBuilder {
|
|||
}
|
||||
|
||||
impl ApiBuilder {
|
||||
pub async fn build(&self, state: Arc<State>) -> Api {
|
||||
pub async fn build(&self, state: ApiState) -> Api {
|
||||
let mut app: Router = Router::new();
|
||||
match &self.version {
|
||||
Some(v) if v == "v0" => app = app.nest(&format!("/{}", v), v0::get_router(state.clone())),
|
||||
_ => app = app.nest("/v0", v0::get_router(state.clone())),
|
||||
Some(v) if v == "v0" => app = app.nest(&format!("/{}", v), v0::get_router(state)),
|
||||
_ => app = app.nest("/v0", v0::get_router(state)),
|
||||
}
|
||||
|
||||
let ip = match &self.bind_ip {
|
||||
|
|
|
@ -1,44 +1,49 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{Router, routing::{put, get}, extract::{Path, Json}, Extension, response::{IntoResponse, Response}, http::StatusCode};
|
||||
use tracing::{warn, debug};
|
||||
|
||||
use crate::state::{State, types::{ElementId, ElementContent}};
|
||||
use crate::state::{types::{ElementId, ElementContent}, ApiState};
|
||||
|
||||
|
||||
|
||||
|
||||
pub fn get_router(state: Arc<State>) -> Router {
|
||||
pub fn get_router(state: ApiState) -> Router {
|
||||
Router::new()
|
||||
.route("/element", put(create_element))
|
||||
.route("/element/:id", get(get_element).post(set_element).delete(remove_element))
|
||||
.layer(Extension(state))
|
||||
.layer(Extension(Arc::new(state)))
|
||||
}
|
||||
|
||||
async fn get_element(Path(id): Path<ElementId>, s: Extension<Arc<State>>) -> Response {
|
||||
async fn get_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
|
||||
let element = s.get_element(&id);
|
||||
match element {
|
||||
Some(el) => (StatusCode::OK, Json{0: el}).into_response(),
|
||||
None => StatusCode::NOT_FOUND.into_response(),
|
||||
Ok(el) => (StatusCode::OK, Json{0: el}).into_response(),
|
||||
Err(e) => {
|
||||
warn!("Element not found:\n{:?}", e);
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_element(s: Extension<Arc<State>>, Json(content): Json<ElementContent>) -> Response {
|
||||
async fn create_element(s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response {
|
||||
let element_id = s.create_element(&content);
|
||||
debug!("{:?}", element_id);
|
||||
match element_id {
|
||||
Ok(id) => (StatusCode::OK, Json{0: &Into::<String>::into(&id)}).into_response(),
|
||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_element(Path(id): Path<ElementId>, s: Extension<Arc<State>>, Json(content): Json<ElementContent>) -> Response {
|
||||
let res = s.set_element_content(&id, &content);
|
||||
async fn set_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response {
|
||||
let res = s.write_element_content(&id, &content);
|
||||
match res {
|
||||
Ok(_) => StatusCode::OK.into_response(),
|
||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_element(Path(id): Path<ElementId>, s: Extension<Arc<State>>) -> Response {
|
||||
async fn remove_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
|
||||
let res = s.remove_element(&id);
|
||||
match res {
|
||||
Ok(_) => StatusCode::OK.into_response(),
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use crate::state::{State, types::PeerId};
|
||||
use crate::state::{types::PeerId, CommState};
|
||||
|
||||
use super::{messages::{Message, MessageContent}, Peer};
|
||||
use super::messages::{Message, MessageContent};
|
||||
|
||||
|
||||
|
||||
pub fn handle(state: Arc<State>, peer: &PeerId, message: Message) {
|
||||
pub fn handle(state: &CommState, peer: &PeerId, message: Message) {
|
||||
debug!("Handling message now: {:?}", message);
|
||||
match message.content() {
|
||||
MessageContent::Hello { peer_name } => {
|
||||
state.set_peer(&Peer::new(peer.clone(), peer_name.clone())).expect("Couldn't set peer");
|
||||
state.set_peer(peer, peer_name).expect("State failed");
|
||||
},
|
||||
MessageContent::CreateElement { id, content } => {
|
||||
state.set_element(id, content).expect("State failed");
|
||||
state.add_received_element(id, content, message.id()).expect("State failed");
|
||||
},
|
||||
MessageContent::SetElement { id, content } => {
|
||||
state.set_element(id, content).expect("State failed");
|
||||
state.update_element_content(id, content, message.id()).expect("State failed");
|
||||
},
|
||||
MessageContent::RemoveElement { id } => {
|
||||
state.remove_element(id).expect("State failed");
|
||||
|
|
|
@ -5,6 +5,7 @@ use tracing::{warn, error, debug};
|
|||
pub use types::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
|
@ -15,13 +16,13 @@ use tokio::sync::RwLock;
|
|||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::Config;
|
||||
use crate::state::State;
|
||||
use crate::state::CommState;
|
||||
use crate::state::types::PeerId;
|
||||
|
||||
use self::messages::Message;
|
||||
|
||||
pub struct CommHandle {
|
||||
state: Arc<State>,
|
||||
state: Arc<CommState>,
|
||||
i2p_server: Arc<I2pListener>,
|
||||
// Maps peer addresses to existing connections to them
|
||||
clients: Arc<RwLock<HashMap<I2pSocketAddr, Arc<RwLock<I2pStream>>>>>,
|
||||
|
@ -29,7 +30,7 @@ pub struct CommHandle {
|
|||
}
|
||||
|
||||
impl CommHandle {
|
||||
pub fn new(state: Arc<State>, config: &Config) -> anyhow::Result<Self> {
|
||||
pub fn new(state: CommState, config: &Config) -> anyhow::Result<Self> {
|
||||
let mut listener_builder = I2pListenerBuilder::default()
|
||||
.with_options(SAMOptions::default());
|
||||
|
||||
|
@ -42,7 +43,7 @@ impl CommHandle {
|
|||
.unwrap();
|
||||
|
||||
Ok(CommHandle {
|
||||
state: state,
|
||||
state: Arc::new(state),
|
||||
i2p_server: Arc::new(listener),
|
||||
clients: Default::default(),
|
||||
thread: RwLock::new(None),
|
||||
|
@ -155,7 +156,7 @@ impl CommHandle {
|
|||
Ok(i2p_dest)
|
||||
}
|
||||
|
||||
fn read_connection(wrapped_stream: Arc<RwLock<I2pStream>>, state: Arc<State>) -> JoinHandle<()> {
|
||||
fn read_connection(wrapped_stream: Arc<RwLock<I2pStream>>, state: Arc<CommState>) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
let mut stream = wrapped_stream.write().await;
|
||||
let peer: PeerId = stream.peer_addr().expect("Failed to get peer addr").into();
|
||||
|
@ -172,7 +173,7 @@ impl CommHandle {
|
|||
Ok(value) => {
|
||||
match serde_json::from_value::<Message>(value) {
|
||||
Ok(message) => {
|
||||
message_processor::handle(state.clone(), &peer, message);
|
||||
message_processor::handle(state.deref(), &peer, message);
|
||||
},
|
||||
Err(e) => warn!("Deserialization failed: {:?}", e),
|
||||
}
|
||||
|
@ -200,14 +201,14 @@ mod tests {
|
|||
use i2p::sam_options::SAMOptions;
|
||||
|
||||
use crate::Config;
|
||||
use crate::state::State;
|
||||
use crate::state::{State, CommState};
|
||||
use crate::comm::{messages, Message};
|
||||
use crate::state::types::ElementId;
|
||||
|
||||
use super::CommHandle;
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
pub async fn msg() {
|
||||
let ch = CommHandle::new(State::new().await.unwrap(), &Config::default() ).unwrap();
|
||||
let ch = CommHandle::new(CommState::new(State::new().await.unwrap()), &Config::default() ).unwrap();
|
||||
ch.run().await;
|
||||
println!("My address: {:?}", ch.i2p_b32_address());
|
||||
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -6,7 +6,7 @@ use comm::{CommHandle, Peer};
|
|||
use config::Config;
|
||||
use i2p::net::I2pSocketAddr;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use state::{State, types::{ElementContent, ElementId, MessageId, PeerId}};
|
||||
use state::{State, types::{ElementContent, ElementId, MessageId, PeerId}, CommState, ApiState};
|
||||
|
||||
mod api;
|
||||
pub mod comm;
|
||||
|
@ -28,10 +28,10 @@ pub struct Ubisync {
|
|||
impl Ubisync {
|
||||
pub async fn new(config: &Config) -> anyhow::Result<Self> {
|
||||
let state = State::new().await?;
|
||||
let comm_handle = Arc::new(CommHandle::new(state.clone(), config)?);
|
||||
let comm_handle = Arc::new(CommHandle::new(CommState::new(state.clone()), config)?);
|
||||
state.set_comm_handle(comm_handle.clone());
|
||||
|
||||
let api = Arc::new(ApiBuilder::from(config.api_config.clone()).build(state.clone()).await);
|
||||
let api = Arc::new(ApiBuilder::from(config.api_config.clone()).build(ApiState::new(state.clone())).await);
|
||||
|
||||
comm_handle.run().await;
|
||||
Ok(Ubisync {
|
||||
|
@ -64,10 +64,6 @@ impl Ubisync {
|
|||
pub fn get_destination(&self) -> anyhow::Result<I2pSocketAddr> {
|
||||
self.comm_handle.i2p_address()
|
||||
}
|
||||
|
||||
pub fn create_element(&self, content: &ElementContent) -> anyhow::Result<ElementId> {
|
||||
self.state_handle.create_element(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
86
src/state/api_state.rs
Normal file
86
src/state/api_state.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use cozo::DbInstance;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{state::{types::ElementId, queries}, comm::messages::MessageContent};
|
||||
|
||||
use super::{State, types::{ElementContent, Element}};
|
||||
|
||||
|
||||
|
||||
pub struct ApiState {
|
||||
state: Arc<State>,
|
||||
}
|
||||
|
||||
impl ApiState {
|
||||
pub fn new(state: Arc<State>) -> Self {
|
||||
ApiState { state: state }
|
||||
}
|
||||
|
||||
pub fn create_element(&self, content: &ElementContent) -> anyhow::Result<ElementId> {
|
||||
let id = ElementId::new();
|
||||
queries::elements::add(self.db(), &id, &content, None, true)?;
|
||||
debug!("Added element {{{}}}", &id.to_string());
|
||||
|
||||
self.state.send_to_peers(MessageContent::CreateElement { id: id.clone(), content: content.clone() });
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn write_element_content(&self, id: &ElementId, content: &ElementContent) -> anyhow::Result<()> {
|
||||
queries::elements::set_content(self.db(), id, content)?;
|
||||
queries::elements::set_local_changes(self.db(), id, true)?;
|
||||
debug!("Wrote element content {{{}}}", &id.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_element(&self, id: &ElementId) -> anyhow::Result<()> {
|
||||
let res = self.state.remove_element(id);
|
||||
debug!("Removed element {{{}}}", &id.to_string());
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_element(&self, id: &ElementId) -> anyhow::Result<Element> {
|
||||
self.state.get_element(id)
|
||||
}
|
||||
|
||||
|
||||
fn db(&self) -> &DbInstance {
|
||||
&self.state.db
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ApiState;
|
||||
use crate::state::{types::ElementContent, State};
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_element_create() {
|
||||
tracing_subscriber::fmt().pretty().init();
|
||||
let state = ApiState::new(State::new().await.unwrap());
|
||||
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap();
|
||||
let el = state.get_element(&id).unwrap();
|
||||
assert_eq!(
|
||||
ElementContent::Text("Test-text".to_string()),
|
||||
el.content().to_owned()
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_element_write() {
|
||||
tracing_subscriber::fmt().pretty().init();
|
||||
let state = ApiState::new(State::new().await.unwrap());
|
||||
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap();
|
||||
state.write_element_content(&id,&ElementContent::Text("Test-text 2".to_string())).unwrap();
|
||||
let el = state.get_element(&id).unwrap();
|
||||
assert_eq!(
|
||||
ElementContent::Text("Test-text 2".to_string()),
|
||||
el.content().to_owned()
|
||||
)
|
||||
}
|
||||
}
|
98
src/state/comm_state.rs
Normal file
98
src/state/comm_state.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use cozo::DbInstance;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{state::queries, comm::Peer};
|
||||
|
||||
use super::{State, types::{MessageId, ElementContent, ElementId, PeerId, Element}};
|
||||
|
||||
|
||||
//TODO: Notify API about changes
|
||||
pub struct CommState {
|
||||
state: Arc<State>
|
||||
}
|
||||
|
||||
impl CommState {
|
||||
pub fn new(state: Arc<State>) -> Self {
|
||||
CommState { state: state }
|
||||
}
|
||||
|
||||
pub fn add_received_element(&self, id: &ElementId, content: &ElementContent, latest_message: &MessageId) -> anyhow::Result<()> {
|
||||
queries::elements::add(self.db(), &id, &content, Some(latest_message.to_owned()), false)?;
|
||||
debug!("Added element {{{}}}", &id.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_element_content(&self, id: &ElementId, content: &ElementContent, latest_message: &MessageId) -> anyhow::Result<()> {
|
||||
//TODO: resolve potential conflicts with local changes
|
||||
queries::elements::set_content(self.db(), id, content)?;
|
||||
queries::elements::set_latest_message(self.db(), id, Some(latest_message.to_owned()))?;
|
||||
debug!("Updated element {{{}}}", &id.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_element(&self, id: &ElementId) -> anyhow::Result<()> {
|
||||
let res = self.state.remove_element(id);
|
||||
debug!("Removed element {{{}}}", &id.to_string());
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_element(&self, id: &ElementId) -> anyhow::Result<Element> {
|
||||
self.state.get_element(id)
|
||||
}
|
||||
|
||||
pub fn set_peer(&self, id: &PeerId, name: &str) -> anyhow::Result<()> {
|
||||
queries::peers::put(self.db(), id, name)?;
|
||||
debug!("Set peer {{{}}}", id.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_peers(&self) -> anyhow::Result<Vec<Peer>> {
|
||||
self.state.get_peers()
|
||||
}
|
||||
|
||||
|
||||
fn db(&self) -> &DbInstance {
|
||||
&self.state.db
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::CommState;
|
||||
use crate::state::{types::{ElementContent, ElementId, MessageId}, State};
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_element_add() {
|
||||
tracing_subscriber::fmt().pretty().init();
|
||||
let state = CommState::new(State::new().await.unwrap());
|
||||
let id = ElementId::new();
|
||||
state.add_received_element(&id, &ElementContent::Text("Test-text".to_string()), &MessageId::new()).unwrap();
|
||||
let el = state.get_element(&id).unwrap();
|
||||
assert_eq!(
|
||||
ElementContent::Text("Test-text".to_string()),
|
||||
el.content().to_owned()
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_element_update() {
|
||||
tracing_subscriber::fmt().pretty().init();
|
||||
let state = CommState::new(State::new().await.unwrap());
|
||||
let id = ElementId::new();
|
||||
state.add_received_element(&id, &ElementContent::Text("Test-text".to_string()), &MessageId::new()).unwrap();
|
||||
state.update_element_content(&id,&ElementContent::Text("Test-text 2".to_string()), &MessageId::new()).unwrap();
|
||||
let el = state.get_element(&id).unwrap();
|
||||
assert_eq!(
|
||||
ElementContent::Text("Test-text 2".to_string()),
|
||||
el.content().to_owned()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -10,9 +10,14 @@ use self::types::{ElementContent, ElementId, Element, Tag};
|
|||
|
||||
pub mod types;
|
||||
|
||||
mod api_state;
|
||||
mod comm_state;
|
||||
mod queries;
|
||||
mod schema;
|
||||
|
||||
pub use api_state::ApiState;
|
||||
pub use comm_state::CommState;
|
||||
|
||||
pub struct State {
|
||||
db: DbInstance,
|
||||
comm_handle: RwLock<Option<Arc<CommHandle>>>,
|
||||
|
@ -34,58 +39,37 @@ impl State {
|
|||
*self.comm_handle.write().as_deref_mut().expect("Could not set state's CommHandle") = Some(handle);
|
||||
}
|
||||
|
||||
|
||||
// Create an element and add it to the database
|
||||
pub fn create_element(&self, content: &ElementContent) -> anyhow::Result<ElementId> {
|
||||
let id = ElementId::new();
|
||||
queries::set_element(&self.db, &id, &content)?;
|
||||
debug!("Created element with id {:?}: {:?}", &id, self.get_element(&id));
|
||||
|
||||
self.send_to_peers(MessageContent::CreateElement { id: id.clone(), content: content.clone() });
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
// Anyone updated an element, update it in the database
|
||||
pub fn set_element(&self, element_id: &ElementId, content: &ElementContent) -> anyhow::Result<()> {
|
||||
let res = queries::set_element(&self.db, element_id, content);
|
||||
debug!("Set element with id {:?}: {:?}", element_id, self.get_element(element_id));
|
||||
|
||||
self.send_to_peers(MessageContent::SetElement { id: element_id.clone(), content: content.clone() });
|
||||
res
|
||||
}
|
||||
|
||||
pub fn set_element_content(&self, element_id: &ElementId, content: &ElementContent) -> anyhow::Result<()> {
|
||||
let res = queries::set_element_content(&self.db, element_id, content);
|
||||
debug!("Set element content with id {:?}: {:?}", element_id, self.get_element(element_id));
|
||||
|
||||
let res = queries::elements::set_content(&self.db, element_id, content);
|
||||
debug!("Set content of element with id {:?}: {:?}", element_id, self.get_element(element_id));
|
||||
|
||||
self.send_to_peers(MessageContent::SetElement { id: element_id.clone(), content: content.clone() });
|
||||
res
|
||||
}
|
||||
|
||||
pub fn remove_element(&self, element_id: &ElementId) -> anyhow::Result<()> {
|
||||
let res = queries::remove_element(&self.db, element_id);
|
||||
let res = queries::elements::remove(&self.db, element_id);
|
||||
|
||||
self.send_to_peers(MessageContent::RemoveElement { id: element_id.clone() });
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_element(&self, id: &ElementId) -> Option<Element> {
|
||||
queries::get_element(&self.db, id).ok()
|
||||
pub fn get_element(&self, id: &ElementId) -> anyhow::Result<Element> {
|
||||
queries::elements::get(&self.db, id)
|
||||
}
|
||||
|
||||
pub fn get_elements_by_tag(&self, tag: &Tag) -> Vec<ElementId> {
|
||||
queries::get_elements_by_tag(&self.db, tag)
|
||||
queries::elements::get_by_tag(&self.db, tag)
|
||||
.map_err(|e| {error!("{}", e); e})
|
||||
.unwrap_or(vec![])
|
||||
}
|
||||
|
||||
pub fn set_peer(&self, peer: &Peer) -> anyhow::Result<()> {
|
||||
queries::add_peer(&self.db, &peer.id(), &peer.name())
|
||||
queries::peers::put(&self.db, &peer.id(), &peer.name())
|
||||
}
|
||||
|
||||
pub fn get_peers(&self) -> anyhow::Result<Vec<Peer>> {
|
||||
queries::get_peers(&self.db)
|
||||
queries::peers::get(&self.db)
|
||||
}
|
||||
|
||||
|
||||
|
@ -104,37 +88,3 @@ impl State {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::state::State;
|
||||
use crate::state::types::ElementContent;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_create() {
|
||||
tracing_subscriber::fmt().pretty().init();
|
||||
let state = State::new().await.unwrap();
|
||||
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap();
|
||||
let el = state.get_element(&id).unwrap();
|
||||
assert_eq!(
|
||||
ElementContent::Text("Test-text".to_string()),
|
||||
el.content().to_owned()
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_update() {
|
||||
tracing_subscriber::fmt().pretty().init();
|
||||
let state = State::new().await.unwrap();
|
||||
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap();
|
||||
state.set_element(&id,&ElementContent::Text("Test-text 2".to_string())).unwrap();
|
||||
let el = state.get_element(&id).unwrap();
|
||||
assert_eq!(
|
||||
ElementContent::Text("Test-text 2".to_string()),
|
||||
el.content().to_owned()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,65 +3,82 @@ use std::collections::BTreeMap;
|
|||
use anyhow::{Error, bail};
|
||||
use cozo::{DbInstance, DataValue, JsonData, ScriptMutability};
|
||||
use serde_json::Value;
|
||||
use tracing::error;
|
||||
use tracing::{error, debug};
|
||||
|
||||
use crate::{state::{ElementContent, ElementId, Element, types::Tag}, run_query};
|
||||
use crate::{state::{ElementContent, ElementId, Element, types::{Tag, MessageId}}, run_query};
|
||||
|
||||
pub fn set_element(db: &DbInstance, id: &ElementId, content: &ElementContent) -> anyhow::Result<()> {
|
||||
pub fn add(db: &DbInstance, id: &ElementId, content: &ElementContent, latest_message: Option<MessageId>, local_changes: bool) -> anyhow::Result<()> {
|
||||
let params = vec![
|
||||
("id", DataValue::Str(serde_json::to_string(&id)?.into())),
|
||||
("content", DataValue::Str(serde_json::to_string(content)?.into()))
|
||||
("content", DataValue::Json(JsonData(serde_json::to_value(content)?))),
|
||||
("latest_message", match latest_message {
|
||||
Some(m) => DataValue::Str(serde_json::to_string(&m)?.into()),
|
||||
None => DataValue::Null,
|
||||
}),
|
||||
("local_changes", DataValue::Bool(local_changes)),
|
||||
];
|
||||
|
||||
match run_query!(&db, ":put elements {id => content}", params, ScriptMutability::Mutable) {
|
||||
match run_query!(&db, ":insert elements {id => content, latest_message, local_changes}", params, ScriptMutability::Mutable) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(report) => bail!(report),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_element_content(db: &DbInstance, id: &ElementId, content: &ElementContent) -> anyhow::Result<()> {
|
||||
let params = vec![
|
||||
("id", DataValue::Str(serde_json::to_string(&id)?.into())),
|
||||
("content", DataValue::Str(serde_json::to_string(content)?.into()))
|
||||
];
|
||||
|
||||
match run_query!(&db, ":put elements {id => content}", params, ScriptMutability::Mutable) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(report) => bail!(report),
|
||||
}
|
||||
pub fn set_content(db: &DbInstance, id: &ElementId, content: &ElementContent) -> anyhow::Result<()> {
|
||||
set_property(db, id, "content", DataValue::Json(JsonData(serde_json::to_value(content)?)))
|
||||
}
|
||||
|
||||
pub fn remove_element(db: &DbInstance, id: &ElementId) -> anyhow::Result<()> {
|
||||
pub fn set_latest_message(db: &DbInstance, id: &ElementId, latest_message: Option<MessageId>) -> anyhow::Result<()> {
|
||||
set_property(db, id, "latest_message", match latest_message {
|
||||
Some(m) => DataValue::Str(serde_json::to_string(&m)?.into()),
|
||||
None => DataValue::Null,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_local_changes(db: &DbInstance, id: &ElementId, local_changes: bool) -> anyhow::Result<()> {
|
||||
set_property(db, id, "local_changes", DataValue::Bool(local_changes))
|
||||
}
|
||||
|
||||
pub fn remove(db: &DbInstance, id: &ElementId) -> anyhow::Result<()> {
|
||||
match run_query!(&db, ":delete elements {id}", vec![("id", DataValue::Str(serde_json::to_string(&id)?.into()))], cozo::ScriptMutability::Mutable) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(report) => bail!(report),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_element(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> {
|
||||
pub fn get(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> {
|
||||
let mut params = BTreeMap::new();
|
||||
params.insert("id".to_string(), DataValue::Str(serde_json::to_string(&id)?.into()));
|
||||
|
||||
let result = db.run_script("
|
||||
?[content] := *elements[$id, content]
|
||||
?[content, latest_message, local_changes] := *elements[$id, content, latest_message, local_changes]
|
||||
", params, cozo::ScriptMutability::Immutable);
|
||||
match result {
|
||||
Ok(val) => {
|
||||
if let Some(firstrow) = val.rows.first() {
|
||||
if let [ DataValue::Json(JsonData(Value::String(content))) ] = firstrow.as_slice() {
|
||||
return Ok(Element::new(
|
||||
debug!("db result: {:?}", &firstrow.as_slice());
|
||||
if let [ DataValue::Json(JsonData(content)), latest_message, DataValue::Bool(local_changes) ] = firstrow.as_slice() {
|
||||
return Ok(Element::from((
|
||||
id.to_owned(),
|
||||
content.as_str().try_into()?,
|
||||
));
|
||||
}
|
||||
serde_json::from_value(content.to_owned())?,
|
||||
match latest_message {
|
||||
DataValue::Str(s) => Some(serde_json::from_str(s)?),
|
||||
_ => None,
|
||||
},
|
||||
local_changes.to_owned()
|
||||
)));
|
||||
}
|
||||
return Err(Error::msg("Could not parse db result as Element"));
|
||||
}
|
||||
else {
|
||||
return Err(Error::msg("No rows returned for element query"))
|
||||
}
|
||||
},
|
||||
Err(report) => bail!(report),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_elements_by_tag(db: &DbInstance, tag: &Tag) -> anyhow::Result<Vec<ElementId>> {
|
||||
pub fn get_by_tag(db: &DbInstance, tag: &Tag) -> anyhow::Result<Vec<ElementId>> {
|
||||
let mut params = BTreeMap::new();
|
||||
params.insert("tag".to_string(), DataValue::Str(serde_json::to_string(tag)?.into()));
|
||||
|
||||
|
@ -88,3 +105,16 @@ pub fn get_elements_by_tag(db: &DbInstance, tag: &Tag) -> anyhow::Result<Vec<Ele
|
|||
Err(report) => bail!(report),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn set_property(db: &DbInstance, id: &ElementId, key: &str, value: DataValue) -> anyhow::Result<()> {
|
||||
let params = vec![
|
||||
("id", DataValue::Str(serde_json::to_string(id)?.into())),
|
||||
(key, value)
|
||||
];
|
||||
|
||||
match run_query!(&db, format!(":update elements {{id => {key}}}"), params, ScriptMutability::Mutable) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(report) => bail!(report),
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
mod elements;
|
||||
pub mod elements;
|
||||
|
||||
pub use elements::*;
|
||||
|
||||
mod peers;
|
||||
pub use peers::*;
|
||||
pub mod peers;
|
||||
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{state::types::PeerId, comm::Peer, run_query};
|
|||
|
||||
|
||||
|
||||
pub fn add_peer(db: &DbInstance, id: &PeerId, name: &str) -> anyhow::Result<()> {
|
||||
pub fn put(db: &DbInstance, id: &PeerId, name: &str) -> anyhow::Result<()> {
|
||||
let params = vec![
|
||||
("id", DataValue::Str(serde_json::to_string(id)?.into())),
|
||||
("name", DataValue::Str(serde_json::to_string(name)?.into()))
|
||||
|
@ -19,7 +19,7 @@ pub fn add_peer(db: &DbInstance, id: &PeerId, name: &str) -> anyhow::Result<()>
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_peers(db: &DbInstance) -> anyhow::Result<Vec<Peer>> {
|
||||
pub fn get(db: &DbInstance) -> anyhow::Result<Vec<Peer>> {
|
||||
let result = db.run_script("
|
||||
?[id, name] := *peers{id, name}
|
||||
", Default::default(), cozo::ScriptMutability::Immutable);
|
||||
|
|
|
@ -17,10 +17,12 @@ pub fn add_schema(db: &DbInstance) -> anyhow::Result<()> {
|
|||
id: String,
|
||||
=>
|
||||
content: Json,
|
||||
latest_message: String?,
|
||||
local_changes: Bool,
|
||||
}}
|
||||
{:create tags {
|
||||
tag: String,
|
||||
element: String
|
||||
element: String,
|
||||
}}
|
||||
", params, cozo::ScriptMutability::Mutable) {
|
||||
Ok(_) => Ok(()),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::{ElementId, ElementContent};
|
||||
use super::{ElementId, ElementContent, MessageId};
|
||||
|
||||
|
||||
|
||||
|
@ -8,12 +8,21 @@ use super::{ElementId, ElementContent};
|
|||
pub struct Element {
|
||||
// Uuid identifying the element itself
|
||||
id: ElementId,
|
||||
content: ElementContent
|
||||
content: ElementContent,
|
||||
latest_message: Option<MessageId>,
|
||||
local_changes: bool,
|
||||
}
|
||||
|
||||
impl From<(ElementId, ElementContent, Option<MessageId>, bool)> for Element {
|
||||
fn from(value: (ElementId, ElementContent, Option<MessageId>, bool)) -> Self {
|
||||
Element { id: value.0, content: value.1, latest_message: value.2, local_changes: value.3 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn new(id: ElementId, content: ElementContent) -> Self {
|
||||
Element { id: id, content: content }
|
||||
// A new element with no latest message must have local changes
|
||||
Element { id: id, content: content, latest_message: None, local_changes: true }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &ElementId {
|
||||
|
|
|
@ -9,6 +9,12 @@ impl ElementId {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToString for ElementId {
|
||||
fn to_string(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ElementId> for String {
|
||||
fn from(value: &ElementId) -> Self {
|
||||
value.0.to_string()
|
||||
|
|
|
@ -14,6 +14,12 @@ impl PeerId {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToString for PeerId {
|
||||
fn to_string(&self) -> String {
|
||||
self.i2p_addr.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for PeerId {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
|
|
|
@ -19,12 +19,16 @@ async fn two_nodes_element_creation() {
|
|||
let test_element_content = ElementContent::Text("Text".to_string());
|
||||
let put_resp = http_client.put(&format!("http://localhost:9981/v0/element")).json(&test_element_content).send().await.unwrap();
|
||||
debug!("{:?}", &put_resp);
|
||||
let id = serde_json::from_str::<ElementId>(&put_resp.text().await.expect("No put response body")).expect("Could not deserialize ElementId");
|
||||
let put_resp_text = put_resp.text().await.expect("No put response body");
|
||||
debug!("{}", put_resp_text);
|
||||
let id = serde_json::from_str::<ElementId>(&put_resp_text).expect("Could not deserialize ElementId");
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(3000)).await;
|
||||
|
||||
let get_resp = http_client.get(&format!("http://localhost:9982/v0/element/{}", Into::<String>::into(&id))).send().await.expect("Get request failed");
|
||||
let received_element = serde_json::from_str::<Element>(&get_resp.text().await.expect("No get request body")).expect("Could not deserialize Element");
|
||||
let get_resp_text = get_resp.text().await.expect("No get request body");
|
||||
debug!("{}", get_resp_text);
|
||||
let received_element = serde_json::from_str::<Element>(&get_resp_text).expect("Could not deserialize Element");
|
||||
debug!("Other node received this element: {:?}", received_element);
|
||||
|
||||
assert_eq!(&test_element_content, received_element.content());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue