Coverage Report

Created: 2026-02-23 10:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-config/src/serde_utils.rs
Line
Count
Source
1
// Copyright 2024 The NativeLink Authors. All rights reserved.
2
//
3
// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    See LICENSE file for details
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use core::marker::PhantomData;
16
use std::borrow::Cow;
17
use std::fmt;
18
19
use byte_unit::Byte;
20
use humantime::parse_duration;
21
use serde::de::Visitor;
22
use serde::{Deserialize, Deserializer, de};
23
24
/// Helper for serde macro so you can use shellexpand variables in the json configuration
25
/// files when the number is a numeric type.
26
///
27
/// # Errors
28
///
29
/// Will return `Err` if deserialization fails.
30
28
pub fn convert_numeric_with_shellexpand<'de, D, T>(deserializer: D) -> Result<T, D::Error>
31
28
where
32
28
    D: Deserializer<'de>,
33
28
    T: TryFrom<i64>,
34
28
    <T as TryFrom<i64>>::Error: fmt::Display,
35
{
36
    struct NumericVisitor<T: TryFrom<i64>>(PhantomData<T>);
37
38
    impl<T> Visitor<'_> for NumericVisitor<T>
39
    where
40
        T: TryFrom<i64>,
41
        <T as TryFrom<i64>>::Error: fmt::Display,
42
    {
43
        type Value = T;
44
45
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
46
0
            formatter.write_str("an integer or a plain number string")
47
0
        }
48
49
27
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
50
27
            T::try_from(v).map_err(de::Error::custom)
51
27
        }
52
53
0
        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
54
0
            let v_i64 = i64::try_from(v).map_err(de::Error::custom)?;
55
0
            T::try_from(v_i64).map_err(de::Error::custom)
56
0
        }
57
58
1
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
59
1
            let expanded = shellexpand::env(v).map_err(de::Error::custom)
?0
;
60
1
            let s = expanded.as_ref().trim();
61
1
            let parsed = s.parse::<i64>().map_err(de::Error::custom)
?0
;
62
1
            T::try_from(parsed).map_err(de::Error::custom)
63
1
        }
64
    }
65
66
28
    deserializer.deserialize_any(NumericVisitor::<T>(PhantomData))
67
28
}
68
69
/// Same as `convert_numeric_with_shellexpand`, but supports `Option<T>`.
70
///
71
/// # Errors
72
///
73
/// Will return `Err` if deserialization fails.
74
24
pub fn convert_optional_numeric_with_shellexpand<'de, D, T>(
75
24
    deserializer: D,
76
24
) -> Result<Option<T>, D::Error>
77
24
where
78
24
    D: Deserializer<'de>,
79
24
    T: TryFrom<i64>,
80
24
    <T as TryFrom<i64>>::Error: fmt::Display,
81
{
82
    struct OptionalNumericVisitor<T: TryFrom<i64>>(PhantomData<T>);
83
84
    impl<'de, T> Visitor<'de> for OptionalNumericVisitor<T>
85
    where
86
        T: TryFrom<i64>,
87
        <T as TryFrom<i64>>::Error: fmt::Display,
88
    {
89
        type Value = Option<T>;
90
91
1
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
92
1
            formatter.write_str("an optional integer or a plain number string")
93
1
        }
94
95
5
        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
96
5
            Ok(None)
97
5
        }
98
99
0
        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
100
0
            Ok(None)
101
0
        }
102
103
19
        fn visit_some<D2: Deserializer<'de>>(
104
19
            self,
105
19
            deserializer: D2,
106
19
        ) -> Result<Self::Value, D2::Error> {
107
19
            deserializer.deserialize_any(self)
108
19
        }
109
110
13
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
111
13
            T::try_from(v).map(Some).map_err(de::Error::custom)
112
13
        }
113
114
0
        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
115
0
            let v_i64 = i64::try_from(v).map_err(de::Error::custom)?;
116
0
            T::try_from(v_i64).map(Some).map_err(de::Error::custom)
117
0
        }
118
119
5
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
120
5
            if v.is_empty() {
  Branch (120:16): [True: 0, False: 0]
  Branch (120:16): [True: 0, False: 0]
  Branch (120:16): [True: 0, False: 5]
  Branch (120:16): [Folded - Ignored]
121
0
                return Err(de::Error::custom("empty string is not a valid number"));
122
5
            }
