mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-06-28 12:09:30 +00:00
feat: default value for chat client builder
This commit is contained in:
parent
0d38dd80b7
commit
c6b56ef2e7
@ -116,7 +116,7 @@ fn run<T: Transport>(transport: T, cli: &Cli) -> Result<()> {
|
|||||||
match cli.registry_url.as_deref() {
|
match cli.registry_url.as_deref() {
|
||||||
Some(url) => {
|
Some(url) => {
|
||||||
let registry = HttpRegistry::new(url);
|
let registry = HttpRegistry::new(url);
|
||||||
let (client, events) = ChatClientBuilder::new()
|
let (client, events) = ChatClientBuilder::default()
|
||||||
.transport(transport)
|
.transport(transport)
|
||||||
.storage_config(storage)
|
.storage_config(storage)
|
||||||
.registration(registry)
|
.registration(registry)
|
||||||
@ -126,7 +126,7 @@ fn run<T: Transport>(transport: T, cli: &Cli) -> Result<()> {
|
|||||||
launch_tui(client, events, cli)
|
launch_tui(client, events, cli)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let (client, events) = ChatClientBuilder::new()
|
let (client, events) = ChatClientBuilder::default()
|
||||||
.transport(transport)
|
.transport(transport)
|
||||||
.storage_config(storage)
|
.storage_config(storage)
|
||||||
.build()
|
.build()
|
||||||
@ -192,7 +192,7 @@ fn run_logos_delivery(cli: Cli) -> Result<()> {
|
|||||||
.context("db path contains non-UTF-8 characters")?
|
.context("db path contains non-UTF-8 characters")?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
logos_chat::ChatClientBuilder::new()
|
logos_chat::ChatClientBuilder::default()
|
||||||
.storage_config(logos_chat::StorageConfig::Encrypted {
|
.storage_config(logos_chat::StorageConfig::Encrypted {
|
||||||
path: db_str,
|
path: db_str,
|
||||||
key: "chat-cli".to_string(),
|
key: "chat-cli".to_string(),
|
||||||
@ -202,7 +202,7 @@ fn run_logos_delivery(cli: Cli) -> Result<()> {
|
|||||||
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
||||||
.context("failed to open persistent client")?
|
.context("failed to open persistent client")?
|
||||||
}
|
}
|
||||||
None => logos_chat::ChatClientBuilder::new()
|
None => logos_chat::ChatClientBuilder::default()
|
||||||
.transport(delivery)
|
.transport(delivery)
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
||||||
|
|||||||
@ -6,13 +6,13 @@ fn main() {
|
|||||||
let bus = MessageBus::default();
|
let bus = MessageBus::default();
|
||||||
let reg = EphemeralRegistry::new();
|
let reg = EphemeralRegistry::new();
|
||||||
|
|
||||||
let (mut saro, saro_events) = ChatClientBuilder::new()
|
let (mut saro, saro_events) = ChatClientBuilder::default()
|
||||||
.transport(InProcessDelivery::new(bus.clone()))
|
.transport(InProcessDelivery::new(bus.clone()))
|
||||||
.registration(reg.clone())
|
.registration(reg.clone())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (mut raya, raya_events) = ChatClientBuilder::new()
|
let (mut raya, raya_events) = ChatClientBuilder::default()
|
||||||
.transport(InProcessDelivery::new(bus))
|
.transport(InProcessDelivery::new(bus))
|
||||||
.registration(reg)
|
.registration(reg)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@ -9,8 +9,10 @@ use crate::delegate::DelegateSigner;
|
|||||||
use crate::errors::ClientError;
|
use crate::errors::ClientError;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
|
|
||||||
/// Marker for a builder field that has not been configured; the corresponding
|
/// Marker for a builder field that has not been configured. A field left `Unset`
|
||||||
/// component will be filled in with a sensible default when `build()` is called.
|
/// at `build()` is a compile error: `build()` requires every component to have a
|
||||||
|
/// concrete type. Use [`ChatClientBuilder::default`] to start from filled-in
|
||||||
|
/// defaults instead.
|
||||||
pub struct Unset;
|
pub struct Unset;
|
||||||
|
|
||||||
pub struct ChatClientBuilder<I = Unset, T = Unset, R = Unset, S = Unset> {
|
pub struct ChatClientBuilder<I = Unset, T = Unset, R = Unset, S = Unset> {
|
||||||
@ -20,8 +22,16 @@ pub struct ChatClientBuilder<I = Unset, T = Unset, R = Unset, S = Unset> {
|
|||||||
storage: S,
|
storage: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChatClientBuilder {
|
impl ChatClientBuilder {
|
||||||
fn default() -> Self {
|
/// An empty builder: every component is `Unset` and must be supplied before
|
||||||
|
/// [`build`](ChatClientBuilder::build). For the common case, prefer
|
||||||
|
/// [`default`](ChatClientBuilder::default), which pre-fills the components.
|
||||||
|
//
|
||||||
|
// `Default` is intentionally not implemented: `default()` below is a distinct
|
||||||
|
// constructor that returns a *different*, pre-filled builder type, which the
|
||||||
|
// `Default` trait (`fn() -> Self`) cannot express.
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ident: Unset,
|
ident: Unset,
|
||||||
transport: Unset,
|
transport: Unset,
|
||||||
@ -29,11 +39,17 @@ impl Default for ChatClientBuilder {
|
|||||||
storage: Unset,
|
storage: Unset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatClientBuilder {
|
/// A builder pre-filled with the default identity, registration, and storage.
|
||||||
pub fn new() -> Self {
|
/// Only the transport is left to set; override any default with the matching
|
||||||
Self::default()
|
/// setter. A complete entry point on its own — there is no need to call
|
||||||
|
/// [`new`](ChatClientBuilder::new) first.
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn default() -> ChatClientBuilder<DelegateSigner, Unset, EphemeralRegistry, ChatStorage> {
|
||||||
|
Self::new()
|
||||||
|
.ident(DelegateSigner::random())
|
||||||
|
.registration(EphemeralRegistry::new())
|
||||||
|
.storage(ChatStorage::in_memory())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +106,11 @@ impl<I, T, R, S> ChatClientBuilder<I, T, R, S> {
|
|||||||
|
|
||||||
type Built<I, T, R, S> = Result<(ChatClient<I, T, R, S>, Receiver<Event>), ClientError>;
|
type Built<I, T, R, S> = Result<(ChatClient<I, T, R, S>, Receiver<Event>), ClientError>;
|
||||||
|
|
||||||
// All four explicitly provided.
|
/// `build()` exists only once every component has a concrete type. Any field
|
||||||
|
/// still `Unset` (always at least the transport, which has no default) fails the
|
||||||
|
/// bounds below, so an incomplete builder is a compile error rather than a
|
||||||
|
/// runtime one. Start from [`default`](ChatClientBuilder::default) to fill the
|
||||||
|
/// identity/registration/storage slots, then set the transport.
|
||||||
impl<I, T, R, S> ChatClientBuilder<I, T, R, S>
|
impl<I, T, R, S> ChatClientBuilder<I, T, R, S>
|
||||||
where
|
where
|
||||||
I: IdentityProvider + Send + 'static,
|
I: IdentityProvider + Send + 'static,
|
||||||
@ -102,114 +122,3 @@ where
|
|||||||
ChatClient::new(self.ident, self.transport, self.registration, self.storage)
|
ChatClient::new(self.ident, self.transport, self.registration, self.storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transport only; I, R, S all default.
|
|
||||||
impl<T: Transport + Send + 'static> ChatClientBuilder<Unset, T, Unset, Unset> {
|
|
||||||
pub fn build(self) -> Built<DelegateSigner, T, EphemeralRegistry, ChatStorage> {
|
|
||||||
ChatClient::new(
|
|
||||||
DelegateSigner::random(),
|
|
||||||
self.transport,
|
|
||||||
EphemeralRegistry::new(),
|
|
||||||
ChatStorage::in_memory(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I and T; R and S default.
|
|
||||||
impl<I, T> ChatClientBuilder<I, T, Unset, Unset>
|
|
||||||
where
|
|
||||||
I: IdentityProvider + Send + 'static,
|
|
||||||
T: Transport + Send + 'static,
|
|
||||||
{
|
|
||||||
pub fn build(self) -> Built<I, T, EphemeralRegistry, ChatStorage> {
|
|
||||||
ChatClient::new(
|
|
||||||
self.ident,
|
|
||||||
self.transport,
|
|
||||||
EphemeralRegistry::new(),
|
|
||||||
ChatStorage::in_memory(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// T and R; I and S default.
|
|
||||||
impl<T, R> ChatClientBuilder<Unset, T, R, Unset>
|
|
||||||
where
|
|
||||||
T: Transport + Send + 'static,
|
|
||||||
R: RegistrationService + Send + 'static,
|
|
||||||
{
|
|
||||||
pub fn build(self) -> Built<DelegateSigner, T, R, ChatStorage> {
|
|
||||||
ChatClient::new(
|
|
||||||
DelegateSigner::random(),
|
|
||||||
self.transport,
|
|
||||||
self.registration,
|
|
||||||
ChatStorage::in_memory(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// T and S; I and R default.
|
|
||||||
impl<T, S> ChatClientBuilder<Unset, T, Unset, S>
|
|
||||||
where
|
|
||||||
T: Transport + Send + 'static,
|
|
||||||
S: ChatStore + Send + 'static,
|
|
||||||
{
|
|
||||||
pub fn build(self) -> Built<DelegateSigner, T, EphemeralRegistry, S> {
|
|
||||||
ChatClient::new(
|
|
||||||
DelegateSigner::random(),
|
|
||||||
self.transport,
|
|
||||||
EphemeralRegistry::new(),
|
|
||||||
self.storage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I, T, and R; S defaults.
|
|
||||||
impl<I, T, R> ChatClientBuilder<I, T, R, Unset>
|
|
||||||
where
|
|
||||||
I: IdentityProvider + Send + 'static,
|
|
||||||
T: Transport + Send + 'static,
|
|
||||||
R: RegistrationService + Send + 'static,
|
|
||||||
{
|
|
||||||
pub fn build(self) -> Built<I, T, R, ChatStorage> {
|
|
||||||
ChatClient::new(
|
|
||||||
self.ident,
|
|
||||||
self.transport,
|
|
||||||
self.registration,
|
|
||||||
ChatStorage::in_memory(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// T, R, and S; I defaults.
|
|
||||||
impl<T, R, S> ChatClientBuilder<Unset, T, R, S>
|
|
||||||
where
|
|
||||||
T: Transport + Send + 'static,
|
|
||||||
R: RegistrationService + Send + 'static,
|
|
||||||
S: ChatStore + Send + 'static,
|
|
||||||
{
|
|
||||||
pub fn build(self) -> Built<DelegateSigner, T, R, S> {
|
|
||||||
ChatClient::new(
|
|
||||||
DelegateSigner::random(),
|
|
||||||
self.transport,
|
|
||||||
self.registration,
|
|
||||||
self.storage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I, T, and S; R defaults.
|
|
||||||
impl<I, T, S> ChatClientBuilder<I, T, Unset, S>
|
|
||||||
where
|
|
||||||
I: IdentityProvider + Send + 'static,
|
|
||||||
T: Transport + Send + 'static,
|
|
||||||
S: ChatStore + Send + 'static,
|
|
||||||
{
|
|
||||||
pub fn build(self) -> Built<I, T, EphemeralRegistry, S> {
|
|
||||||
ChatClient::new(
|
|
||||||
self.ident,
|
|
||||||
self.transport,
|
|
||||||
EphemeralRegistry::new(),
|
|
||||||
self.storage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,10 +4,12 @@ mod delegate;
|
|||||||
mod delivery_in_process;
|
mod delivery_in_process;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod event;
|
mod event;
|
||||||
|
mod logos;
|
||||||
|
|
||||||
pub use builder::{ChatClientBuilder, Unset};
|
pub use builder::{ChatClientBuilder, Unset};
|
||||||
pub use client::{ChatClient, Transport};
|
pub use client::{ChatClient, Transport};
|
||||||
pub use delegate::DelegateSigner;
|
pub use delegate::DelegateSigner;
|
||||||
|
pub use logos::LogosChatClient;
|
||||||
pub use delivery_in_process::{InProcessDelivery, MessageBus};
|
pub use delivery_in_process::{InProcessDelivery, MessageBus};
|
||||||
pub use errors::ClientError;
|
pub use errors::ClientError;
|
||||||
pub use event::{Event, MessageSender};
|
pub use event::{Event, MessageSender};
|
||||||
|
|||||||
58
crates/client/src/logos.rs
Normal file
58
crates/client/src/logos.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//! The opinionated Logos client.
|
||||||
|
//!
|
||||||
|
//! [`ChatClientBuilder`] is generic and can only default the zero-config
|
||||||
|
//! components (random identity, ephemeral registry, in-memory storage) — it has
|
||||||
|
//! no way to know a registry endpoint or a database path, so its defaults are
|
||||||
|
//! the test-grade ones. `LogosChatClient` is the layer that *does* commit to a
|
||||||
|
//! stack: a delegate identity, the HTTP keypackage + account registry, and
|
||||||
|
//! encrypted on-disk storage. It exists so independently built clients share the
|
||||||
|
//! same production services instead of each re-deriving them.
|
||||||
|
//!
|
||||||
|
//! Only the transport is left to the caller: it carries native dependencies and
|
||||||
|
//! environment-specific configuration that belong to the binary, not here.
|
||||||
|
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use libchat::{ChatStorage, StorageConfig};
|
||||||
|
|
||||||
|
use crate::ChatClientBuilder;
|
||||||
|
use crate::client::{ChatClient, Transport};
|
||||||
|
use crate::delegate::DelegateSigner;
|
||||||
|
use crate::errors::ClientError;
|
||||||
|
use crate::event::Event;
|
||||||
|
use components::HttpRegistry;
|
||||||
|
|
||||||
|
// The endpoint for account and keypackage registration service.
|
||||||
|
const REGISTRY_ENDPOINT: &str = "http://127.0.0.1:18080";
|
||||||
|
|
||||||
|
/// A [`ChatClient`] wired to the Logos service stack: a [`DelegateSigner`]
|
||||||
|
/// identity, the HTTP keypackage + account registry ([`HttpRegistry`], which is
|
||||||
|
/// both the keypackage store and the account → device directory), and encrypted
|
||||||
|
/// [`ChatStorage`]. Only the transport `T` is supplied by the caller.
|
||||||
|
pub type LogosChatClient<T> = ChatClient<DelegateSigner, T, HttpRegistry, ChatStorage>;
|
||||||
|
|
||||||
|
impl<T> LogosChatClient<T>
|
||||||
|
where
|
||||||
|
T: Transport + Send + 'static,
|
||||||
|
{
|
||||||
|
/// Open a client on the Logos stack over `transport`, persisting to the
|
||||||
|
/// encrypted database at `db_path` unlocked with `db_key`. The identity and
|
||||||
|
/// registry are preconfigured; the registry endpoint is a placeholder for now.
|
||||||
|
///
|
||||||
|
/// `db_path` is a per-client location and `db_key` is a secret, so both are
|
||||||
|
/// caller-supplied — never baked into the library.
|
||||||
|
pub fn open(
|
||||||
|
transport: T,
|
||||||
|
db_path: impl Into<String>,
|
||||||
|
db_key: impl Into<String>,
|
||||||
|
) -> Result<(Self, Receiver<Event>), ClientError> {
|
||||||
|
ChatClientBuilder::new()
|
||||||
|
.ident(DelegateSigner::random())
|
||||||
|
.transport(transport)
|
||||||
|
.registration(HttpRegistry::new(REGISTRY_ENDPOINT))
|
||||||
|
.storage_config(StorageConfig::Encrypted {
|
||||||
|
path: db_path.into(),
|
||||||
|
key: db_key.into(),
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -39,7 +39,7 @@ fn create_test_client(
|
|||||||
logos_chat::ClientError,
|
logos_chat::ClientError,
|
||||||
> {
|
> {
|
||||||
let d = InProcessDelivery::new(message_bus);
|
let d = InProcessDelivery::new(message_bus);
|
||||||
ChatClientBuilder::new()
|
ChatClientBuilder::default()
|
||||||
.transport(d)
|
.transport(d)
|
||||||
.registration(reg)
|
.registration(reg)
|
||||||
.build()
|
.build()
|
||||||
@ -108,7 +108,7 @@ fn direct_v1_standalone_integration() {
|
|||||||
|
|
||||||
// Build saro's client with its associated delegate so its outbound messages
|
// Build saro's client with its associated delegate so its outbound messages
|
||||||
// carry a credential the receiver can verify against the published bundle.
|
// carry a credential the receiver can verify against the published bundle.
|
||||||
let (mut saro, _saro_events) = ChatClientBuilder::new()
|
let (mut saro, _saro_events) = ChatClientBuilder::default()
|
||||||
.ident(saro_delegate)
|
.ident(saro_delegate)
|
||||||
.transport(InProcessDelivery::new(bus.clone()))
|
.transport(InProcessDelivery::new(bus.clone()))
|
||||||
.registration(reg_service.clone())
|
.registration(reg_service.clone())
|
||||||
@ -293,7 +293,7 @@ fn malformed_inbound_surfaces_as_error_event() {
|
|||||||
let delivery = FailingDelivery::new();
|
let delivery = FailingDelivery::new();
|
||||||
let inbound_tx = delivery.inbound_sender();
|
let inbound_tx = delivery.inbound_sender();
|
||||||
|
|
||||||
let (_client, events) = ChatClientBuilder::new()
|
let (_client, events) = ChatClientBuilder::default()
|
||||||
.transport(delivery)
|
.transport(delivery)
|
||||||
.build()
|
.build()
|
||||||
.expect("client create");
|
.expect("client create");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user