use polyoxide_core::{
HttpClient, HttpClientBuilder, RateLimiter, RetryConfig, DEFAULT_POOL_SIZE, DEFAULT_TIMEOUT_MS,
};
use crate::{
api::{
comments::Comments, events::Events, health::Health, markets::Markets, search::Search,
series::Series, sports::Sports, tags::Tags, user::User,
},
error::GammaError,
};
const DEFAULT_BASE_URL: &str = "https://gamma-api.polymarket.com";
#[derive(Clone)]
pub struct Gamma {
pub(crate) http_client: HttpClient,
}
impl Gamma {
pub fn new() -> Result<Self, GammaError> {
Self::builder().build()
}
pub fn builder() -> GammaBuilder {
GammaBuilder::new()
}
pub fn markets(&self) -> Markets {
Markets {
http_client: self.http_client.clone(),
}
}
pub fn events(&self) -> Events {
Events {
http_client: self.http_client.clone(),
}
}
pub fn series(&self) -> Series {
Series {
http_client: self.http_client.clone(),
}
}
pub fn tags(&self) -> Tags {
Tags {
http_client: self.http_client.clone(),
}
}
pub fn sports(&self) -> Sports {
Sports {
http_client: self.http_client.clone(),
}
}
pub fn comments(&self) -> Comments {
Comments {
http_client: self.http_client.clone(),
}
}
pub fn search(&self) -> Search {
Search {
http_client: self.http_client.clone(),
}
}
pub fn user(&self) -> User {
User {
http_client: self.http_client.clone(),
}
}
pub fn health(&self) -> Health {
Health {
http_client: self.http_client.clone(),
}
}
}
pub struct GammaBuilder {
base_url: String,
timeout_ms: u64,
pool_size: usize,
retry_config: Option<RetryConfig>,
max_concurrent: Option<usize>,
}
impl GammaBuilder {
fn new() -> Self {
Self {
base_url: DEFAULT_BASE_URL.to_string(),
timeout_ms: DEFAULT_TIMEOUT_MS,
pool_size: DEFAULT_POOL_SIZE,
retry_config: None,
max_concurrent: None,
}
}
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn timeout_ms(mut self, timeout: u64) -> Self {
self.timeout_ms = timeout;
self
}
pub fn pool_size(mut self, size: usize) -> Self {
self.pool_size = size;
self
}
pub fn with_retry_config(mut self, config: RetryConfig) -> Self {
self.retry_config = Some(config);
self
}
pub fn max_concurrent(mut self, max: usize) -> Self {
self.max_concurrent = Some(max);
self
}
pub fn build(self) -> Result<Gamma, GammaError> {
let mut builder = HttpClientBuilder::new(&self.base_url)
.timeout_ms(self.timeout_ms)
.pool_size(self.pool_size)
.with_rate_limiter(RateLimiter::gamma_default())
.with_max_concurrent(self.max_concurrent.unwrap_or(4));
if let Some(config) = self.retry_config {
builder = builder.with_retry_config(config);
}
let http_client = builder.build()?;
Ok(Gamma { http_client })
}
}
impl Default for GammaBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_default() {
let builder = GammaBuilder::default();
assert_eq!(builder.base_url, DEFAULT_BASE_URL);
assert_eq!(builder.timeout_ms, DEFAULT_TIMEOUT_MS);
assert_eq!(builder.pool_size, DEFAULT_POOL_SIZE);
}
#[test]
fn test_builder_custom_url() {
let builder = GammaBuilder::new().base_url("https://custom.api.com");
assert_eq!(builder.base_url, "https://custom.api.com");
}
#[test]
fn test_builder_custom_timeout() {
let builder = GammaBuilder::new().timeout_ms(60_000);
assert_eq!(builder.timeout_ms, 60_000);
}
#[test]
fn test_builder_custom_pool_size() {
let builder = GammaBuilder::new().pool_size(20);
assert_eq!(builder.pool_size, 20);
}
#[test]
fn test_builder_custom_retry_config() {
let config = RetryConfig {
max_retries: 5,
initial_backoff_ms: 1000,
max_backoff_ms: 30_000,
};
let builder = GammaBuilder::new().with_retry_config(config);
let config = builder.retry_config.unwrap();
assert_eq!(config.max_retries, 5);
assert_eq!(config.initial_backoff_ms, 1000);
}
#[test]
fn test_builder_build_success() {
let gamma = Gamma::builder().build();
assert!(gamma.is_ok());
}
#[test]
fn test_builder_invalid_url() {
let result = Gamma::builder().base_url("://bad").build();
assert!(result.is_err());
}
#[test]
fn test_builder_custom_max_concurrent() {
let builder = GammaBuilder::new().max_concurrent(10);
assert_eq!(builder.max_concurrent, Some(10));
}
#[tokio::test]
async fn test_default_concurrency_limit_is_4() {
let gamma = Gamma::new().unwrap();
let mut permits = Vec::new();
for _ in 0..4 {
permits.push(gamma.http_client.acquire_concurrency().await);
}
assert!(permits.iter().all(|p| p.is_some()));
let result = tokio::time::timeout(
std::time::Duration::from_millis(50),
gamma.http_client.acquire_concurrency(),
)
.await;
assert!(
result.is_err(),
"5th permit should block with default limit of 4"
);
}
#[test]
fn test_new_creates_client() {
let gamma = Gamma::new();
assert!(gamma.is_ok());
}
#[test]
fn test_client_namespaces_accessible() {
let gamma = Gamma::new().unwrap();
let _markets = gamma.markets();
let _events = gamma.events();
let _series = gamma.series();
let _tags = gamma.tags();
let _sports = gamma.sports();
let _comments = gamma.comments();
let _search = gamma.search();
let _user = gamma.user();
let _health = gamma.health();
}
}