use crate::models::AuthRequest;
use crate::websocket::config::{ConnectionConfig, ConnectionConfigBuilder};
#[derive(Debug, Clone, Default)]
pub struct Unset;
#[derive(Debug, Clone)]
pub struct WithAuth(AuthRequest);
#[derive(Clone, Debug)]
pub struct WebSocketFactory<S = Unset> {
state: S,
base_url: Option<String>,
}
impl Default for WebSocketFactory<Unset> {
fn default() -> Self {
Self::new()
}
}
impl WebSocketFactory<Unset> {
#[must_use]
pub fn new() -> Self {
Self {
state: Unset,
base_url: None,
}
}
#[must_use]
pub fn auth(self, auth: AuthRequest) -> WebSocketFactory<WithAuth> {
WebSocketFactory {
state: WithAuth(auth),
base_url: self.base_url,
}
}
}
impl<S> WebSocketFactory<S> {
#[must_use]
pub fn base_url(mut self, base: impl Into<String>) -> Self {
self.base_url = Some(base.into());
self
}
fn endpoint_for(&self, kind: &str) -> String {
let base = self
.base_url
.as_deref()
.unwrap_or(crate::urls::WS_BASE_ROOT);
format!(
"{}/{}/{}/streaming",
base.trim_end_matches('/'),
crate::urls::API_VERSION,
kind,
)
}
}
impl WebSocketFactory<WithAuth> {
pub fn stock(&self) -> ConnectionConfigBuilder {
ConnectionConfig::builder(self.endpoint_for("stock"), self.state.0.clone())
}
pub fn futopt(&self) -> ConnectionConfigBuilder {
ConnectionConfig::builder(self.endpoint_for("futopt"), self.state.0.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::urls::{FUTOPT_WS, STOCK_WS};
#[test]
fn test_default_stock_endpoint() {
let factory = WebSocketFactory::new().auth(AuthRequest::with_api_key("k"));
let cfg = factory.stock().build();
assert_eq!(cfg.url, STOCK_WS);
}
#[test]
fn test_default_futopt_endpoint() {
let factory = WebSocketFactory::new().auth(AuthRequest::with_api_key("k"));
let cfg = factory.futopt().build();
assert_eq!(cfg.url, FUTOPT_WS);
}
#[test]
fn test_custom_base_url_applied_to_stock() {
let factory = WebSocketFactory::new()
.base_url("wss://staging.fugle.tw/marketdata")
.auth(AuthRequest::with_api_key("k"));
let cfg = factory.stock().build();
assert_eq!(
cfg.url,
"wss://staging.fugle.tw/marketdata/v1.0/stock/streaming",
);
}
#[test]
fn test_custom_base_url_applied_to_futopt() {
let factory = WebSocketFactory::new()
.base_url("ws://localhost:8080")
.auth(AuthRequest::with_api_key("k"));
let cfg = factory.futopt().build();
assert_eq!(cfg.url, "ws://localhost:8080/v1.0/futopt/streaming");
}
#[test]
fn test_base_url_strips_trailing_slashes() {
let factory = WebSocketFactory::new()
.base_url("wss://example.com/marketdata///")
.auth(AuthRequest::with_api_key("k"));
let cfg = factory.stock().build();
assert_eq!(
cfg.url,
"wss://example.com/marketdata/v1.0/stock/streaming",
);
}
#[test]
fn test_factory_yields_independent_builders() {
let factory = WebSocketFactory::new().auth(AuthRequest::with_api_key("k"));
let a = factory.stock().message_buffer(2048).build();
let b = factory.stock().message_buffer(4096).build();
assert_eq!(a.message_buffer, 2048);
assert_eq!(b.message_buffer, 4096);
}
#[test]
fn test_chained_setters_compose_with_factory() {
let factory = WebSocketFactory::new()
.base_url("wss://staging.fugle.tw/marketdata")
.auth(AuthRequest::with_api_key("k"));
let cfg = factory
.stock()
.message_buffer(8192)
.event_buffer(256)
.build();
assert_eq!(
cfg.url,
"wss://staging.fugle.tw/marketdata/v1.0/stock/streaming",
);
assert_eq!(cfg.message_buffer, 8192);
assert_eq!(cfg.event_buffer, 256);
}
#[test]
fn test_base_url_before_auth_compiles() {
let _ = WebSocketFactory::new()
.base_url("ws://example.com")
.auth(AuthRequest::with_api_key("k"));
}
#[allow(dead_code, reason = "compile-fail doctest, never executed")]
fn _stock_before_auth_must_not_compile() {}
#[allow(dead_code, reason = "compile-fail doctest, never executed")]
fn _futopt_before_auth_must_not_compile() {}
}