libchat/crates/client/src/builder.rs
2026-06-25 18:07:19 +08:00

125 lines
4.3 KiB
Rust

use components::EphemeralRegistry;
use crossbeam_channel::Receiver;
use libchat::{ChatError, ChatStorage, IdentityProvider, RegistrationService, StorageConfig};
use storage::ChatStore;
use crate::Transport;
use crate::client::ChatClient;
use crate::delegate::DelegateSigner;
use crate::errors::ClientError;
use crate::event::Event;
/// Marker for a builder field that has not been configured. A field left `Unset`
/// 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 ChatClientBuilder<I = Unset, T = Unset, R = Unset, S = Unset> {
ident: I,
transport: T,
registration: R,
storage: S,
}
impl ChatClientBuilder {
/// 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 {
ident: Unset,
transport: Unset,
registration: Unset,
storage: Unset,
}
}
/// A builder pre-filled with the default identity, registration, and storage.
/// Only the transport is left to set; override any default with the matching
/// 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())
}
}
impl<I, T, R, S> ChatClientBuilder<I, T, R, S> {
pub fn ident<NI>(self, ident: NI) -> ChatClientBuilder<NI, T, R, S> {
ChatClientBuilder {
ident,
transport: self.transport,
registration: self.registration,
storage: self.storage,
}
}
pub fn transport<NT>(self, transport: NT) -> ChatClientBuilder<I, NT, R, S> {
ChatClientBuilder {
ident: self.ident,
transport,
registration: self.registration,
storage: self.storage,
}
}
pub fn registration<NR>(self, registration: NR) -> ChatClientBuilder<I, T, NR, S> {
ChatClientBuilder {
ident: self.ident,
transport: self.transport,
registration,
storage: self.storage,
}
}
pub fn storage<NS>(self, storage: NS) -> ChatClientBuilder<I, T, R, NS> {
ChatClientBuilder {
ident: self.ident,
transport: self.transport,
registration: self.registration,
storage,
}
}
pub fn storage_config(self, config: StorageConfig) -> ChatClientBuilder<I, T, R, ChatStorage> {
let storage = ChatStorage::new(config)
.map_err(ChatError::from)
.expect("Storage config file should be valid");
ChatClientBuilder {
ident: self.ident,
transport: self.transport,
registration: self.registration,
storage,
}
}
}
type Built<I, T, R, S> = Result<(ChatClient<I, T, R, S>, Receiver<Event>), ClientError>;
/// `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>
where
I: IdentityProvider + Send + 'static,
T: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
pub fn build(self) -> Built<I, T, R, S> {
ChatClient::new(self.ident, self.transport, self.registration, self.storage)
}
}