use std::collections::HashMap;
use std::time::Duration;
use crate::ports::GraphQlAuth;
#[derive(Debug, Clone)]
pub struct CostThrottleConfig {
pub max_points: f64,
pub restore_per_sec: f64,
pub min_available: f64,
pub max_delay_ms: u64,
pub estimated_cost_per_request: f64,
}
impl Default for CostThrottleConfig {
fn default() -> Self {
Self {
max_points: 10_000.0,
restore_per_sec: 500.0,
min_available: 50.0,
max_delay_ms: 30_000,
estimated_cost_per_request: 100.0,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum RateLimitStrategy {
#[default]
SlidingWindow,
TokenBucket,
}
#[derive(Debug, Clone)]
pub struct RateLimitConfig {
pub max_requests: u32,
pub window: Duration,
pub max_delay_ms: u64,
pub strategy: RateLimitStrategy,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
max_requests: 100,
window: Duration::from_secs(60),
max_delay_ms: 30_000,
strategy: RateLimitStrategy::SlidingWindow,
}
}
}
pub trait GraphQlTargetPlugin: Send + Sync {
fn name(&self) -> &str;
fn endpoint(&self) -> &str;
fn version_headers(&self) -> HashMap<String, String> {
HashMap::new()
}
fn default_auth(&self) -> Option<GraphQlAuth> {
None
}
fn default_page_size(&self) -> usize {
50
}
fn supports_cursor_pagination(&self) -> bool {
true
}
#[allow(clippy::unnecessary_literal_bound)]
fn description(&self) -> &str {
""
}
fn cost_throttle_config(&self) -> Option<CostThrottleConfig> {
None
}
fn rate_limit_config(&self) -> Option<RateLimitConfig> {
None
}
}
#[cfg(test)]
#[allow(clippy::unnecessary_literal_bound, clippy::unwrap_used)]
mod tests {
use super::*;
use crate::ports::GraphQlAuthKind;
struct MinimalPlugin;
impl GraphQlTargetPlugin for MinimalPlugin {
fn name(&self) -> &str {
"minimal"
}
fn endpoint(&self) -> &str {
"https://api.example.com/graphql"
}
}
#[test]
fn default_methods_return_expected_values() {
let plugin = MinimalPlugin;
assert!(plugin.version_headers().is_empty());
assert!(plugin.default_auth().is_none());
assert_eq!(plugin.default_page_size(), 50);
assert!(plugin.supports_cursor_pagination());
assert_eq!(plugin.description(), "");
}
#[test]
fn custom_version_headers_are_returned() {
struct Versioned;
impl GraphQlTargetPlugin for Versioned {
fn name(&self) -> &str {
"versioned"
}
fn endpoint(&self) -> &str {
"https://api.v.com/graphql"
}
fn version_headers(&self) -> HashMap<String, String> {
[("X-API-VERSION".to_string(), "v2".to_string())].into()
}
}
let headers = Versioned.version_headers();
assert_eq!(headers.get("X-API-VERSION").map(String::as_str), Some("v2"));
}
#[test]
fn default_auth_can_be_overridden() {
struct Authed;
impl GraphQlTargetPlugin for Authed {
fn name(&self) -> &str {
"authed"
}
fn endpoint(&self) -> &str {
"https://api.a.com/graphql"
}
fn default_auth(&self) -> Option<GraphQlAuth> {
Some(GraphQlAuth {
kind: GraphQlAuthKind::Bearer,
token: "${env:TOKEN}".to_string(),
header_name: None,
})
}
}
let auth = Authed.default_auth().unwrap();
assert_eq!(auth.kind, GraphQlAuthKind::Bearer);
assert_eq!(auth.token, "${env:TOKEN}");
}
}