From 5ecd52d237946ee156807e8834d04053d417fafc Mon Sep 17 00:00:00 2001 From: frelodev Date: Sat, 9 Mar 2024 18:45:32 +0100 Subject: [PATCH] First commit of codemp-proto in new repo --- .gitignore | 2 ++ Cargo.toml | 15 +++++++++++ build.rs | 15 +++++++++++ proto/auth.proto | 20 +++++++++++++++ proto/buffer.proto | 20 +++++++++++++++ proto/common.proto | 18 ++++++++++++++ proto/cursor.proto | 36 +++++++++++++++++++++++++++ proto/files.proto | 11 ++++++++ proto/workspace.proto | 54 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 249 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 proto/auth.proto create mode 100644 proto/buffer.proto create mode 100644 proto/common.proto create mode 100644 proto/cursor.proto create mode 100644 proto/files.proto create mode 100644 proto/workspace.proto create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..869df07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..80314bb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "codemp-proto" +version = "0.6.0" +edition = "2021" + +[lib] +name = "codemp_proto" + +[dependencies] +prost = "0.12.3" +tonic = "0.11.0" +uuid = "1.7.0" + +[build-dependencies] +tonic-build = "0.11.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..31679d6 --- /dev/null +++ b/build.rs @@ -0,0 +1,15 @@ +fn main() -> Result<(), Box> { + tonic_build::configure() + .compile( + &[ + "proto/common.proto", + "proto/cursor.proto", + "proto/files.proto", + "proto/auth.proto", + "proto/workspace.proto", + "proto/buffer.proto", + ], + &["proto"], + )?; + Ok(()) + } diff --git a/proto/auth.proto b/proto/auth.proto new file mode 100644 index 0000000..39f7a20 --- /dev/null +++ b/proto/auth.proto @@ -0,0 +1,20 @@ +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; +} diff --git a/proto/buffer.proto b/proto/buffer.proto new file mode 100644 index 0000000..e018440 --- /dev/null +++ b/proto/buffer.proto @@ -0,0 +1,20 @@ +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; +} diff --git a/proto/common.proto b/proto/common.proto new file mode 100644 index 0000000..6b7ce31 --- /dev/null +++ b/proto/common.proto @@ -0,0 +1,18 @@ +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 { } diff --git a/proto/cursor.proto b/proto/cursor.proto new file mode 100644 index 0000000..0b4861b --- /dev/null +++ b/proto/cursor.proto @@ -0,0 +1,36 @@ +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; +} diff --git a/proto/files.proto b/proto/files.proto new file mode 100644 index 0000000..5df3461 --- /dev/null +++ b/proto/files.proto @@ -0,0 +1,11 @@ +syntax = "proto2"; + +package files; + +message BufferNode { + required string path = 1; +} + +message BufferTree { + repeated BufferNode buffers = 1; +} diff --git a/proto/workspace.proto b/proto/workspace.proto new file mode 100644 index 0000000..26eaa04 --- /dev/null +++ b/proto/workspace.proto @@ -0,0 +1,54 @@ +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; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bd4e025 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,58 @@ +#[allow(non_snake_case)] +pub mod proto { + pub mod common { + tonic::include_proto!("common"); + + impl From 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 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 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 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"); } +} \ No newline at end of file