123
5
            if v.trim().is_empty() {
  Branch (123:16): [True: 0, False: 0]
  Branch (123:16): [True: 0, False: 0]
  Branch (123:16): [True: 0, False: 5]
  Branch (123:16): [Folded - Ignored]
124
0
                return Ok(None);
125
5
            }
126
5
            let 
expanded4
= shellexpand::env(v).map_err(de::Error::custom)
?1
;
127
4
            let s = expanded.as_ref().trim();
128
4
            let 
parsed2
= s.parse::<i64>().map_err(de::Error::custom)
?2
;
129
2
            T::try_from(parsed).map(Some).map_err(de::Error::custom)
130
5
        }
131
    }
132
133
24
    deserializer.deserialize_option(OptionalNumericVisitor::<T>(PhantomData))
134
24
}
135
136
/// Helper for serde macro so you can use shellexpand variables in the json
137
/// configuration files when the input is a string.
138
///
139
/// Handles YAML/JSON values according to the YAML 1.2 specification:
140
/// - Empty string (`""`) remains an empty string
141
/// - `null` becomes `None`
142
/// - Missing field becomes `None`
143
/// - Whitespace is preserved
144
///
145
/// # Errors
146
///
147
/// Will return `Err` if deserialization fails.
148
164
pub fn convert_string_with_shellexpand<'de, D: Deserializer<'de>>(
149
164
    deserializer: D,
150
164
) -> Result<String, D::Error> {
151
164
    let value = String::deserialize(deserializer)
?0
;
152
164
    Ok((*(shellexpand::env(&value).map_err(de::Error::custom)
?0
)).to_string())
153
164
}
154
155
18
pub fn convert_boolean_with_shellexpand<'de, D, T>(deserializer: D) -> Result<T, D::Error>
156
18
where
157
18
    D: Deserializer<'de>,
158
18
    T: TryFrom<bool>,
159
18
    <T as TryFrom<bool>>::Error: fmt::Display,
160
{
161
    struct BooleanExpandVisitor<T: TryFrom<bool>>(PhantomData<T>);
162
163
    impl<T> Visitor<'_> for BooleanExpandVisitor<T>
164
    where
165
        T: TryFrom<bool>,
166
        <T as TryFrom<bool>>::Error: fmt::Display,
167
    {
168
        type Value = T;
169
170
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
171
0
            formatter.write_str("a boolean or a shell-expandable string that is a boolean")
172
0
        }
173
174
12
        fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
175
12
            T::try_from(v).map_err(de::Error::custom)
176
12
        }
177
178
6
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
179
6
            if v.is_empty() {
  Branch (179:16): [True: 0, False: 0]
  Branch (179:16): [True: 0, False: 6]
  Branch (179:16): [Folded - Ignored]
180
0
                return Err(de::Error::custom("empty string is not a valid number"));
181
6
            }
182
6
            let expanded = shellexpand::env(v).map_err(de::Error::custom)
?0
;
183
6
            let s = expanded.as_ref().trim().to_lowercase();
184
6
            let 
parsed5
= s.parse::<bool>().map_err(de::Error::custom)
?1
;
185
5
            T::try_from(parsed).map_err(de::Error::custom)
186
6
        }
187
    }
188
189
18
    deserializer.deserialize_any(BooleanExpandVisitor::<T>(PhantomData))
