1use smallvec::SmallVec;
8use std::sync::LazyLock;
9
10pub(crate) fn parse_env_usize(name: &str, default: usize) -> usize {
12 std::env::var(name)
13 .ok()
14 .and_then(|v| v.parse().ok())
15 .unwrap_or(default)
16}
17
18pub(crate) fn parse_env_usize_opt(name: &str) -> Option<usize> {
20 std::env::var(name).ok().and_then(|v| v.parse().ok())
21}
22
23pub(crate) static DEFAULT_PARALLEL_THRESHOLD: LazyLock<usize> =
24 LazyLock::new(|| parse_env_usize("JSON_TOOLS_PARALLEL_THRESHOLD", 100));
25pub(crate) static DEFAULT_NESTED_PARALLEL_THRESHOLD: LazyLock<usize> =
26 LazyLock::new(|| parse_env_usize("JSON_TOOLS_NESTED_PARALLEL_THRESHOLD", 100));
27pub(crate) static DEFAULT_MAX_ARRAY_INDEX: LazyLock<usize> =
28 LazyLock::new(|| parse_env_usize("JSON_TOOLS_MAX_ARRAY_INDEX", 100_000));
29pub(crate) static DEFAULT_NUM_THREADS: LazyLock<Option<usize>> =
30 LazyLock::new(|| parse_env_usize_opt("JSON_TOOLS_NUM_THREADS"));
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[repr(u8)] pub(crate) enum OperationMode {
36 Flatten,
38 Unflatten,
40 Normal,
42}
43
44#[derive(Debug, Clone, Default)]
46pub struct FilteringConfig {
47 pub remove_empty_strings: bool,
49 pub remove_nulls: bool,
51 pub remove_empty_objects: bool,
53 pub remove_empty_arrays: bool,
55}
56
57impl FilteringConfig {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 #[must_use]
65 pub fn remove_empty_strings(mut self, enabled: bool) -> Self {
66 self.remove_empty_strings = enabled;
67 self
68 }
69
70 #[must_use]
72 pub fn remove_nulls(mut self, enabled: bool) -> Self {
73 self.remove_nulls = enabled;
74 self
75 }
76
77 #[must_use]
79 pub fn remove_empty_objects(mut self, enabled: bool) -> Self {
80 self.remove_empty_objects = enabled;
81 self
82 }
83
84 #[must_use]
86 pub fn remove_empty_arrays(mut self, enabled: bool) -> Self {
87 self.remove_empty_arrays = enabled;
88 self
89 }
90
91 pub fn has_any_filter(&self) -> bool {
93 self.remove_empty_strings
94 || self.remove_nulls
95 || self.remove_empty_objects
96 || self.remove_empty_arrays
97 }
98}
99
100#[derive(Debug, Clone, Default)]
102pub struct CollisionConfig {
103 pub handle_collisions: bool,
105}
106
107impl CollisionConfig {
108 pub fn new() -> Self {
110 Self::default()
111 }
112
113 #[must_use]
115 pub fn handle_collisions(mut self, enabled: bool) -> Self {
116 self.handle_collisions = enabled;
117 self
118 }
119
120 pub fn has_collision_handling(&self) -> bool {
122 self.handle_collisions
123 }
124}
125
126#[derive(Debug, Clone, Default)]
128pub struct ReplacementConfig {
129 pub key_replacements: SmallVec<[(String, String); 2]>,
132 pub value_replacements: SmallVec<[(String, String); 2]>,
135}
136
137impl ReplacementConfig {
138 pub fn new() -> Self {
140 Self {
141 key_replacements: SmallVec::new(),
142 value_replacements: SmallVec::new(),
143 }
144 }
145
146 #[must_use]
148 pub fn add_key_replacement(
149 mut self,
150 find: impl Into<String>,
151 replace: impl Into<String>,
152 ) -> Self {
153 self.key_replacements.push((find.into(), replace.into()));
154 self
155 }
156
157 #[must_use]
159 pub fn add_value_replacement(
160 mut self,
161 find: impl Into<String>,
162 replace: impl Into<String>,
163 ) -> Self {
164 self.value_replacements.push((find.into(), replace.into()));
165 self
166 }
167
168 pub fn has_key_replacements(&self) -> bool {
170 !self.key_replacements.is_empty()
171 }
172
173 pub fn has_value_replacements(&self) -> bool {
175 !self.value_replacements.is_empty()
176 }
177}
178
179#[derive(Debug, Clone)]
181pub struct ProcessingConfig {
182 pub separator: String,
184 pub lowercase_keys: bool,
186 pub filtering: FilteringConfig,
188 pub collision: CollisionConfig,
190 pub replacements: ReplacementConfig,
192 pub auto_convert_types: bool,
194 pub parallel_threshold: usize,
196 pub num_threads: Option<usize>,
198 pub nested_parallel_threshold: usize,
202 pub max_array_index: usize,
205}
206
207impl Default for ProcessingConfig {
208 fn default() -> Self {
209 Self {
210 separator: ".".to_string(),
211 lowercase_keys: false,
212 filtering: FilteringConfig::default(),
213 collision: CollisionConfig::default(),
214 replacements: ReplacementConfig::default(),
215 auto_convert_types: false,
216 parallel_threshold: *DEFAULT_PARALLEL_THRESHOLD,
217 num_threads: None, nested_parallel_threshold: *DEFAULT_NESTED_PARALLEL_THRESHOLD,
219 max_array_index: *DEFAULT_MAX_ARRAY_INDEX,
220 }
221 }
222}
223
224impl ProcessingConfig {
225 pub fn new() -> Self {
227 Self::default()
228 }
229
230 pub(crate) fn effective_thread_count(&self, item_count: usize) -> usize {
233 let base = self.num_threads.unwrap_or_else(|| {
234 std::thread::available_parallelism()
235 .map(|p| p.get())
236 .unwrap_or(4)
237 });
238 base.max(1).min(item_count)
239 }
240
241 #[must_use]
243 pub fn separator(mut self, separator: impl Into<String>) -> Self {
244 self.separator = separator.into();
245 self
246 }
247
248 #[must_use]
250 pub fn lowercase_keys(mut self, enabled: bool) -> Self {
251 self.lowercase_keys = enabled;
252 self
253 }
254
255 #[must_use]
257 pub fn filtering(mut self, filtering: FilteringConfig) -> Self {
258 self.filtering = filtering;
259 self
260 }
261
262 #[must_use]
264 pub fn collision(mut self, collision: CollisionConfig) -> Self {
265 self.collision = collision;
266 self
267 }
268
269 #[must_use]
271 pub fn replacements(mut self, replacements: ReplacementConfig) -> Self {
272 self.replacements = replacements;
273 self
274 }
275}