use std::time::Duration;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::config::headers::OneOrMany;
use crate::config::primitives::percentage::Percentage;
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(untagged)]
pub enum UsageReportingExclude {
Expression { expression: String },
OperationNames(Vec<String>),
}
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum UsageReportingSamplingKeyKind {
#[default]
OperationName,
OperationType,
OperationBody,
}
pub type UsageReportingSamplingKey = OneOrMany<UsageReportingSamplingKeyKind>;
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct AtLeastOnceSamplingConfig {
pub key: UsageReportingSamplingKey,
#[serde(default = "default_max_distinct_keys")]
pub max_distinct_keys: u64,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct UsageReportingSamplingConfig {
#[serde(default = "default_sample_rate")]
#[schemars(with = "String")]
pub rate: Percentage,
#[serde(default)]
pub at_least_once: Option<AtLeastOnceSamplingConfig>,
}
impl Default for UsageReportingSamplingConfig {
fn default() -> Self {
Self {
rate: default_sample_rate(),
at_least_once: None,
}
}
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct UsageReportingConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default = "default_endpoint")]
pub endpoint: String,
#[serde(default)]
pub sampling: UsageReportingSamplingConfig,
#[serde(default)]
pub exclude: Option<UsageReportingExclude>,
#[serde(default = "default_buffer_size")]
pub buffer_size: usize,
#[serde(default = "default_accept_invalid_certs")]
pub accept_invalid_certs: bool,
#[serde(
default = "default_connect_timeout",
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize"
)]
#[schemars(with = "String")]
pub connect_timeout: Duration,
#[serde(
default = "default_request_timeout",
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize"
)]
#[schemars(with = "String")]
pub request_timeout: Duration,
#[serde(
default = "default_flush_interval",
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize"
)]
#[schemars(with = "String")]
pub flush_interval: Duration,
}
#[cfg(test)]
mod tests {
use super::UsageReportingConfig;
use crate::config::usage_reporting::UsageReportingExclude;
#[test]
fn exclude_supports_expression_object() {
let config: UsageReportingConfig = serde_json::from_str(
r#"{
"enabled": true,
"sampling": { "rate": "100%" },
"exclude": { "expression": ".request.operation.name == \"Health\"" }
}"#,
)
.expect("config with expression object should deserialize");
let exclude = config.exclude.expect("exclude should be present");
assert!(matches!(exclude, UsageReportingExclude::Expression { .. }));
if let UsageReportingExclude::Expression { expression } = exclude {
assert_eq!(
expression, ".request.operation.name == \"Health\"",
"expression should match the input"
);
}
}
#[test]
fn exclude_supports_legacy_operation_list() {
let config: UsageReportingConfig = serde_json::from_str(
r#"{
"enabled": true,
"sampling": { "rate": "100%" },
"exclude": ["IntrospectionQuery", "HealthCheck"]
}"#,
)
.expect("config with legacy operation list should deserialize");
let exclude = config.exclude.expect("exclude should be present");
assert!(matches!(exclude, UsageReportingExclude::OperationNames(_)));
if let UsageReportingExclude::OperationNames(names) = exclude {
assert_eq!(
names,
vec!["IntrospectionQuery".to_string(), "HealthCheck".to_string()],
"operation names should match the input"
);
}
}
#[test]
fn at_least_once_no_default() {
let config = serde_json::from_str::<UsageReportingConfig>(
r#"{
"enabled": true,
"sampling": {
"rate": "10%",
"at_least_once": {}
}
}"#,
);
assert!(
config.is_err(),
"config with no key should fail to deserialize"
);
}
}
impl Default for UsageReportingConfig {
fn default() -> Self {
Self {
enabled: default_enabled(),
endpoint: default_endpoint(),
sampling: Default::default(),
exclude: None,
buffer_size: default_buffer_size(),
accept_invalid_certs: default_accept_invalid_certs(),
connect_timeout: default_connect_timeout(),
request_timeout: default_request_timeout(),
flush_interval: default_flush_interval(),
}
}
}
fn default_enabled() -> bool {
false
}
fn default_endpoint() -> String {
"https://app.graphql-hive.com/usage".to_string()
}
fn default_sample_rate() -> Percentage {
Percentage::from_f64(1.0).unwrap()
}
fn default_max_distinct_keys() -> u64 {
100_000
}
fn default_buffer_size() -> usize {
1000
}
fn default_accept_invalid_certs() -> bool {
false
}
fn default_request_timeout() -> Duration {
Duration::from_secs(15)
}
fn default_connect_timeout() -> Duration {
Duration::from_secs(5)
}
fn default_flush_interval() -> Duration {
Duration::from_secs(5)
}