190
18
}
191
192
/// Same as `convert_string_with_shellexpand`, but supports `Vec<String>`.
193
///
194
/// # Errors
195
///
196
/// Will return `Err` if deserialization fails.
197
20
pub fn convert_vec_string_with_shellexpand<'de, D: Deserializer<'de>>(
198
20
    deserializer: D,
199
20
) -> Result<Vec<String>, D::Error> {
200
20
    let vec = Vec::<String>::deserialize(deserializer)
?0
;
201
20
    vec.into_iter()
202
21
        .
map20
(|s| {
203
21
            shellexpand::env(&s)
204
21
                .map_err(de::Error::custom)
205
21
                .map(Cow::into_owned)
206
21
        })
207
20
        .collect()
208
20
}
209
210
/// Same as `convert_string_with_shellexpand`, but supports `Option<String>`.
211
///
212
/// # Errors
213
///
214
/// Will return `Err` if deserialization fails.
215
21
pub fn convert_optional_string_with_shellexpand<'de, D: Deserializer<'de>>(
216
21
    deserializer: D,
217
21
) -> Result<Option<String>, D::Error> {
218
21
    let value = Option::<String>::deserialize(deserializer)
?0
;
219
18
    match value {
220
18
        Some(
v2
) if v.is_empty(
)2
=>
Ok(Some(String::new()))2
, // Keep empty string as empty string
  Branch (220:20): [True: 0, False: 11]
  Branch (220:20): [True: 0, False: 0]
  Branch (220:20): [True: 2, False: 5]
  Branch (220:20): [Folded - Ignored]
221
16
        Some(v) => Ok(Some(
222
16
            (*(shellexpand::env(&v).map_err(de::Error::custom)
?0
)).to_string(),
223
        )),
224
3
        None => Ok(None), // Handle both null and field not present
225
    }
226
21
}
227
228
/// # Errors
229
///
230
/// Will return `Err` if deserialization fails.
231
56
pub fn convert_data_size_with_shellexpand<'de, D, T>(deserializer: D) -> Result<T, D::Error>
232
56
where
233
56
    D: Deserializer<'de>,
234
56
    T: TryFrom<u128>,
235
56
    <T as TryFrom<u128>>::Error: fmt::Display,
236
{
237
    struct DataSizeVisitor<T: TryFrom<u128>>(PhantomData<T>);
238
239
    impl<T> Visitor<'_> for DataSizeVisitor<T>
240
    where
241
        T: TryFrom<u128>,
242
        <T as TryFrom<u128>>::Error: fmt::Display,
243
    {
244
        type Value = T;
245
246
1
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
247
1
            formatter.write_str("either a number of bytes as an integer, or a string with a data size format (e.g., \"1GB\", \"500MB\", \"1.5TB\")")
248
1
        }
249
250
0
        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
251
0
            T::try_from(u128::from(v)).map_err(de::Error::custom)
252
0
        }
253
254
15
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
255
15
            if v < 0 {
  Branch (255:16): [True: 0, False: 12]
  Branch (255:16): [True: 0, False: 0]
  Branch (255:16): [True: 0, False: 1]
  Branch (255:16): [True: 1, False: 1]
  Branch (255:16): [True: 0, False: 0]
256
1
                return Err(de::Error::custom("Negative data size is not allowed"));
257
14
            }
258
14
            let v_u128 = u128::try_from(v).map_err(de::Error::custom)
?0
;
259
14
            T::try_from(v_u128).map_err(de::Error::custom)
260
15
        }
261
262
0
        fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
263
0
            T::try_from(v).map_err(de::Error::custom)
264
0
        }
265
266
0
        fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
267
0
            if v < 0 {
  Branch (267:16): [Folded - Ignored]
  Branch (267:16): [Folded - Ignored]
268
0
                return Err(de::Error::custom("Negative data size is not allowed"));
269
0
            }
270
0
            let v_u128 = u128::try_from(v).map_err(de::Error::custom)?;
271
0
            T::try_from(v_u128).map_err(de::Error::custom)
272
0
        }
273
274
40
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
275
40
            let expanded = shellexpand::env(v).map_err(de::Error::custom)
?0
;
276
40
            let s = expanded.as_ref().trim();
277
40
            if v.is_empty() {
  Branch (277:16): [True: 0, False: 19]
  Branch (277:16): [True: 0, False: 0]
  Branch (277:16): [True: 0, False: 2]
  Branch (277:16): [True: 1, False: 18]
  Branch (277:16): [True: 0, False: 0]
278
1
                return Err(de::Error::custom("Missing value in a size field"));
279
39
            }
280
39
            let 
byte_size37
= Byte::parse_str(s, true).map_err(de::Error::custom)
?2
;
281
37
            let bytes = byte_size.as_u128();
282
37
            T::try_from(bytes).map_err(de::Error::custom)
283
40
        }
284
    }
