fraiseql_core/runtime/
jsonb_strategy.rs1use serde::Deserialize;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9#[non_exhaustive]
10pub enum JsonbStrategy {
11 #[default]
13 Project,
14 Stream,
16}
17
18impl std::str::FromStr for JsonbStrategy {
19 type Err = String;
20
21 fn from_str(s: &str) -> Result<Self, Self::Err> {
22 match s.to_lowercase().as_str() {
23 "project" => Ok(JsonbStrategy::Project),
24 "stream" => Ok(JsonbStrategy::Stream),
25 other => {
26 Err(format!("Invalid JSONB strategy '{}', must be 'project' or 'stream'", other))
27 },
28 }
29 }
30}
31
32impl<'de> Deserialize<'de> for JsonbStrategy {
33 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
34 where
35 D: serde::Deserializer<'de>,
36 {
37 let s = String::deserialize(deserializer)?;
38 std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
39 }
40}
41
42#[derive(Debug, Clone)]
44pub struct JsonbOptimizationOptions {
45 pub default_strategy: JsonbStrategy,
47
48 pub auto_threshold_percent: u32,
50}
51
52impl Default for JsonbOptimizationOptions {
53 fn default() -> Self {
54 Self {
55 default_strategy: JsonbStrategy::Project,
56 auto_threshold_percent: 80,
57 }
58 }
59}
60
61impl JsonbOptimizationOptions {
62 pub fn choose_strategy(&self, requested_fields: usize, total_fields: usize) -> JsonbStrategy {
64 if total_fields == 0 {
65 return self.default_strategy;
66 }
67
68 #[allow(clippy::cast_precision_loss)]
69 let percent = (requested_fields as f64 / total_fields as f64) * 100.0;
71
72 if percent >= f64::from(self.auto_threshold_percent) {
73 JsonbStrategy::Stream
74 } else {
75 self.default_strategy
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 #![allow(clippy::unwrap_used)] use super::*;
85
86 #[test]
91 fn test_jsonb_strategy_from_str_project() {
92 let strategy: JsonbStrategy = "project".parse().unwrap();
93 assert_eq!(strategy, JsonbStrategy::Project);
94 }
95
96 #[test]
97 fn test_jsonb_strategy_from_str_stream() {
98 let strategy: JsonbStrategy = "stream".parse().unwrap();
99 assert_eq!(strategy, JsonbStrategy::Stream);
100 }
101
102 #[test]
103 fn test_jsonb_strategy_from_str_case_insensitive() {
104 assert_eq!("PROJECT".parse::<JsonbStrategy>().unwrap(), JsonbStrategy::Project);
105 assert_eq!("Stream".parse::<JsonbStrategy>().unwrap(), JsonbStrategy::Stream);
106 assert_eq!("pRoJeCt".parse::<JsonbStrategy>().unwrap(), JsonbStrategy::Project);
107 }
108
109 #[test]
110 fn test_jsonb_strategy_from_str_invalid() {
111 let result = "invalid".parse::<JsonbStrategy>();
112 let err = result.expect_err("expected Err for invalid JSONB strategy string");
113 assert!(err.contains("Invalid JSONB strategy"), "unexpected error message: {err}");
114 }
115
116 #[test]
117 fn test_jsonb_strategy_deserialize() {
118 let json = r#""project""#;
119 let strategy: JsonbStrategy = serde_json::from_str(json).unwrap();
120 assert_eq!(strategy, JsonbStrategy::Project);
121 }
122
123 #[test]
124 fn test_jsonb_strategy_deserialize_stream() {
125 let json = r#""stream""#;
126 let strategy: JsonbStrategy = serde_json::from_str(json).unwrap();
127 assert_eq!(strategy, JsonbStrategy::Stream);
128 }
129
130 #[test]
135 fn test_choose_strategy_below_threshold() {
136 let opts = JsonbOptimizationOptions {
137 default_strategy: JsonbStrategy::Project,
138 auto_threshold_percent: 80,
139 };
140
141 let strategy = opts.choose_strategy(5, 10);
142 assert_eq!(strategy, JsonbStrategy::Project);
143 }
144
145 #[test]
146 fn test_choose_strategy_at_threshold() {
147 let opts = JsonbOptimizationOptions {
148 default_strategy: JsonbStrategy::Project,
149 auto_threshold_percent: 80,
150 };
151
152 let strategy = opts.choose_strategy(8, 10);
153 assert_eq!(strategy, JsonbStrategy::Stream);
154 }
155
156 #[test]
157 fn test_choose_strategy_above_threshold() {
158 let opts = JsonbOptimizationOptions {
159 default_strategy: JsonbStrategy::Project,
160 auto_threshold_percent: 80,
161 };
162
163 let strategy = opts.choose_strategy(9, 10);
164 assert_eq!(strategy, JsonbStrategy::Stream);
165 }
166
167 #[test]
168 fn test_choose_strategy_respects_default() {
169 let opts = JsonbOptimizationOptions {
170 default_strategy: JsonbStrategy::Stream,
171 auto_threshold_percent: 80,
172 };
173
174 let strategy = opts.choose_strategy(2, 10);
175 assert_eq!(strategy, JsonbStrategy::Stream);
176 }
177
178 #[test]
179 fn test_choose_strategy_zero_total() {
180 let opts = JsonbOptimizationOptions::default();
181 let strategy = opts.choose_strategy(0, 0);
182 assert_eq!(strategy, JsonbStrategy::Project);
183 }
184}