feat: moved out proto + feature flag cleanup

Co-authored-by: alemi <me@alemi.dev>
This commit is contained in:
zaaarf 2024-03-09 19:59:36 +01:00
parent 4694a01c9b
commit a38c28f401
19 changed files with 28 additions and 335 deletions

View file

@ -10,30 +10,19 @@ name = "codemp"
# core
tracing = "0.1"
# woot
codemp-woot = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/woot.git", features = ["serde"], tag = "v0.1.2", optional = true }
codemp-woot = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/woot.git", features = ["serde"], tag = "v0.1.2" }
# proto
uuid = { version = "1.3.1", features = ["v4"], optional = true }
tonic = { version = "0.9", features = ["tls", "tls-roots"], optional = true }
prost = { version = "0.11.8", optional = true }
codemp-proto = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp-proto.git", tag = "v0.6.1" }
uuid = { version = "1.7", features = ["v4"] }
tonic = { version = "0.11.0", features = ["tls", "tls-roots"] }
# api
similar = { version = "2.2", features = ["inline"], optional = true }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "sync"], optional = true }
async-trait = { version = "0.1", optional = true }
similar = { version = "2.2", features = ["inline"] }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "sync"] }
async-trait = { version = "0.1" }
# client
md5 = { version = "0.7.0", optional = true }
serde_json = { version = "1", optional = true }
tokio-stream = { version = "0.1", optional = true }
md5 = { version = "0.7.0" }
serde_json = { version = "1" }
tokio-stream = { version = "0.1" }
serde = { version = "1.0.193", features = ["derive"] }
dashmap = { version = "5.5.3", optional = true }
postcard = { version = "1.0.8", optional = true }
[build-dependencies]
tonic-build = "0.9"
[features]
default = []
api = ["dep:async-trait"]
woot = ["dep:codemp-woot", "dep:similar"]
proto = ["dep:prost", "dep:tonic", "dep:uuid"]
client = ["proto", "api", "dep:tokio", "dep:tokio-stream", "dep:uuid", "dep:md5", "dep:serde_json", "dep:dashmap", "dep:postcard"]
server = ["proto", "woot"]
dashmap = { version = "5.5.3" }
postcard = { version = "1.0.8" }

View file

@ -1,18 +0,0 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::configure()
// .build_client(cfg!(feature = "client"))
// .build_server(cfg!(feature = "server")) // FIXME if false, build fails????
// .build_transport(cfg!(feature = "proto"))
.compile(
&[
"proto/common.proto",
"proto/cursor.proto",
"proto/files.proto",
"proto/auth.proto",
"proto/workspace.proto",
"proto/buffer.proto",
],
&["proto"],
)?;
Ok(())
}

View file

@ -1,20 +0,0 @@
syntax = "proto2";
package auth;
// authenticates users, issuing tokens
service Auth {
// send credentials and join a workspace, returns ready to use token
rpc Login (WorkspaceJoinRequest) returns (Token);
}
message Token {
required string token = 1;
}
// TODO one-request-to-do-it-all from login to workspace access
message WorkspaceJoinRequest {
required string username = 1;
required string password = 2;
optional string workspace_id = 3;
}

View file

@ -1,20 +0,0 @@
syntax = "proto2";
import "common.proto";
package buffer;
// handle buffer changes, keep in sync users
service Buffer {
// attach to a buffer and receive operations
rpc Attach (stream Operation) returns (stream BufferEvent);
}
message Operation {
required bytes data = 1;
}
message BufferEvent {
required Operation op = 1;
required common.Identity user = 2;
}

View file

@ -1,18 +0,0 @@
syntax = "proto2";
package common;
// a wrapper payload representing an uuid
message Identity {
// uuid bytes, as string
required string id = 1;
}
// a collection of identities
message IdentityList {
repeated Identity users = 1;
}
//generic Empty message
message Empty { }

View file