285
286
56
    deserializer.deserialize_any(DataSizeVisitor::<T>(PhantomData))
287
56
}
288
289
/// # Errors
290
///
291
/// Will return `Err` if deserialization fails.
292
4
pub fn convert_optional_data_size_with_shellexpand<'de, D, T>(
293
4
    deserializer: D,
294
4
) -> Result<Option<T>, D::Error>
295
4
where
296
4
    D: Deserializer<'de>,
297
4
    T: TryFrom<u128>,
298
4
    <T as TryFrom<u128>>::Error: fmt::Display,
299
{
300
    struct DataSizeVisitor<T: TryFrom<u128>>(PhantomData<T>);
301
302
    impl<'de, T> Visitor<'de> for DataSizeVisitor<T>
303
    where
304
        T: TryFrom<u128>,
305
        <T as TryFrom<u128>>::Error: fmt::Display,
306
    {
307
        type Value = Option<T>;
308
309
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
310
0
            formatter.write_str("an optional number of bytes as an integer, or a string with a data size format (e.g., \"1GB\", \"500MB\", \"1.5TB\")")
311
0
        }
312
313
1
        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
314
1
            Ok(None)
315
1
        }
316
317
0
        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
318
0
            Ok(None)
319
0
        }
320
321
3
        fn visit_some<D2: Deserializer<'de>>(
322
3
            self,
323
3
            deserializer: D2,
324
3
        ) -> Result<Self::Value, D2::Error> {
325
3
            deserializer.deserialize_any(self)
326
3
        }
327
328
0
        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
329
0
            T::try_from(u128::from(v))
330
0
                .map(Some)
331
0
                .map_err(de::Error::custom)
332
0
        }
333
334
1
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
335
1
            if v < 0 {
  Branch (335:16): [True: 0, False: 0]
  Branch (335:16): [True: 0, False: 1]
  Branch (335:16): [Folded - Ignored]
336
0
                return Err(de::Error::custom("Negative data size is not allowed"));
337
1
            }
338
1
            let v_u128 = u128::try_from(v).map_err(de::Error::custom)
?0
;
339
1
            T::try_from(v_u128).map(Some).map_err(de::Error::custom)
340
1
        }
341
342
0
        fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
343
0
            T::try_from(v).map(Some).map_err(de::Error::custom)
344
0
        }
345
346
0
        fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
347
0
            if v < 0 {
  Branch (347:16): [Folded - Ignored]
  Branch (347:16): [Folded - Ignored]
348
0
                return Err(de::Error::custom("Negative data size is not allowed"));
349
0
            }
350
0
            let v_u128 = u128::try_from(v).map_err(de::Error::custom)?;
351
0
            T::try_from(v_u128).map(Some).map_err(de::Error::custom)
352
0
        }
353
354
2
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
355
2
            let expanded = shellexpand::env(v).map_err(de::Error::custom)
?0
;
356
2
            let s = expanded.as_ref().trim();
357
2
            if v.is_empty() {
  Branch (357:16): [True: 0, False: 0]
  Branch (357:16): [True: 0, False: 2]
  Branch (357:16): [Folded - Ignored]
358
0
                return Err(de::Error::custom("Missing value in a size field"));
359
2
            }
360
2
            let byte_size = Byte::parse_str(s, true).map_err(de::Error::custom)
?0
;
361
2
            let bytes = byte_size.as_u128();
362
2
            T::try_from(bytes).map(Some).map_err(de::Error::custom)
363
2
        }
364
    }
365
366
4
    deserializer.deserialize_option(DataSizeVisitor::<T>(PhantomData))
367
4
}
368
369
/// # Errors
370
///
371
/// Will return `Err` if deserialization fails.
372
21
pub fn convert_duration_with_shellexpand<'de, D, T>(deserializer: D) -> Result<T, D::Error>
373
21
where
374
21
    D: Deserializer<'de>,
375
21
    T: TryFrom<u64>,
376
21
    <T as TryFrom<u64>>::Error: fmt::Display,
