Skip to main content

velesdb_core/guardrails/
limits.rs

1//! Query limits configuration and violation types (EPIC-048).
2//!
3//! Defines the configurable thresholds for guard-rails and the error
4//! type returned when a query exceeds any of them.
5
6use serde::{Deserialize, Serialize};
7
8/// Default maximum traversal depth for graph queries.
9pub const DEFAULT_MAX_DEPTH: u32 = 10;
10
11/// Default maximum cardinality (intermediate results).
12pub const DEFAULT_MAX_CARDINALITY: usize = 100_000;
13
14/// Default memory limit per query (100 MB).
15pub const DEFAULT_MEMORY_LIMIT_BYTES: usize = 100 * 1024 * 1024;
16
17/// Default rate limit (queries per second).
18pub const DEFAULT_RATE_LIMIT_QPS: u32 = 100_000;
19
20/// Default circuit breaker failure threshold.
21pub const DEFAULT_CIRCUIT_FAILURE_THRESHOLD: u32 = 5;
22
23/// Default circuit breaker recovery time in seconds.
24pub const DEFAULT_CIRCUIT_RECOVERY_SECONDS: u64 = 30;
25
26/// Query limits configuration (EPIC-048).
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(default)]
29pub struct QueryLimits {
30    /// Maximum graph traversal depth (US-002).
31    pub max_depth: u32,
32    /// Maximum intermediate cardinality (US-003).
33    pub max_cardinality: usize,
34    /// Memory limit per query in bytes (US-004).
35    pub memory_limit_bytes: usize,
36    /// Query timeout in milliseconds (US-001).
37    ///
38    /// A value of `0` disables the timeout check entirely (no timeout fired).
39    /// The default is `30_000` (30 seconds). Use `0` only for offline/batch workloads
40    /// where unbounded execution time is acceptable.
41    pub timeout_ms: u64,
42    /// Rate limit: max queries per second per client (US-005).
43    pub rate_limit_qps: u32,
44    /// Circuit breaker: failure threshold before tripping (US-006).
45    pub circuit_failure_threshold: u32,
46    /// Circuit breaker: recovery time in seconds (US-006).
47    pub circuit_recovery_seconds: u64,
48}
49
50impl Default for QueryLimits {
51    fn default() -> Self {
52        Self {
53            max_depth: DEFAULT_MAX_DEPTH,
54            max_cardinality: DEFAULT_MAX_CARDINALITY,
55            memory_limit_bytes: DEFAULT_MEMORY_LIMIT_BYTES,
56            timeout_ms: 30_000,
57            rate_limit_qps: DEFAULT_RATE_LIMIT_QPS,
58            circuit_failure_threshold: DEFAULT_CIRCUIT_FAILURE_THRESHOLD,
59            circuit_recovery_seconds: DEFAULT_CIRCUIT_RECOVERY_SECONDS,
60        }
61    }
62}
63
64impl QueryLimits {
65    /// Creates a new `QueryLimits` with default values.
66    #[must_use]
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Sets the maximum traversal depth.
72    #[must_use]
73    pub fn with_max_depth(mut self, depth: u32) -> Self {
74        self.max_depth = depth;
75        self
76    }
77
78    /// Sets the maximum cardinality.
79    #[must_use]
80    pub fn with_max_cardinality(mut self, cardinality: usize) -> Self {
81        self.max_cardinality = cardinality;
82        self
83    }
84
85    /// Sets the memory limit in bytes.
86    #[must_use]
87    pub fn with_memory_limit(mut self, bytes: usize) -> Self {
88        self.memory_limit_bytes = bytes;
89        self
90    }
91
92    /// Sets the query timeout in milliseconds.
93    #[must_use]
94    pub fn with_timeout_ms(mut self, ms: u64) -> Self {
95        self.timeout_ms = ms;
96        self
97    }
98}
99
100/// Guard-rail violation error (EPIC-048).
101#[derive(Debug, Clone, PartialEq, Eq)]
102#[non_exhaustive]
103pub enum GuardRailViolation {
104    /// Query exceeded maximum traversal depth (US-002).
105    DepthExceeded {
106        /// Maximum allowed depth.
107        max: u32,
108        /// Actual depth reached.
109        actual: u32,
110    },
111    /// Query exceeded maximum cardinality (US-003).
112    CardinalityExceeded {
113        /// Maximum allowed cardinality.
114        max: usize,
115        /// Actual cardinality reached.
116        actual: usize,
117    },
118    /// Query exceeded memory limit (US-004).
119    MemoryExceeded {
120        /// Maximum allowed memory in bytes.
121        max_bytes: usize,
122        /// Actual memory used in bytes.
123        used_bytes: usize,
124    },
125    /// Query timed out (US-001).
126    Timeout {
127        /// Maximum allowed time in milliseconds.
128        max_ms: u64,
129        /// Actual elapsed time in milliseconds.
130        elapsed_ms: u64,
131    },
132    /// Rate limit exceeded (US-005).
133    RateLimitExceeded {
134        /// Configured rate limit (queries per second).
135        limit_qps: u32,
136    },
137    /// Circuit breaker is open (US-006).
138    CircuitOpen {
139        /// Time until recovery in seconds.
140        recovery_in_seconds: u64,
141    },
142}
143
144impl std::fmt::Display for GuardRailViolation {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        match self {
147            Self::DepthExceeded { max, actual } => {
148                write!(f, "Traversal depth exceeded: max={max}, actual={actual}")
149            }
150            Self::CardinalityExceeded { max, actual } => {
151                write!(f, "Cardinality exceeded: max={max}, actual={actual}")
152            }
153            Self::MemoryExceeded {
154                max_bytes,
155                used_bytes,
156            } => {
157                write!(
158                    f,
159                    "Memory limit exceeded: max={}MB, used={}MB",
160                    max_bytes / (1024 * 1024),
161                    used_bytes / (1024 * 1024)
162                )
163            }
164            Self::Timeout { max_ms, elapsed_ms } => {
165                write!(f, "Query timed out: max={max_ms}ms, elapsed={elapsed_ms}ms")
166            }
167            Self::RateLimitExceeded { limit_qps } => {
168                write!(f, "Rate limit exceeded: {limit_qps} queries/second")
169            }
170            Self::CircuitOpen {
171                recovery_in_seconds,
172            } => {
173                write!(
174                    f,
175                    "Circuit breaker open, recovery in {recovery_in_seconds}s"
176                )
177            }
178        }
179    }
180}
181
182impl std::error::Error for GuardRailViolation {}