@ -1,36 +0,0 @@
syntax = "proto2";
package cursor;
import "common.proto";
import "files.proto";
// handle cursor events and broadcast to all users
service Cursor {
// subscribe to a workspace's cursor events
rpc Attach (stream cursor.CursorPosition) returns (stream cursor.CursorEvent);
}
// a tuple indicating row and column
message RowCol {
required int32 row = 1;
required int32 col = 2;
}
// cursor position object
message CursorPosition {
// path of current buffer this cursor is into
required files.BufferNode buffer = 1;
// cursor start position
required RowCol start = 2;
// cursor end position
required RowCol end = 3;
}
// cursor event, with user id and cursor position
message CursorEvent {
// user moving the cursor
required common.Identity user = 1;
// new cursor position
required CursorPosition position = 2;
}

View file

@ -1,11 +0,0 @@
syntax = "proto2";
package files;
message BufferNode {
required string path = 1;
}
message BufferTree {
repeated BufferNode buffers = 1;
}

View file

@ -1,54 +0,0 @@
syntax = "proto2";
package workspace;
import "common.proto";
import "files.proto";
import "auth.proto";
service Workspace {
rpc Attach (common.Empty) returns (stream WorkspaceEvent);
rpc CreateBuffer (files.BufferNode) returns (common.Empty);
rpc AccessBuffer (files.BufferNode) returns (BufferCredentials);
rpc DeleteBuffer (files.BufferNode) returns (common.Empty);
rpc ListBuffers (common.Empty) returns (files.BufferTree);
rpc ListUsers (common.Empty) returns (common.IdentityList);
rpc ListBufferUsers (files.BufferNode) returns (common.IdentityList);
}
message WorkspaceEvent {
message UserJoin {
required common.Identity user = 1;
}
message UserLeave {
required common.Identity user = 1;
}
message FileCreate {
required string path = 1;
}
message FileRename {
required string before = 1;
required string after = 2;
}
message FileDelete {
required string path = 1;
}
oneof event {
UserJoin join = 1;
UserLeave leave = 2;
FileCreate create = 3;
FileRename rename = 4;
FileDelete delete = 5;
}
}
// TODO this is very ugly because we can't just return a new token (which is already smelly but whatev), we also need to tell the underlying id so that
// the client can put it as metadata while attaching, because it can't really know the underlying id that the server is using for each buffer without
// parsing the token itself. meehhhhhh, this bleeds underlying implementation to the upper levels, how can we avoid this??
message BufferCredentials {
required common.Identity id = 1;
required auth.Token token = 2;
}

View file

