diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0be0e4f..f2f72ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo test --verbose + - run: cargo test --verbose --features=client test-doc: runs-on: ubuntu-latest @@ -28,7 +28,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo doc --features=serialize + - run: cargo doc --features=client,serialize test-beta: runs-on: ubuntu-latest @@ -47,7 +47,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - - run: cargo test --verbose + - run: cargo test --verbose --features=client test-build: needs: [test-unit] @@ -75,21 +75,13 @@ jobs: test-coverage: needs: [test-unit] runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - features: - - java - - js - - py - - lua steps: - uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo test --no-default-features --features=ci,${{ matrix.features }},test-coverage,py-abi3,lua-jit tests::coverage + - run: cargo test --no-default-features --features=ci,test-coverage,py-abi3,lua-jit tests::coverage test-functional: needs: [test-build] @@ -100,7 +92,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo test --verbose --features=test-e2e tests::e2e + - run: cargo test --verbose --features=ci,test-e2e tests::e2e env: CODEMP_TEST_USERNAME_ALICE: ${{ secrets.CODEMP_TEST_USERNAME_ALICE }} CODEMP_TEST_PASSWORD_ALICE: ${{ secrets.CODEMP_TEST_PASSWORD_ALICE }} diff --git a/Cargo.toml b/Cargo.toml index 90e46b4..1fee634 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,15 +31,14 @@ thiserror = "2.0" # crdt diamond-types = "1.0" # proto -codemp-proto = { git = "https://github.com/hexedtech/codemp-proto", rev = "6b45fd7bd7c03ef60234880c59157481def4ca71", features = ["client"] } -uuid = { version = "1.17", features = ["v4"] } -tonic = { version = "0.14", features = ["tls-native-roots"] } +codemp-proto = { git = "https://github.com/hexedtech/codemp-proto", rev = "6b45fd7bd7c03ef60234880c59157481def4ca71", optional = true } # api -tokio = { version = "1.47", features = ["macros", "rt-multi-thread", "sync"] } xxhash-rust = { version = "0.8", features = ["xxh3"] } # client -tokio-stream = "0.1" -dashmap = "6.1" +tokio = { version = "1.47", features = ["macros", "rt-multi-thread", "sync"], optional = true } +tonic = { version = "0.14", features = ["tls-native-roots"], optional = true } +tokio-stream = { version = "0.1", optional = true } +dashmap = { version = "6.1", optional = true } # glue (multiple) tracing-subscriber = { version = "0.3", optional = true } @@ -66,6 +65,7 @@ async-trait = { version = "0.1", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } syn = { version = "2.0", features = ["full"], optional = true } regex = { version = "1.12", optional = true } +uuid = { version = "1.17", features = ["v4"], optional = true } [build-dependencies] @@ -77,17 +77,20 @@ pyo3-introspection = { version = "0.28", optional = true } [features] default = ["lua-jit", "py-abi3", "py-extmod"] +proto = ["dep:codemp-proto"] +client = ["proto", "dep:tonic", "dep:tokio", "dep:tokio-stream", "dep:dashmap", "codemp-proto?/client"] +server = ["proto", "codemp-proto?/server"] # extra async-trait = ["dep:async-trait"] -serialize = ["dep:serde", "uuid/serde"] +serialize = ["dep:serde", "uuid?/serde"] # special tests which require more setup -test-e2e = [] +test-e2e = ["client", "dep:uuid"] test-coverage = ["dep:syn", "dep:regex"] # ffi -java = ["dep:jni", "dep:tracing-subscriber", "dep:jni-toolbox", "codemp-proto/java"] -js = ["dep:napi-build", "dep:tracing-subscriber", "dep:napi", "dep:napi-derive", "codemp-proto/js"] -py = ["dep:pyo3", "dep:tracing-subscriber", "dep:pyo3-build-config", "dep:pyo3-introspection", "codemp-proto/py"] -lua = ["serialize", "dep:mlua", "dep:mlua-serde-derive", "dep:tracing-subscriber", "codemp-proto/lua"] +java = ["client", "dep:jni", "dep:tracing-subscriber", "dep:jni-toolbox", "codemp-proto/java"] +js = ["client", "dep:napi-build", "dep:tracing-subscriber", "dep:napi", "dep:napi-derive", "codemp-proto/js"] +py = ["client", "dep:pyo3", "dep:tracing-subscriber", "dep:pyo3-build-config", "dep:pyo3-introspection", "codemp-proto/py"] +lua = ["client", "serialize", "dep:mlua", "dep:mlua-serde-derive", "dep:tracing-subscriber", "dep:uuid", "codemp-proto/lua"] # ffi variants lua-jit = ["mlua?/luajit"] lua-54 = ["mlua?/lua54"] @@ -98,7 +101,7 @@ ci = [] [package.metadata.docs.rs] # enabled features when building on docs.rs -features = ["serialize"] +features = ["client", "serialize"] [profile.release] opt-level = 'z' diff --git a/dist/java/src/mp/code/Client.java b/dist/java/src/mp/code/Session.java similarity index 95% rename from dist/java/src/mp/code/Client.java rename to dist/java/src/mp/code/Session.java index d1bfa3e..6aff724 100644 --- a/dist/java/src/mp/code/Client.java +++ b/dist/java/src/mp/code/Session.java @@ -16,26 +16,26 @@ * This is the only object you are expected to hold yourself; unlike all the others, * there are no copies of it managed exclusively by the library. When this is garbage * collected, it will free the underlying memory. - * A Client is used to join and manage workspaces, and to obtain information about + * A Session is used to join and manage workspaces, and to obtain information about * the current session. */ @Getter -public final class Client { +public final class Session { private final long ptr; - Client(long ptr) { + Session(long ptr) { this.ptr = ptr; Extensions.CLEANER.register(this, () -> free(ptr)); } /** - * Connects to a remote CodeMP server and creates a {@link Client} instance + * Connects to a remote CodeMP server and creates a {@link Session} instance * for interacting with it. * @param config a {@link Config} object containing the connection settings - * @return a holder for the Client's pointer + * @return a holder for the Session's pointer * @throws ConnectionException if an error occurs in communicating with the server */ - public static native Client connect(Config config) throws ConnectionException; + public static native Session connect(Config config) throws ConnectionException; private static native UserInfo current_user(long self); @@ -222,7 +222,7 @@ public SessionEvent recv() throws ControllerException { return recv(this.ptr); } - private static native void callback(long self, Consumer cb); + private static native void callback(long self, Consumer cb); /** * Registers a callback to be invoked whenever a {@link SessionEvent} occurs. @@ -231,7 +231,7 @@ public SessionEvent recv() throws ControllerException { * you should probably spawn a new thread in here, to avoid deadlocking * @see Extensions#drive(boolean) */ - public void callback(Consumer cb) { + public void callback(Consumer cb) { callback(this.ptr, cb); } diff --git a/dist/lua/annotations.lua b/dist/lua/annotations.lua index d31e2c3..326cb0c 100644 --- a/dist/lua/annotations.lua +++ b/dist/lua/annotations.lua @@ -53,16 +53,16 @@ function StringArrayPromise:cancel() end function StringArrayPromise:and_then(cb) end ----@class (exact) ClientPromise : Promise -local ClientPromise = {} +---@class (exact) SessionPromise : Promise +local SessionPromise = {} --- block until promise is ready and return value ---- @return Client -function ClientPromise:await() end +--- @return Session +function SessionPromise:await() end --- cancel promise execution -function ClientPromise:cancel() end ----@param cb fun(x: Client) callback to invoke +function SessionPromise:cancel() end +---@param cb fun(x: Session) callback to invoke ---invoke callback asynchronously as soon as promise is ready -function ClientPromise:and_then(cb) end +function SessionPromise:and_then(cb) end ---@class (exact) WorkspacePromise : Promise @@ -216,23 +216,23 @@ function WorkspaceIdentifierListPromise:and_then(cb) end -- [[ END ASYNC STUFF ]] ----@class (exact) Client +---@class (exact) Session ---the effective local client, handling connecting to codemp server -local Client = {} +local Session = {} ---@return UserInfo ---current logged in user for this client -function Client:current_user() end +function Session:current_user() end ---@return string[] ---array of all currently active workspace names -function Client:active_workspaces() end +function Session:active_workspaces() end ---@return NilPromise ---@async ---@nodiscard ---refresh current user token if possible -function Client:refresh() end +function Session:refresh() end ---@param user string workspace owning user ---@param ws string workspace id to connect to @@ -240,26 +240,26 @@ function Client:refresh() end ---@async ---@nodiscard ---join requested workspace if possible and subscribe to event bus -function Client:attach_workspace(user, ws) end +function Session:attach_workspace(user, ws) end ---@param ws string workspace id to create ---@return NilPromise ---@async ---@nodiscard ---create a new workspace with given id -function Client:create_workspace(ws) end +function Session:create_workspace(ws) end ---@param user string workspace owning user ---@param ws string workspace id to leave ---leave workspace with given id, detaching and disconnecting -function Client:leave_workspace(user, ws) end +function Session:leave_workspace(user, ws) end ---@param ws string workspace id to delete ---@return NilPromise ---@async ---@nodiscard ---delete workspace with given id -function Client:delete_workspace(ws) end +function Session:delete_workspace(ws) end ---@param user string user owning the workspace to quit ---@param workspace string workspace to quit @@ -267,7 +267,7 @@ function Client:delete_workspace(ws) end ---@async ---@nodiscard ---quit a joined workspace, by user + workspace name -function Client:quit_workspace(user, workspace) end +function Session:quit_workspace(user, workspace) end ---@param user string user inviting us ---@param workspace string workspace being invited to @@ -275,7 +275,7 @@ function Client:quit_workspace(user, workspace) end ---@async ---@nodiscard ---accept an invite to a new workspace -function Client:accept_invite(user, workspace) end +function Session:accept_invite(user, workspace) end ---@param user string user inviting us ---@param workspace string workspace being invited to @@ -283,7 +283,7 @@ function Client:accept_invite(user, workspace) end ---@async ---@nodiscard ---reject an invite to a new workspace -function Client:reject_invite(user, workspace) end +function Session:reject_invite(user, workspace) end ---@param ws string workspace id to delete ---@param user string user name to invite to given workspace @@ -291,32 +291,32 @@ function Client:reject_invite(user, workspace) end ---@async ---@nodiscard ---grant user acccess to workspace -function Client:invite_to_workspace(ws, user) end +function Session:invite_to_workspace(ws, user) end ---@return WorkspaceIdentifierListPromise ---@async ---@nodiscard ---fetch and list owned workspaces -function Client:fetch_owned_workspaces() end +function Session:fetch_owned_workspaces() end ---@return WorkspaceIdentifierListPromise ---@async ---@nodiscard ---fetch and list joined workspaces -function Client:fetch_joined_workspaces() end +function Session:fetch_joined_workspaces() end ---@param user string user owning this workspace ---@param ws string workspace id to get ---@return Workspace? ---get an active workspace by name -function Client:get_workspace(user, ws) end +function Session:get_workspace(user, ws) end ---@param user string username to lookup ---@return UserInfoPromise ---@async ---@nodiscard ---get full user info for given username from server -function Client:get_user_info(user) end +function Session:get_user_info(user) end ---@class (exact) SessionEvent ---@field kind integer (SessionEventKind) event kind @@ -327,26 +327,26 @@ function Client:get_user_info(user) end ---@async ---@nodiscard ---try to receive session events, returning nil if none is available -function Client:try_recv() end +function Session:try_recv() end ---@return SessionEventPromise ---@async ---@nodiscard ---block until next client event and return it -function Client:recv() end +function Session:recv() end ---@return NilPromise ---@async ---@nodiscard ---block until next session event without returning it -function Client:poll() end +function Session:poll() end ---clears any previously registered session callback -function Client:clear_callback() end +function Session:clear_callback() end ----@param cb fun(w: Client) callback to invoke on each workspace event received +---@param cb fun(w: Session) callback to invoke on each workspace event received ---register a new callback to be called on session events (replaces any previously registered one) -function Client:callback(cb) end +function Session:callback(cb) end @@ -657,7 +657,7 @@ function CursorController:callback(cb) end local Codemp = {} ---@param config Config configuration for ----@return ClientPromise +---@return SessionPromise ---@async ---@nodiscard ---connect to codemp server, authenticate and return client diff --git a/src/api/config.rs b/src/api/config.rs index 08b5b80..e0c6a4e 100644 --- a/src/api/config.rs +++ b/src/api/config.rs @@ -42,22 +42,26 @@ impl Config { } } + /// get server host address #[inline] - pub(crate) fn host(&self) -> &str { + pub fn host(&self) -> &str { self.host.as_deref().unwrap_or("api.code.mp") } + /// get server port number #[inline] - pub(crate) fn port(&self) -> u16 { + pub fn port(&self) -> u16 { self.port.unwrap_or(50053) } + /// get whether TLS should be used #[inline] - pub(crate) fn tls(&self) -> bool { + pub fn tls(&self) -> bool { self.tls.unwrap_or(true) } - pub(crate) fn endpoint(&self) -> String { + /// get the actual server uri, combining its parts (tls,host,port) + pub fn endpoint(&self) -> String { format!( "{}://{}:{}", if self.tls() { "https" } else { "http" }, diff --git a/src/api/controller.rs b/src/api/controller.rs index 853f1e5..57ef262 100644 --- a/src/api/controller.rs +++ b/src/api/controller.rs @@ -78,7 +78,8 @@ pub trait AsyncReceiver: Sized + Send + Sync { pub struct ControllerCallback(pub Box); impl ControllerCallback { - pub(crate) fn call(&self, x: T) { + /// Invoke the callback blockingly, passing its associated controller + pub fn call(&self, x: T) { self.0(x) } } diff --git a/src/api/crdt.rs b/src/api/crdt.rs new file mode 100644 index 0000000..0b85b51 --- /dev/null +++ b/src/api/crdt.rs @@ -0,0 +1,51 @@ +#![allow(missing_docs)] + +pub trait CRDT: Default { + type Version; + + fn version(&self) -> Self::Version; + + fn register_agent(&mut self, agent: impl AsRef) -> usize; + + fn op(&mut self, op: Operation) -> Result; +} + +pub struct Operation { + pub agent: usize, + pub position: usize, + pub kind: OperationKind, +} + +pub enum OperationKind { + Insert(String), + Delete(usize), +} + +#[derive(Default)] +pub struct FakeCRDT; + +impl CRDT for FakeCRDT { + type Version = usize; + + fn version(&self) -> usize { + todo!() + } + + fn register_agent(&mut self, _agent: impl AsRef) -> usize { + todo!() + } + + fn op(&mut self, op: Operation) -> Result { + todo!() + } +} + +#[deprecated = "lets do this with serde..."] +pub fn encode_op(op: Operation) -> Vec { + todo!() +} + +#[deprecated = "lets do this with serde..."] +pub fn decode_op(data: Vec) -> Operation { + todo!() +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 1b789f3..98c0b26 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,8 +1,13 @@ //! # API //! These traits and structs represent the main `codemp` library API. +/// codemp internal CRDT api, abstracting away its concepts +pub mod crdt; +pub use crdt::CRDT; + /// a generic async provider for bidirectional communication pub mod controller; +pub use controller::{AsyncReceiver, AsyncSender, Controller}; /// a generic representation of a text change pub mod change; @@ -12,4 +17,4 @@ pub mod config; pub use change::{BufferUpdate, TextChange}; pub use config::Config; -pub use controller::{AsyncReceiver, AsyncSender, Controller}; + diff --git a/src/buffer/controller.rs b/src/client/buffer/controller.rs similarity index 100% rename from src/buffer/controller.rs rename to src/client/buffer/controller.rs diff --git a/src/buffer/mod.rs b/src/client/buffer/mod.rs similarity index 100% rename from src/buffer/mod.rs rename to src/client/buffer/mod.rs diff --git a/src/buffer/worker.rs b/src/client/buffer/worker.rs similarity index 100% rename from src/buffer/worker.rs rename to src/client/buffer/worker.rs diff --git a/src/cursor/controller.rs b/src/client/cursor/controller.rs similarity index 98% rename from src/cursor/controller.rs rename to src/client/cursor/controller.rs index 3caec9c..cf6f3d4 100644 --- a/src/cursor/controller.rs +++ b/src/client/cursor/controller.rs @@ -8,7 +8,7 @@ use tokio::sync::{mpsc, oneshot, watch}; use crate::{ api::{Controller, controller::{AsyncReceiver, AsyncSender, ControllerCallback}}, errors::ControllerResult, - network::AuthedService, + client::network::AuthedService, }; use codemp_proto::cursor::{CursorEvent, CursorUpdate, cursor_client::CursorClient}; diff --git a/src/cursor/mod.rs b/src/client/cursor/mod.rs similarity index 100% rename from src/cursor/mod.rs rename to src/client/cursor/mod.rs diff --git a/src/cursor/worker.rs b/src/client/cursor/worker.rs similarity index 99% rename from src/cursor/worker.rs rename to src/client/cursor/worker.rs index 109fe20..bcd8fe0 100644 --- a/src/cursor/worker.rs +++ b/src/client/cursor/worker.rs @@ -7,7 +7,7 @@ use crate::{ api::controller::ControllerCallback, errors::RemoteResult, ext::IgnorableError, - network::AuthedService, + client::network::AuthedService, }; use codemp_proto::{ common::Empty, diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 0000000..a6f7ae9 --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,5 @@ +pub mod buffer; +pub mod cursor; +pub mod session; +pub mod workspace; +pub(crate) mod network; diff --git a/src/network.rs b/src/client/network.rs similarity index 100% rename from src/network.rs rename to src/client/network.rs diff --git a/src/client.rs b/src/client/session.rs similarity index 95% rename from src/client.rs rename to src/client/session.rs index dc7d034..187def7 100644 --- a/src/client.rs +++ b/src/client/session.rs @@ -1,5 +1,5 @@ -//! ### Client -//! Main `codemp` client, containing and managing all underlying services. +//! ### Session +//! Main `codemp` session client, containing and managing all underlying services. use std::sync::Arc; @@ -13,8 +13,7 @@ use crate::{ api::AsyncReceiver, errors::{ConnectionResult, RemoteResult}, ext::{IgnorableError, InternallyMutable}, - network, - workspace::Workspace, + client::{network, workspace::Workspace}, }; use codemp_proto::{ auth::{LoginRequest, auth_client::AuthClient}, @@ -31,14 +30,14 @@ use pyo3::prelude::*; /// /// It generates a new UUID and stores user credentials upon connecting. /// -/// A new [`Client`] can be obtained with [`Client::connect`]. +/// A new [`Session`] can be obtained with [`Session::connect`]. #[derive(Debug, Clone)] #[cfg_attr(feature = "js", napi_derive::napi)] #[cfg_attr(feature = "py", pyclass(from_py_object))] -pub struct Client(Arc); +pub struct Session(Arc); #[derive(Debug)] -struct ClientInner { +struct SessionInner { user: Arc, config: crate::api::Config, workspaces: DashMap>, @@ -47,14 +46,14 @@ struct ClientInner { claims: InternallyMutable, poll_tx: tokio::sync::mpsc::UnboundedSender>, callback: - tokio::sync::watch::Sender>>, + tokio::sync::watch::Sender>>, events: tokio::sync::Mutex< tokio::sync::mpsc::UnboundedReceiver, >, } -impl Client { - /// Connect to the server, authenticate and instantiate a new [`Client`]. +impl Session { + /// Connect to the server, authenticate and instantiate a new [`Session`]. #[tracing::instrument] pub async fn connect(config: crate::api::Config) -> ConnectionResult { // TODO move these two into network.rs @@ -81,14 +80,14 @@ impl Client { let stream = session.attach(Empty {}).await?.into_inner(); - let worker = ClientWorker { + let worker = SessionWorker { callback: cb_rx, pollers: Vec::new(), poll_rx, events: ev_tx, }; - let inner = Arc::new(ClientInner { + let inner = Arc::new(SessionInner { user: Arc::new(resp.user), workspaces: DashMap::default(), poll_tx, @@ -105,7 +104,7 @@ impl Client { worker.work(stream, weak).await; }); - Ok(Client(inner)) + Ok(Session(inner)) } /// Refresh session token. @@ -376,7 +375,7 @@ impl Client { } } -impl AsyncReceiver for Client { +impl AsyncReceiver for Session { async fn try_recv( &self, ) -> crate::errors::ControllerResult> { @@ -404,20 +403,20 @@ impl AsyncReceiver for Client { } } -struct ClientWorker { +struct SessionWorker { callback: - tokio::sync::watch::Receiver>>, + tokio::sync::watch::Receiver>>, pollers: Vec>, poll_rx: tokio::sync::mpsc::UnboundedReceiver>, events: tokio::sync::mpsc::UnboundedSender, } -impl ClientWorker { +impl SessionWorker { #[tracing::instrument(skip(self, stream, weak))] pub(crate) async fn work( mut self, mut stream: tonic::Streaming, - weak: std::sync::Weak, + weak: std::sync::Weak, ) { tracing::debug!("client worker starting"); loop { @@ -464,7 +463,7 @@ impl ClientWorker { }); if let Some(cb) = self.callback.borrow().as_ref() { if let Some(ws) = weak.upgrade() { - cb.call(Client(ws)); + cb.call(Session(ws)); } else { break tracing::debug!("client worker clean (late) exit"); } diff --git a/src/workspace.rs b/src/client/workspace.rs similarity index 99% rename from src/workspace.rs rename to src/client/workspace.rs index d263696..9986cf3 100644 --- a/src/workspace.rs +++ b/src/client/workspace.rs @@ -7,10 +7,9 @@ use crate::{ api::{ controller::{AsyncReceiver, ControllerCallback}, }, - buffer, cursor, + client::{buffer, cursor, network::Services}, errors::{ConnectionResult, ControllerResult, RemoteResult}, ext::IgnorableError, - network::Services, }; use codemp_proto::{ diff --git a/src/errors.rs b/src/errors.rs index e4b97dc..218619e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -5,14 +5,17 @@ /// /// This currently wraps an [http code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status), /// returned as procedure status. +#[cfg(feature = "client")] #[derive(Debug, thiserror::Error)] #[error("server rejected procedure with error code: {0:?}")] pub struct RemoteError(#[from] tonic::Status); /// Wraps [std::result::Result] with a [RemoteError]. +#[cfg(feature = "client")] pub type RemoteResult = std::result::Result; /// An error that may occur when processing requests that require new connections. +#[cfg(feature = "client")] #[derive(Debug, thiserror::Error)] pub enum ConnectionError { /// Underlying [`tonic::transport::Error`]. @@ -24,6 +27,7 @@ pub enum ConnectionError { Remote(#[from] RemoteError), } +#[cfg(feature = "client")] impl From for ConnectionError { fn from(value: tonic::Status) -> Self { Self::Remote(RemoteError(value)) @@ -31,6 +35,7 @@ impl From for ConnectionError { } /// Wraps [std::result::Result] with a [ConnectionError]. +#[cfg(feature = "client")] pub type ConnectionResult = std::result::Result; /// An error that may occur when an [`crate::api::Controller`] attempts to @@ -47,12 +52,14 @@ pub enum ControllerError { Unfulfilled, } +#[cfg(feature = "client")] impl From> for ControllerError { fn from(_: tokio::sync::mpsc::error::SendError) -> Self { Self::Stopped } } +#[cfg(feature = "client")] impl From for ControllerError { fn from(_: tokio::sync::oneshot::error::RecvError) -> Self { Self::Unfulfilled diff --git a/src/ext.rs b/src/ext.rs index de16297..5289ba2 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -1,8 +1,6 @@ //! ### Extensions //! Contains a number of utils used internally or that may be of general interest. -use crate::{api::controller::AsyncReceiver, errors::ControllerResult}; -use tokio::sync::mpsc; /// Poll all given buffer controllers and wait, returning the first one ready. /// @@ -13,12 +11,14 @@ use tokio::sync::mpsc; /// complete. /// /// It may return an error if all buffers returned errors while polling. +#[cfg(feature = "client")] pub async fn select_buffer( - buffers: &[crate::buffer::Controller], + buffers: &[crate::client::buffer::Controller], timeout: Option, runtime: &tokio::runtime::Runtime, -) -> ControllerResult> { - let (tx, mut rx) = mpsc::unbounded_channel(); +) -> crate::errors::ControllerResult> { + use crate::api::controller::AsyncReceiver; + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); let mut tasks = Vec::new(); for buffer in buffers { let _tx = tx.clone(); @@ -62,18 +62,21 @@ pub fn hash(data: impl AsRef<[u8]>) -> i64 { /// A field that can be *internally mutated* regardless of its external mutability. /// /// Currently, it wraps the [`tokio::sync::watch`] channel couple to achieve this. +#[cfg(feature = "client")] #[derive(Debug)] pub struct InternallyMutable { getter: tokio::sync::watch::Receiver, setter: tokio::sync::watch::Sender, } +#[cfg(feature = "client")] impl Default for InternallyMutable { fn default() -> Self { Self::new(T::default()) } } +#[cfg(feature = "client")] impl InternallyMutable { /// Creates a new internally mutable type with the given value. pub fn new(init: T) -> Self { @@ -95,6 +98,7 @@ impl InternallyMutable { } } +#[cfg(feature = "client")] impl InternallyMutable { /// Gets and clones the internal value. pub fn get(&self) -> T { @@ -121,6 +125,7 @@ where } } +#[cfg(feature = "client")] pub(crate) fn token_to_metadata( tok: codemp_proto::common::Token, ) -> tonic::Result> { diff --git a/src/ffi/java/client.rs b/src/ffi/java/client.rs deleted file mode 100644 index 220c2f5..0000000 --- a/src/ffi/java/client.rs +++ /dev/null @@ -1,182 +0,0 @@ -use jni_toolbox::jni; - -use crate::{ - errors::{ConnectionError, ControllerError, RemoteError}, - prelude::* -}; - -/// Connect using the given credentials to the default server, and return a [Client] to interact with it. -#[jni(package = "mp.code", class = "Client")] -fn connect(config: CodempConfig) -> Result { - super::tokio().block_on(CodempClient::connect(config)) -} - -/// Gets the [UserInfo] for the current user. -#[jni(package = "mp.code", class = "Client")] -fn current_user(client: &mut CodempClient) -> CodempUserInfo { - client.current_user().clone() -} - -/// Join a [Workspace] and return a pointer to it. -#[jni(package = "mp.code", class = "Client")] -fn attach_workspace( - client: &mut CodempClient, - user: String, - workspace: String, -) -> Result { - super::tokio().block_on(client.attach_workspace(user, workspace)) -} - -/// Accepts an invitation to a workspace. -#[jni(package = "mp.code", class = "Client")] -fn accept_invite(client: &mut CodempClient, user: String, workspace: String) -> Result<(), RemoteError> { - super::tokio().block_on(client.accept_invite(user, workspace)) -} - -/// Rejects an invitation to a workspace. -#[jni(package = "mp.code", class = "Client")] -fn reject_invite(client: &mut CodempClient, user: String, workspace: String) -> Result<(), RemoteError> { - super::tokio().block_on(client.reject_invite(user, workspace)) -} - -/// Quit a joined [Workspace]. -#[jni(package = "mp.code", class = "Client")] -fn quit_workspace(client: &mut CodempClient, user: String, workspace: String) -> Result<(), RemoteError> { - super::tokio().block_on(client.quit_workspace(user, workspace)) -} - -/// Create a workspace on server, if allowed to. -#[jni(package = "mp.code", class = "Client")] -fn create_workspace(client: &mut CodempClient, workspace: String) -> Result<(), RemoteError> { - super::tokio().block_on(client.create_workspace(workspace)) -} - -/// Delete a workspace on server, if allowed to. -#[jni(package = "mp.code", class = "Client")] -fn delete_workspace(client: &mut CodempClient, workspace: String) -> Result<(), RemoteError> { - super::tokio().block_on(client.delete_workspace(workspace)) -} - -/// Invite another user to an owned workspace. -#[jni(package = "mp.code", class = "Client")] -fn invite_to_workspace( - client: &mut CodempClient, - workspace: String, - user: String, -) -> Result<(), RemoteError> { - super::tokio().block_on(client.invite_to_workspace(workspace, user)) -} - -/// List owned workspaces. -#[jni(package = "mp.code", class = "Client")] -fn fetch_owned_workspaces(client: &mut CodempClient) -> Result, RemoteError> { - super::tokio().block_on(client.fetch_owned_workspaces()) -} - -/// List joined workspaces. -#[jni(package = "mp.code", class = "Client")] -fn fetch_joined_workspaces(client: &mut CodempClient) -> Result, RemoteError> { - super::tokio().block_on(client.fetch_joined_workspaces()) -} - -/// List available workspaces. -#[jni(package = "mp.code", class = "Client")] -fn active_workspaces(client: &mut CodempClient) -> Vec { - client.active_workspaces() -} - -/// Leave a [Workspace] and return whether or not the client was in such workspace. -#[jni(package = "mp.code", class = "Client")] -fn leave_workspace(client: &mut CodempClient, user: String, workspace: String) -> bool { - client.leave_workspace(user, workspace) -} - -/// Get a [Workspace] by name and returns a pointer to it. -#[jni(package = "mp.code", class = "Client")] -fn get_workspace(client: &mut CodempClient, user: String, workspace: String) -> Option { - client.get_workspace(user, workspace) -} - -/// Fetches information about a user. -#[jni(package = "mp.code", class = "Client")] -fn get_user_info(client: &mut CodempClient, user: String) -> Result { - super::tokio().block_on(client.get_user_info(user)) -} - -/// Try to fetch a [TextChange], or return null if there's nothing. -#[jni(package = "mp.code", class = "Client")] -fn try_recv(client: &mut CodempClient) -> Result, ControllerError> { - super::tokio().block_on(client.try_recv()) -} - -/// Block until it receives a [TextChange]. -#[jni(package = "mp.code", class = "Client")] -fn recv(client: &mut CodempClient) -> Result { - super::tokio().block_on(client.recv()) -} - -/// Register a callback for client changes. -#[jni(package = "mp.code", class = "Client")] -fn callback<'local>( - env: &mut jni::Env<'local>, - client: &mut CodempClient, - cb: jni::objects::JObject<'local>, -) -> Result<(), jni::errors::Error> { - if cb.is_null() { - return Err(jni::errors::Error::NullPtr( - "null pointer to buffer callback", - )); - } - - let cb_ref = env.new_global_ref(cb)?; - let jvm = env.get_java_vm()?; - - client.callback(move |controller: CodempClient| { - let result: Result<(), jni::errors::Error> = jvm.attach_current_thread(|env| { - env.with_local_frame(5, |env| { - use jni_toolbox::IntoJavaObject; - let jclient = controller.into_java_object(env)?; - env.call_method( - &cb_ref, - jni::jni_str!("accept"), - jni::jni_sig!((event: java.lang.Object) -> ()), - &[jni::objects::JValue::Object(&jclient)], - )?; - Ok::<(), jni::errors::Error>(()) - })?; - - Ok(()) - }); - - if let Err(e) = result { - tracing::error!("error invoking client callback: {e}"); - } - }); - - Ok(()) -} - -/// Clear the callback for client changes. -#[jni(package = "mp.code", class = "Client")] -fn clear_callback(client: &mut CodempClient) { - client.clear_callback() -} - -/// Block until there is a new value available. -#[jni(package = "mp.code", class = "Client")] -fn poll(client: &mut CodempClient) -> Result<(), ControllerError> { - super::tokio().block_on(client.poll()) -} - -/// Refresh the client's session token. -#[jni(package = "mp.code", class = "Client")] -fn refresh(client: &mut CodempClient) -> Result<(), RemoteError> { - super::tokio().block_on(client.refresh()) -} - -/// Called by the Java GC to drop a [Client]. -#[allow(unsafe_code)] -#[jni(package = "mp.code", class = "Client")] -fn free(input: jni::sys::jlong) { - let _ = unsafe { Box::from_raw(input as *mut CodempClient) }; -} diff --git a/src/ffi/java/mod.rs b/src/ffi/java/mod.rs index b75d3e7..1234ec9 100644 --- a/src/ffi/java/mod.rs +++ b/src/ffi/java/mod.rs @@ -1,8 +1,8 @@ /// FFI methods relating to buffers. pub mod buffer; -/// FFI methods relating to clients. -pub mod client; +/// FFI methods relating to sessions. +pub mod session; /// FFI methods relating to cursors. pub mod cursor; @@ -131,7 +131,7 @@ macro_rules! java_ptr_class { }; } -java_ptr_class!(crate::prelude::CodempClient, "mp/code/Client"); +java_ptr_class!(crate::prelude::CodempSession, "mp/code/Session"); java_ptr_class!(crate::prelude::CodempWorkspace, "mp/code/Workspace"); java_ptr_class!( crate::prelude::CodempBufferController, diff --git a/src/ffi/java/session.rs b/src/ffi/java/session.rs new file mode 100644 index 0000000..19ae52c --- /dev/null +++ b/src/ffi/java/session.rs @@ -0,0 +1,182 @@ +use jni_toolbox::jni; + +use crate::{ + errors::{ConnectionError, ControllerError, RemoteError}, + prelude::* +}; + +/// Connect using the given credentials to the default server, and return a [Session] to interact with it. +#[jni(package = "mp.code", class = "Session")] +fn connect(config: CodempConfig) -> Result { + super::tokio().block_on(CodempSession::connect(config)) +} + +/// Gets the [UserInfo] for the current user. +#[jni(package = "mp.code", class = "Session")] +fn current_user(session: &mut CodempSession) -> CodempUserInfo { + session.current_user().clone() +} + +/// Join a [Workspace] and return a pointer to it. +#[jni(package = "mp.code", class = "Session")] +fn attach_workspace( + session: &mut CodempSession, + user: String, + workspace: String, +) -> Result { + super::tokio().block_on(session.attach_workspace(user, workspace)) +} + +/// Accepts an invitation to a workspace. +#[jni(package = "mp.code", class = "Session")] +fn accept_invite(session: &mut CodempSession, user: String, workspace: String) -> Result<(), RemoteError> { + super::tokio().block_on(session.accept_invite(user, workspace)) +} + +/// Rejects an invitation to a workspace. +#[jni(package = "mp.code", class = "Session")] +fn reject_invite(session: &mut CodempSession, user: String, workspace: String) -> Result<(), RemoteError> { + super::tokio().block_on(session.reject_invite(user, workspace)) +} + +/// Quit a joined [Workspace]. +#[jni(package = "mp.code", class = "Session")] +fn quit_workspace(session: &mut CodempSession, user: String, workspace: String) -> Result<(), RemoteError> { + super::tokio().block_on(session.quit_workspace(user, workspace)) +} + +/// Create a workspace on server, if allowed to. +#[jni(package = "mp.code", class = "Session")] +fn create_workspace(session: &mut CodempSession, workspace: String) -> Result<(), RemoteError> { + super::tokio().block_on(session.create_workspace(workspace)) +} + +/// Delete a workspace on server, if allowed to. +#[jni(package = "mp.code", class = "Session")] +fn delete_workspace(session: &mut CodempSession, workspace: String) -> Result<(), RemoteError> { + super::tokio().block_on(session.delete_workspace(workspace)) +} + +/// Invite another user to an owned workspace. +#[jni(package = "mp.code", class = "Session")] +fn invite_to_workspace( + session: &mut CodempSession, + workspace: String, + user: String, +) -> Result<(), RemoteError> { + super::tokio().block_on(session.invite_to_workspace(workspace, user)) +} + +/// List owned workspaces. +#[jni(package = "mp.code", class = "Session")] +fn fetch_owned_workspaces(session: &mut CodempSession) -> Result, RemoteError> { + super::tokio().block_on(session.fetch_owned_workspaces()) +} + +/// List joined workspaces. +#[jni(package = "mp.code", class = "Session")] +fn fetch_joined_workspaces(session: &mut CodempSession) -> Result, RemoteError> { + super::tokio().block_on(session.fetch_joined_workspaces()) +} + +/// List available workspaces. +#[jni(package = "mp.code", class = "Session")] +fn active_workspaces(session: &mut CodempSession) -> Vec { + session.active_workspaces() +} + +/// Leave a [Workspace] and return whether or not the session was in such workspace. +#[jni(package = "mp.code", class = "Session")] +fn leave_workspace(session: &mut CodempSession, user: String, workspace: String) -> bool { + session.leave_workspace(user, workspace) +} + +/// Get a [Workspace] by name and returns a pointer to it. +#[jni(package = "mp.code", class = "Session")] +fn get_workspace(session: &mut CodempSession, user: String, workspace: String) -> Option { + session.get_workspace(user, workspace) +} + +/// Fetches information about a user. +#[jni(package = "mp.code", class = "Session")] +fn get_user_info(session: &mut CodempSession, user: String) -> Result { + super::tokio().block_on(session.get_user_info(user)) +} + +/// Try to fetch a [TextChange], or return null if there's nothing. +#[jni(package = "mp.code", class = "Session")] +fn try_recv(session: &mut CodempSession) -> Result, ControllerError> { + super::tokio().block_on(session.try_recv()) +} + +/// Block until it receives a [TextChange]. +#[jni(package = "mp.code", class = "Session")] +fn recv(session: &mut CodempSession) -> Result { + super::tokio().block_on(session.recv()) +} + +/// Register a callback for session changes. +#[jni(package = "mp.code", class = "Session")] +fn callback<'local>( + env: &mut jni::Env<'local>, + session: &mut CodempSession, + cb: jni::objects::JObject<'local>, +) -> Result<(), jni::errors::Error> { + if cb.is_null() { + return Err(jni::errors::Error::NullPtr( + "null pointer to buffer callback", + )); + } + + let cb_ref = env.new_global_ref(cb)?; + let jvm = env.get_java_vm()?; + + session.callback(move |controller: CodempSession| { + let result: Result<(), jni::errors::Error> = jvm.attach_current_thread(|env| { + env.with_local_frame(5, |env| { + use jni_toolbox::IntoJavaObject; + let jsession = controller.into_java_object(env)?; + env.call_method( + &cb_ref, + jni::jni_str!("accept"), + jni::jni_sig!((event: java.lang.Object) -> ()), + &[jni::objects::JValue::Object(&jsession)], + )?; + Ok::<(), jni::errors::Error>(()) + })?; + + Ok(()) + }); + + if let Err(e) = result { + tracing::error!("error invoking session callback: {e}"); + } + }); + + Ok(()) +} + +/// Clear the callback for session changes. +#[jni(package = "mp.code", class = "Session")] +fn clear_callback(session: &mut CodempSession) { + session.clear_callback() +} + +/// Block until there is a new value available. +#[jni(package = "mp.code", class = "Session")] +fn poll(session: &mut CodempSession) -> Result<(), ControllerError> { + super::tokio().block_on(session.poll()) +} + +/// Refresh the session's token. +#[jni(package = "mp.code", class = "Session")] +fn refresh(session: &mut CodempSession) -> Result<(), RemoteError> { + super::tokio().block_on(session.refresh()) +} + +/// Called by the Java GC to drop a [`CodempSession`]. +#[allow(unsafe_code)] +#[jni(package = "mp.code", class = "Session")] +fn free(input: jni::sys::jlong) { + let _ = unsafe { Box::from_raw(input as *mut CodempSession) }; +} diff --git a/src/ffi/js/mod.rs b/src/ffi/js/mod.rs index 9b32ae5..403593b 100644 --- a/src/ffi/js/mod.rs +++ b/src/ffi/js/mod.rs @@ -1,5 +1,5 @@ mod buffer; -mod client; +mod session; mod cursor; mod ext; mod workspace; diff --git a/src/ffi/js/client.rs b/src/ffi/js/session.rs similarity index 92% rename from src/ffi/js/client.rs rename to src/ffi/js/session.rs index 914d86d..d3b45a8 100644 --- a/src/ffi/js/client.rs +++ b/src/ffi/js/session.rs @@ -4,22 +4,22 @@ use napi_derive::napi; use crate::prelude::{ CodempAsyncReceiver as AsyncReceiver, CodempConfig as Config, - CodempClient as Client, + CodempSession as Session, CodempUserInfo as UserInfo, CodempSessionEvent as SessionEvent, CodempWorkspace as Workspace, CodempWorkspaceIdentifier as WorkspaceIdentifier, }; -/// connect to codemp servers and return a client session +/// connect to codemp servers and return a session #[allow(dead_code)] #[napi] -pub async fn connect(config: Config) -> napi::Result { - Ok(Client::connect(config).await?) +pub async fn connect(config: Config) -> napi::Result { + Ok(Session::connect(config).await?) } #[napi] -impl Client { +impl Session { #[napi(js_name = "createWorkspace")] /// create workspace with given id, if able to pub async fn js_create_workspace(&self, workspace: String) -> napi::Result<()> { @@ -85,7 +85,7 @@ impl Client { } #[napi(js_name = "refresh")] - /// refresh client session token + /// refresh session token pub async fn js_refresh(&self) -> napi::Result<()> { Ok(self.refresh().await?) } @@ -118,13 +118,13 @@ impl Client { /// There can only be one callback registered at any given time. #[napi( js_name = "callback", - ts_args_type = "fun: (err: Error|null, event: Client) => void" + ts_args_type = "fun: (err: Error|null, event: Session) => void" )] pub fn js_callback( &self, - fun: ThreadsafeFunction, + fun: ThreadsafeFunction, ) -> napi::Result<()> { - self.callback(move |controller: Client| { + self.callback(move |controller: Session| { fun.call(Ok(controller.clone()), ThreadsafeFunctionCallMode::Blocking); //check this with tracing also we could use Ok(event) to get the error // If it blocks the main thread too many time we have to change this diff --git a/src/ffi/lua/ext/callback.rs b/src/ffi/lua/ext/callback.rs index 0a708ff..3e5209d 100644 --- a/src/ffi/lua/ext/callback.rs +++ b/src/ffi/lua/ext/callback.rs @@ -120,7 +120,7 @@ callback_args! { VecStr: Vec, UserInfo: CodempUserInfo, VecUserInfo: Vec, - Client: CodempClient, + Session: CodempSession, CursorController: CodempCursorController, BufferController: CodempBufferController, Workspace: CodempWorkspace, diff --git a/src/ffi/lua/mod.rs b/src/ffi/lua/mod.rs index 7c4a5e2..f8c10d4 100644 --- a/src/ffi/lua/mod.rs +++ b/src/ffi/lua/mod.rs @@ -1,5 +1,5 @@ mod buffer; -mod client; +mod session; mod cursor; mod ext; mod workspace; @@ -32,7 +32,7 @@ fn entrypoint(lua: &Lua) -> LuaResult { exports.set( "connect", lua.create_function( - |_, (config,): (CodempConfig,)| ext::a_sync::a_sync! { => CodempClient::connect(config).await? }, + |_, (config,): (CodempConfig,)| ext::a_sync::a_sync! { => CodempSession::connect(config).await? }, )?, )?; diff --git a/src/ffi/lua/client.rs b/src/ffi/lua/session.rs similarity index 95% rename from src/ffi/lua/client.rs rename to src/ffi/lua/session.rs index 1250f43..d10b59a 100644 --- a/src/ffi/lua/client.rs +++ b/src/ffi/lua/session.rs @@ -3,7 +3,7 @@ use mlua::prelude::*; use super::ext::a_sync::a_sync; -impl LuaUserData for CodempClient { +impl LuaUserData for CodempSession { fn add_methods>(methods: &mut M) { methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| { Ok(format!("{:?}", this)) @@ -111,7 +111,7 @@ impl LuaUserData for CodempClient { methods.add_method("callback", |lua, this, (cb,): (LuaFunction,)| { let key = this.lua_callback_id(); lua.set_named_registry_value(&key, cb)?; - Ok(this.callback(move |controller: CodempClient| { + Ok(this.callback(move |controller: CodempSession| { super::ext::callback().invoke(key.clone(), controller, false) })) }); @@ -123,10 +123,10 @@ impl LuaUserData for CodempClient { } } -impl CodempClient { +impl CodempSession { fn lua_callback_id(&self) -> String { format!( - "codemp-client({})-callback-registry", + "codemp-session({})-callback-registry", self.current_user().name ) } diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 8210e6d..5cfc163 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -7,14 +7,14 @@ //! use codemp::api::controller::{AsyncReceiver, AsyncSender}; // needed for send/recv trait methods //! //! // connect first, api.code.mp is managed by hexed.technology -//! let client = codemp::Client::connect(codemp::api::Config { +//! let session = codemp::Session::connect(codemp::api::Config { //! username: "mail@example.net".into(), password: "dont-use-this-password".into(), //! ..Default::default() //! }).await?; //! //! // create and join a workspace -//! client.create_workspace("some-workspace").await?; -//! let workspace = client.attach_workspace("my-username", "some-workspace").await?; +//! session.create_workspace("some-workspace").await?; +//! let workspace = session.attach_workspace("my-username", "some-workspace").await?; //! //! // create a new buffer in this workspace and attach to it //! workspace.create_buffer("/my/file.txt", None).await?; diff --git a/src/ffi/python/mod.rs b/src/ffi/python/mod.rs index 9d692e3..6ab779b 100644 --- a/src/ffi/python/mod.rs +++ b/src/ffi/python/mod.rs @@ -1,4 +1,4 @@ -mod client; +mod session; mod controllers; mod workspace; @@ -309,11 +309,11 @@ fn connect(py: Python, config: Py) -> PyResult { let conf: CodempConfig = config.extract(py)?; Ok(Promise(Some(crate::ffi::python::tokio().spawn( async move { - let client = CodempClient::connect(conf).await?; - Python::attach(|py| Ok(client.into_pyobject(py)?.into_any().unbind())) + let session = CodempSession::connect(conf).await?; + Python::attach(|py| Ok(session.into_pyobject(py)?.into_any().unbind())) }, )))) - // a_sync!(Client::connect(conf).await) + // a_sync!(Session::connect(conf).await) } #[pyfunction] @@ -456,7 +456,7 @@ mod pycodemp { use super::CodempSessionEvent; #[pymodule_export] - use super::CodempClient; + use super::CodempSession; #[pymodule_export] use super::CodempConfig; diff --git a/src/ffi/python/client.rs b/src/ffi/python/session.rs similarity index 97% rename from src/ffi/python/client.rs rename to src/ffi/python/session.rs index 9ecfb74..47629db 100644 --- a/src/ffi/python/client.rs +++ b/src/ffi/python/session.rs @@ -3,14 +3,14 @@ use crate::prelude::*; use pyo3::prelude::*; #[pymethods] -impl CodempClient { +impl CodempSession { // #[new] // fn __new__( // host: String, // username: String, // password: String, // ) -> crate::errors::ConnectionResult { - // super::tokio().block_on(Client::connect(host, username, password)) + // super::tokio().block_on(Session::connect(host, username, password)) // } #[pyo3(name = "refresh")] diff --git a/src/lib.rs b/src/lib.rs index 7472938..b313cbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! It is built as a batteries-included client library managing an authenticated user, multiple //! workspaces each containing any number of buffers. //! -//! The [`Client`] is completely managed by the library itself, making its use simple across async +//! The [`Session`] is completely managed by the library itself, making its use simple across async //! contexts and FFI boundaries. All memory is managed by the library itself, which gives out always //! atomic reference-counted pointers to internally mutable objects. Asynchronous actions are //! abstracted away by the [`api::Controller`], providing an unopinionated approach with both @@ -15,12 +15,12 @@ //! to support a potentially infinite number of editors. //! //! # Overview -//! The main entrypoint is [`Client::connect`], which establishes an authenticated connection with -//! a supported remote server and returns a [`Client`] handle to interact with it. +//! The main entrypoint is [`Session::connect`], which establishes an authenticated connection with +//! a supported remote server and returns a [`Session`] handle to interact with it. //! //! ```no_run //! # async { -//! let client = codemp::Client::connect( +//! let client = codemp::Session::connect( //! codemp::api::Config::new( //! "mail@example.net", //! "dont-use-this-password" @@ -31,12 +31,12 @@ //! # }; //! ``` //! -//! A [`Client`] can acquire a [`Workspace`] handle by joining an existing one it can access with -//! [`Client::attach_workspace`] or create a new one with [`Client::create_workspace`]. +//! A [`Session`] can acquire a [`Workspace`] handle by joining an existing one it can access with +//! [`Session::attach_workspace`] or create a new one with [`Session::create_workspace`]. //! //! ```no_run //! # async { -//! # let client = codemp::Client::connect(codemp::api::Config::new("", "")).await.unwrap(); +//! # let client = codemp::Session::connect(codemp::api::Config::new("", "")).await.unwrap(); //! client.create_workspace("my-workspace").await.expect("failed to create workspace!"); //! let workspace = client.attach_workspace("my-user", "my-workspace").await.expect("failed to attach!"); //! # }; @@ -48,7 +48,7 @@ //! //! ```no_run //! # async { -//! # let client = codemp::Client::connect(codemp::api::Config::new("", "")).await.unwrap(); +//! # let client = codemp::Session::connect(codemp::api::Config::new("", "")).await.unwrap(); //! # client.create_workspace("").await.unwrap(); //! # let workspace = client.attach_workspace("", "").await.unwrap(); //! use codemp::api::controller::{AsyncSender, AsyncReceiver}; // needed to access trait methods @@ -64,7 +64,7 @@ //! //! ```no_run //! # async { -//! # let client = codemp::Client::connect(codemp::api::Config::new("", "")).await.unwrap(); +//! # let client = codemp::Session::connect(codemp::api::Config::new("", "")).await.unwrap(); //! # client.create_workspace("").await.unwrap(); //! # let workspace = client.attach_workspace("", "").await.unwrap(); //! # use codemp::api::controller::{AsyncSender, AsyncReceiver}; @@ -100,19 +100,17 @@ /// core structs and traits pub mod api; -/// cursor related types and controller -pub mod cursor; - -/// buffer related types and controller -pub mod buffer; - -/// workspace handle and operations -pub mod workspace; -pub use workspace::Workspace; - -/// client handle, containing all of the above +/// client handle, containing all needed components +#[cfg(feature = "client")] pub mod client; -pub use client::Client; +#[cfg(feature = "client")] +pub use client::workspace::Workspace; +#[cfg(feature = "client")] +pub use client::session::Session; + +/// language-specific ffi "glue" +#[cfg(feature = "client")] +pub mod ffi; /// crate error types pub mod errors; @@ -123,17 +121,12 @@ pub mod prelude; /// common utils used in this library and re-exposed pub mod ext; -/// language-specific ffi "glue" -pub mod ffi; - /// end-to-end tests, useful to assert server compliance -#[cfg(any(feature = "test-e2e", test))] +#[cfg(any(test, feature = "test-e2e", feature = "test-coverage"))] pub mod tests; -/// internal network services and interceptors -pub(crate) mod network; - /// re-export codemp_proto as codemp::proto +#[cfg(feature = "proto")] pub use codemp_proto as proto; /// Get the current version of the client diff --git a/src/prelude.rs b/src/prelude.rs index 08a5a71..acec064 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -8,6 +8,7 @@ pub use crate::api::{ BufferUpdate as CodempBufferUpdate, }; +#[cfg(feature = "proto")] pub use crate::proto::{ common::UserInfo as CodempUserInfo, buffer::BufferNode as CodempBufferNode, buffer::BufferAttributes as CodempBufferAttributes, @@ -18,7 +19,8 @@ pub use crate::proto::{ workspace::WorkspaceEvent as CodempWorkspaceEvent, }; +#[cfg(feature = "client")] pub use crate::{ - buffer::Controller as CodempBufferController, client::Client as CodempClient, - cursor::Controller as CodempCursorController, workspace::Workspace as CodempWorkspace, + client::buffer::Controller as CodempBufferController, client::session::Session as CodempSession, + client::cursor::Controller as CodempCursorController, client::workspace::Workspace as CodempWorkspace, }; diff --git a/src/tests/coverage/annotations.rs b/src/tests/coverage/annotations.rs index f1f745f..e99751b 100644 --- a/src/tests/coverage/annotations.rs +++ b/src/tests/coverage/annotations.rs @@ -1,10 +1,10 @@ #[test] -#[cfg(all(test, feature = "lua"))] +#[cfg(test)] fn lua_annotations_should_cover_ffi_api_surface() { let annotations = include_str!("../../../dist/lua/annotations.lua"); let source_maps = [ - ("Client", include_str!("../../ffi/lua/client.rs"), false), + ("Session", include_str!("../../ffi/lua/session.rs"), false), ("Workspace", include_str!("../../ffi/lua/workspace.rs"), false), ("BufferController", include_str!("../../ffi/lua/buffer.rs"), false), ("CursorController", include_str!("../../ffi/lua/cursor.rs"), false), @@ -36,12 +36,12 @@ fn lua_annotations_should_cover_ffi_api_surface() { } #[test] -#[cfg(all(test, feature = "java"))] +#[cfg(test)] fn java_annotations_should_cover_ffi_api_surface() { let mut annotations_map = std::collections::HashMap::new(); for (clazz, content) in [ - ("Client", include_str!("../../../dist/java/src/mp/code/Client.java")), + ("Session", include_str!("../../../dist/java/src/mp/code/Session.java")), ("Workspace", include_str!("../../../dist/java/src/mp/code/Workspace.java")), ("Extensions", include_str!("../../../dist/java/src/mp/code/Extensions.java")), ("BufferController", include_str!("../../../dist/java/src/mp/code/BufferController.java")), @@ -51,7 +51,7 @@ fn java_annotations_should_cover_ffi_api_surface() { } let source = concat!( - include_str!("../../ffi/java/client.rs"), + include_str!("../../ffi/java/session.rs"), include_str!("../../ffi/java/workspace.rs"), include_str!("../../ffi/java/buffer.rs"), include_str!("../../ffi/java/cursor.rs"), diff --git a/src/tests/coverage/ffi.rs b/src/tests/coverage/ffi.rs index fa81375..52c872a 100644 --- a/src/tests/coverage/ffi.rs +++ b/src/tests/coverage/ffi.rs @@ -1,11 +1,14 @@ -#![allow(missing_docs)] // internal test helper +#![allow(missing_docs, clippy::unwrap_used)] // internal test helper use std::collections::{BTreeMap, BTreeSet}; -use std::fs; -fn parse(path: &str) -> syn::File { - syn::parse_file(&fs::read_to_string(path).expect("Could not parse file")).unwrap() -} +const SOURCE_FILES: &[&str] = &[ + include_str!("../../../src/client/session.rs"), + include_str!("../../../src/client/workspace.rs"), + include_str!("../../../src/client/buffer/controller.rs"), + include_str!("../../../src/client/cursor/controller.rs"), + include_str!("../../../src/api/controller.rs"), +]; // 1) Discover core API from target objects fn discover_core_surface(files: &[&str], targets: &[&str]) -> BTreeMap> { @@ -17,7 +20,7 @@ fn discover_core_surface(files: &[&str], targets: &[&str]) -> BTreeMap = BTreeMap::new(); for file in files { - let ast = parse(file); + let ast = syn::parse_file(file).unwrap(); for item in ast.items { match item { @@ -85,7 +88,7 @@ fn discover_core_surface(files: &[&str], targets: &[&str]) -> BTreeMap for ClientFixture { - async fn setup(&mut self) -> Result> { +impl ScopedFixture for ClientFixture { + async fn setup(&mut self) -> Result> { let upper = self.name.to_uppercase(); let username = self.username.clone().unwrap_or_else(|| { std::env::var(format!("CODEMP_TEST_USERNAME_{upper}")).unwrap_or_default() @@ -66,7 +66,7 @@ impl ScopedFixture for ClientFixture { let password = self.password.clone().unwrap_or_else(|| { std::env::var(format!("CODEMP_TEST_PASSWORD_{upper}")).unwrap_or_default() }); - let client = crate::Client::connect(crate::api::Config { + let client = crate::Session::connect(crate::api::Config { username, password, tls: Some(false), @@ -110,15 +110,15 @@ impl WorkspaceFixture { } } -impl ScopedFixture<(crate::Client, crate::Workspace)> for WorkspaceFixture { - async fn setup(&mut self) -> Result<(crate::Client, crate::Workspace), Box> { +impl ScopedFixture<(crate::Session, crate::Workspace)> for WorkspaceFixture { + async fn setup(&mut self) -> Result<(crate::Session, crate::Workspace), Box> { let client = ClientFixture::of(&self.user).setup().await?; client.create_workspace(self.workspace.to_string()).await?; let workspace = client.attach_workspace(&self.user, &self.workspace).await?; Ok((client, workspace)) } - async fn cleanup(&mut self, resource: Option<(crate::Client, crate::Workspace)>) { + async fn cleanup(&mut self, resource: Option<(crate::Session, crate::Workspace)>) { if let Some((client, workspace)) = resource { client.leave_workspace(&client.current_user().name, &workspace.id().workspace); if let Err(e) = client.delete_workspace(self.workspace.clone()).await { @@ -130,9 +130,9 @@ impl ScopedFixture<(crate::Client, crate::Workspace)> for WorkspaceFixture { impl ScopedFixture<( - crate::Client, + crate::Session, crate::Workspace, - crate::Client, + crate::Session, crate::Workspace, )> for WorkspaceFixture { @@ -140,9 +140,9 @@ impl &mut self, ) -> Result< ( - crate::Client, + crate::Session, crate::Workspace, - crate::Client, + crate::Session, crate::Workspace, ), Box, @@ -173,9 +173,9 @@ impl async fn cleanup( &mut self, resource: Option<( - crate::Client, + crate::Session, crate::Workspace, - crate::Client, + crate::Session, crate::Workspace, )>, ) { @@ -226,24 +226,24 @@ impl BufferFixture { impl ScopedFixture<( - crate::Client, + crate::Session, crate::Workspace, - crate::buffer::Controller, - crate::Client, + crate::client::buffer::Controller, + crate::Session, crate::Workspace, - crate::buffer::Controller, + crate::client::buffer::Controller, )> for BufferFixture { async fn setup( &mut self, ) -> Result< ( - crate::Client, + crate::Session, crate::Workspace, - crate::buffer::Controller, - crate::Client, + crate::client::buffer::Controller, + crate::Session, crate::Workspace, - crate::buffer::Controller, + crate::client::buffer::Controller, ), Box, > { @@ -288,12 +288,12 @@ impl async fn cleanup( &mut self, resource: Option<( - crate::Client, + crate::Session, crate::Workspace, - crate::buffer::Controller, - crate::Client, + crate::client::buffer::Controller, + crate::Session, crate::Workspace, - crate::buffer::Controller, + crate::client::buffer::Controller, )>, ) { if let Some((client, ws, _, _, _, _)) = resource { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index dbb540b..51ac90d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -3,5 +3,5 @@ #[cfg(feature = "test-e2e")] pub mod e2e; -#[cfg(feature = "test-coverage")] +#[cfg(all(test, feature = "test-coverage"))] mod coverage;