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 canonical = match kind {
"stock" => crate::urls::STOCK_WS,
"futopt" => crate::urls::FUTOPT_WS,
_ => unreachable!("endpoint_for only called with \"stock\" / \"futopt\""),
};
match self.base_url.as_deref() {
None => canonical.to_string(),
Some(base) => format!("{}/{}/streaming", base.trim_end_matches('/'), 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/v1.0")
.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/v1.0")
.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/v1.0///")
.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_different_api_version_is_honored() {
let factory = WebSocketFactory::new()
.base_url("wss://api.fugle.tw/marketdata/v2.0")
.auth(AuthRequest::with_api_key("k"));
let cfg = factory.stock().build();
assert_eq!(
cfg.url,
"wss://api.fugle.tw/marketdata/v2.0/stock/streaming",
);
}
#[test]
fn test_legacy_host_root_caller_produces_non_canonical_url() {
let factory = WebSocketFactory::new()
.base_url("wss://api.fugle.tw/marketdata") .auth(AuthRequest::with_api_key("k"));
let cfg = factory.stock().build();
assert_eq!(
cfg.url,
"wss://api.fugle.tw/marketdata/stock/streaming",
"Pinned non-canonical URL — confirms 0.5.x host-root callers \
must be updated to include /v1.0 in their base_url string"
);
}
#[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/v1.0")
.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() {}
}