@ -3,7 +3,6 @@
//! an editor-friendly representation of a text change in a buffer
//! to easily interface with codemp from various editors
#[cfg(feature = "woot")]
use crate::woot::{WootResult, woot::Woot, crdt::{TextEditor, CRDT, Op}};
/// an editor-friendly representation of a text change in a buffer
@ -30,7 +29,6 @@ pub struct TextChange {
}
impl TextChange {
#[cfg(feature = "woot")]
/// create a new TextChange from the difference of given strings
pub fn from_diff(before: &str, after: &str) -> TextChange {
let diff = similar::TextDiff::from_chars(before, after);
@ -61,7 +59,6 @@ impl TextChange {
}
}
#[cfg(feature = "woot")]
/// consume the [TextChange], transforming it into a Vec of [woot::crdt::Op]
pub fn transform(self, woot: &Woot) -> WootResult<Vec<Op>> {
let mut out = Vec::new();
@ -114,12 +111,11 @@ impl TextChange {
/// convert from byte index to row and column
/// txt must be the whole content of the buffer, in order to count lines
#[cfg(feature = "proto")]
pub fn index_to_rowcol(txt: &str, index: usize) -> crate::proto::cursor::RowCol {
pub fn index_to_rowcol(txt: &str, index: usize) -> codemp_proto::cursor::RowCol {
// FIXME might panic, use .get()
let row = txt[..index].matches('\n').count() as i32;
let col = txt[..index].split('\n').last().unwrap_or("").len() as i32;
crate::proto::cursor::RowCol { row, col }
codemp_proto::cursor::RowCol { row, col }
}
}

View file

@ -10,7 +10,7 @@ use woot::woot::Woot;
use crate::errors::IgnorableError;
use crate::api::controller::ControllerWorker;
use crate::api::TextChange;
use crate::proto::buffer::{BufferEvent, Operation};
use codemp_proto::buffer::{BufferEvent, Operation};
use super::controller::BufferController;

View file

@ -12,17 +12,17 @@ use tonic::transport::{Channel, Endpoint};
use tonic::IntoRequest;
use uuid::Uuid;
use crate::proto::auth::auth_client::AuthClient;
use crate::{
api::controller::ControllerWorker,
cursor::worker::CursorWorker,
proto::{
use codemp_proto::auth::auth_client::AuthClient;
use codemp_proto::{
common::Empty,
buffer::buffer_client::BufferClient,
cursor::cursor_client::CursorClient,
auth::{Token, WorkspaceJoinRequest},
workspace::workspace_client::WorkspaceClient,
},
};
use crate::{
api::controller::ControllerWorker,
cursor::worker::CursorWorker,
workspace::Workspace
};

View file

@ -5,8 +5,8 @@
use tokio::sync::{mpsc, broadcast::{self, error::{TryRecvError, RecvError}}, Mutex, watch};
use tonic::async_trait;
use crate::{api::Controller, errors::IgnorableError, proto::cursor::{CursorEvent, CursorPosition}};
use crate::{api::Controller, errors::IgnorableError};
use codemp_proto::cursor::{CursorEvent, CursorPosition};
/// the cursor controller implementation
///
/// this contains

View file

@ -11,27 +11,3 @@ pub(crate) mod worker;
pub mod controller;
pub use controller::CursorController as Controller;
use crate::proto::cursor::RowCol;
impl From::<RowCol> for (i32, i32) {
fn from(pos: RowCol) -> (i32, i32) {
(pos.row, pos.col)
}
}
impl From::<(i32, i32)> for RowCol {
fn from((row, col): (i32, i32)) -> Self {
RowCol { row, col }
}
}
impl PartialOrd for RowCol {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match self.row.partial_cmp(&other.row) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
self.col.partial_cmp(&other.col)
}
}

View file

@ -3,7 +3,8 @@ use std::sync::Arc;
use tokio::sync::{mpsc, broadcast::{self}, Mutex, watch};
use tonic::{Streaming, async_trait};
use crate::{api::controller::ControllerWorker, errors::IgnorableError, proto::cursor::{CursorPosition, CursorEvent}};
use crate::{api::controller::ControllerWorker, errors::IgnorableError};
use codemp_proto::cursor::{CursorPosition, CursorEvent};
use super::controller::CursorController;

View file

@ -82,7 +82,6 @@ impl Display for Error {
}
}
#[cfg(feature = "client")]
impl From<tonic::Status> for Error {
fn from(status: tonic::Status) -> Self {
Error::Transport {
@ -92,7 +91,6 @@ impl From<tonic::Status> for Error {
}
}
#[cfg(feature = "client")]
impl From<tonic::transport::Error> for Error {
fn from(err: tonic::transport::Error) -> Self {
Error::Transport {
@ -102,28 +100,24 @@ impl From<tonic::transport::Error> for Error {
}
}
#[cfg(feature = "client")]
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
Error::Channel { send: true }
}
}
#[cfg(feature = "client")]
impl<T> From<tokio::sync::watch::error::SendError<T>> for Error {
fn from(_value: tokio::sync::watch::error::SendError<T>) -> Self {
Error::Channel { send: true }
}
}
#[cfg(feature = "client")]
impl From<tokio::sync::broadcast::error::RecvError> for Error {
fn from(_value: tokio::sync::broadcast::error::RecvError) -> Self {
Error::Channel { send: false }
}
}
#[cfg(feature = "client")]
impl From<tokio::sync::watch::error::RecvError> for Error {
fn from(_value: tokio::sync::watch::error::RecvError) -> Self {
Error::Channel { send: false }

View file

@ -5,22 +5,18 @@
//! the global instance reference is immutable and lazy-loaded, and requires `global` feature.
/// static global instance, allocated only if feature `global` is active
#[cfg(feature = "global")]
pub mod global {
#[cfg(not(feature = "sync"))]
lazy_static::lazy_static! {
/// the global instance of codemp session
pub static ref INSTANCE : super::a_sync::Instance = super::a_sync::Instance::default();
}
#[cfg(feature = "sync")]
lazy_static::lazy_static! {
/// the global instance of codemp session
pub static ref INSTANCE : super::sync::Instance = super::sync::Instance::default();
}
}
#[cfg(feature = "global")]
pub use global::INSTANCE;
/// async implementation of session instance

View file

@ -116,105 +116,33 @@
#![doc(html_no_source)]
/// public traits exposed to clients
#[cfg(feature = "api")]
pub mod api;
/// cursor related types and controller
#[cfg(feature = "client")]
pub mod cursor;
/// buffer operations, factory, controller and types
#[cfg(feature = "client")]
pub mod buffer;
/// crate error types and helpers
pub mod errors;
/// underlying client session manager
#[cfg(feature = "client")]
pub mod client;
/// workspace operations
#[cfg(feature = "client")]
pub mod workspace;
/// all-in-one imports : `use codemp::prelude::*;`
pub mod prelude;
/// underlying OperationalTransform library used, re-exported
#[cfg(feature = "woot")]
pub use woot;
/// protocol types and services auto-generated by grpc
#[cfg(feature = "proto")]
#[allow(non_snake_case)]
pub mod proto {
pub mod common {
tonic::include_proto!("common");
impl From<uuid::Uuid> for Identity {
fn from(id: uuid::Uuid) -> Self {
Identity { id: id.to_string() }
}
}
impl From<&uuid::Uuid> for Identity {
fn from(id: &uuid::Uuid) -> Self {
Identity { id: id.to_string() }
}
}
impl From<Identity> for uuid::Uuid {
fn from(value: Identity) -> Self {
uuid::Uuid::parse_str(&value.id).expect("invalid uuid in identity")
}
}
impl From<&Identity> for uuid::Uuid {
fn from(value: &Identity) -> Self {
uuid::Uuid::parse_str(&value.id).expect("invalid uuid in identity")
}
}
}
pub mod files {
tonic::include_proto!("files");
impl From<String> for BufferNode {
fn from(value: String) -> Self {
BufferNode { path: value }
}
}
impl From<&str> for BufferNode {
fn from(value: &str) -> Self {
BufferNode { path: value.to_string() }
}
}
impl From<BufferNode> for String {
fn from(value: BufferNode) -> Self {
value.path
}
}
}
pub mod buffer { tonic::include_proto!("buffer"); }
pub mod cursor { tonic::include_proto!("cursor"); }
pub mod workspace { tonic::include_proto!("workspace"); }
pub mod auth { tonic::include_proto!("auth"); }
}
pub use errors::Error;
pub use errors::Result;
#[cfg(feature = "client")]
pub use client::Client;
#[cfg(feature = "client")]
pub use workspace::Workspace;

View file

@ -7,16 +7,13 @@ pub use crate::{
Result as CodempResult,
};
#[cfg(feature = "woot")]
pub use crate::woot::crdt::Op as CodempOp;
#[cfg(feature = "api")]
pub use crate::api::{
Controller as CodempController,
TextChange as CodempTextChange,
};
#[cfg(feature = "client")]
pub use crate::{
// Instance as CodempInstance,
client::Client as CodempClient,
@ -25,10 +22,3 @@ pub use crate::{
cursor::Controller as CodempCursorController,
buffer::Controller as CodempBufferController,
};
#[cfg(feature = "proto")]
pub use crate::{
proto::cursor::CursorPosition as CodempCursorPosition,
proto::cursor::CursorEvent as CodempCursorEvent,
proto::cursor::RowCol as CodempRowCol,
};

View file

@ -5,8 +5,8 @@ use tonic::Streaming;
use uuid::Uuid;
use crate::{
api::controller::ControllerWorker, buffer::{self, worker::BufferWorker}, client::Services, cursor,
proto::{auth::Token, common::{Identity, Empty}, files::BufferNode, workspace::{WorkspaceEvent, workspace_event::{Event as WorkspaceEventInner, FileCreate, FileDelete, FileRename, UserJoin, UserLeave}}}
};
use codemp_proto::{auth::Token, common::{Identity, Empty}, files::BufferNode, workspace::{WorkspaceEvent, workspace_event::{Event as WorkspaceEventInner, FileCreate, FileDelete, FileRename, UserJoin, UserLeave}}};
//TODO may contain more info in the future
#[derive(Debug, Clone)]