use serde::{Deserialize, Serialize};
use crate::{
graphql::complexity::{ComplexityConfig, QueryMetrics, RequestValidator},
security::errors::{Result, SecurityError},
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QueryValidatorConfig {
pub max_depth: usize,
pub max_complexity: usize,
pub max_size_bytes: usize,
pub max_aliases: usize,
}
impl QueryValidatorConfig {
#[must_use]
pub const fn permissive() -> Self {
Self {
max_depth: 20,
max_complexity: 5000,
max_size_bytes: 1_000_000, max_aliases: 100,
}
}
#[must_use]
pub const fn standard() -> Self {
Self {
max_depth: 10,
max_complexity: 1000,
max_size_bytes: 256_000, max_aliases: 30,
}
}
#[must_use]
pub const fn strict() -> Self {
Self {
max_depth: 5,
max_complexity: 500,
max_size_bytes: 64_000, max_aliases: 10,
}
}
}
#[derive(Debug, Clone)]
pub struct QueryValidator {
config: QueryValidatorConfig,
}
impl QueryValidator {
#[must_use]
pub const fn from_config(config: QueryValidatorConfig) -> Self {
Self { config }
}
#[must_use]
pub const fn permissive() -> Self {
Self::from_config(QueryValidatorConfig::permissive())
}
#[must_use]
pub const fn standard() -> Self {
Self::from_config(QueryValidatorConfig::standard())
}
#[must_use]
pub const fn strict() -> Self {
Self::from_config(QueryValidatorConfig::strict())
}
pub fn validate(&self, query: &str) -> Result<QueryMetrics> {
let size_bytes = query.len();
if size_bytes > self.config.max_size_bytes {
return Err(SecurityError::QueryTooLarge {
size: size_bytes,
max_size: self.config.max_size_bytes,
});
}
let rv = RequestValidator::from_config(&ComplexityConfig {
max_depth: self.config.max_depth,
max_complexity: self.config.max_complexity,
max_aliases: self.config.max_aliases,
});
let metrics =
rv.analyze(query).map_err(|e| SecurityError::MalformedQuery(e.to_string()))?;
if metrics.depth > self.config.max_depth {
return Err(SecurityError::QueryTooDeep {
depth: metrics.depth,
max_depth: self.config.max_depth,
});
}
if metrics.complexity > self.config.max_complexity {
return Err(SecurityError::QueryTooComplex {
complexity: metrics.complexity,
max_complexity: self.config.max_complexity,
});
}
if metrics.alias_count > self.config.max_aliases {
return Err(SecurityError::TooManyAliases {
alias_count: metrics.alias_count,
max_aliases: self.config.max_aliases,
});
}
Ok(metrics)
}
#[must_use]
pub const fn config(&self) -> &QueryValidatorConfig {
&self.config
}
}