377
{
378
    struct DurationVisitor<T: TryFrom<u64>>(PhantomData<T>);
379
380
    impl<T> Visitor<'_> for DurationVisitor<T>
381
    where
382
        T: TryFrom<u64>,
383
        <T as TryFrom<u64>>::Error: fmt::Display,
384
    {
385
        type Value = T;
386
387
1
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
388
1
            formatter.write_str("either a number of seconds as an integer, or a string with a duration format (e.g., \"1h2m3s\", \"30m\", \"1d\")")
389
1
        }
390
391
17
        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
392
17
            T::try_from(v).map_err(de::Error::custom)
393
17
        }
394
395
9
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
396
9
            if v < 0 {
  Branch (396:16): [True: 0, False: 0]
  Branch (396:16): [True: 0, False: 1]
  Branch (396:16): [True: 0, False: 2]
  Branch (396:16): [True: 1, False: 5]
  Branch (396:16): [True: 0, False: 0]
397
1
                return Err(de::Error::custom("Negative duration is not allowed"));
398
8
            }
399
8
            let v_u64 = u64::try_from(v).map_err(de::Error::custom)
?0
;
400
8
            self.visit_u64(v_u64)
401
9
        }
402
403
0
        fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
404
0
            let v_u64 = u64::try_from(v).map_err(de::Error::custom)?;
405
0
            self.visit_u64(v_u64)
406
0
        }
407
408
0
        fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
409
0
            if v < 0 {
  Branch (409:16): [Folded - Ignored]
  Branch (409:16): [Folded - Ignored]
410
0
                return Err(de::Error::custom("Negative duration is not allowed"));
411
0
            }
412
0
            let v_u64 = u64::try_from(v).map_err(de::Error::custom)?;
413
0
            self.visit_u64(v_u64)
414
0
        }
415
416
11
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
417
11
            let expanded = shellexpand::env(v).map_err(de::Error::custom)
?0
;
418
11
            let expanded = expanded.as_ref().trim();
419
11
            let 
duration9
= parse_duration(expanded).map_err(de::Error::custom)
?2
;
420
9
            let secs = duration.as_secs();
421
9
            self.visit_u64(secs)
422
11
        }
423
    }
424
425
21
    deserializer.deserialize_any(DurationVisitor::<T>(PhantomData))
426
21
}
427
428
/// # Errors
429
///
430
/// Will return `Err` if deserialization fails.
431
2
pub fn convert_duration_with_shellexpand_and_negative<'de, D, T>(
432
2
    deserializer: D,
433
2
) -> Result<T, D::Error>
434
2
where
435
2
    D: Deserializer<'de>,
436
2
    T: TryFrom<i64>,
437
2
    <T as TryFrom<i64>>::Error: fmt::Display,
438
{
439
    struct DurationVisitor<T: TryFrom<i64>>(PhantomData<T>);
440
441
    impl<T> Visitor<'_> for DurationVisitor<T>
442
    where
443
        T: TryFrom<i64>,
444
        <T as TryFrom<i64>>::Error: fmt::Display,
445
    {
446
        type Value = T;
447
448
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
449
0
            formatter.write_str("either a number of seconds as an integer, or a string with a duration format (e.g., \"1h2m3s\", \"30m\", \"1d\")")
450
0
        }
451
452
0
        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
453
0
            let v_i64 = i64::try_from(v).map_err(de::Error::custom)?;
454
0
            self.visit_i64(v_i64)
455
0
        }
456
457
2
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
458
2
            T::try_from(v).map_err(de::Error::custom)
459
2
        }
460
461
0
        fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
462
0
            let v_i64 = i64::try_from(v).map_err(de::Error::custom)?;
463
0
            self.visit_i64(v_i64)
464
0
        }
465
466
0
        fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
467
0
            let v_i64 = i64::try_from(v).map_err(de::Error::custom)?;
468
0
            self.visit_i64(v_i64)
469
0
        }
470
471
0
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
472
0
            let expanded = shellexpand::env(v).map_err(de::Error::custom)?;
473
0
            let expanded = expanded.as_ref().trim();
474
0
            let duration = parse_duration(expanded).map_err(de::Error::custom)?;
475
0
            let secs = duration.as_secs();
476
0
            self.visit_u64(secs)
477
0
        }
478
    }
479
480
2
    deserializer.deserialize_any(DurationVisitor::<T>(PhantomData))
481
2
}