Coverage Report

Created: 2024-10-22 12:33

/build/source/nativelink-config/src/serde_utils.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2024 The NativeLink Authors. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (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
//    http://www.apache.org/licenses/LICENSE-2.0
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 std::borrow::Cow;
16
use std::fmt;
17
use std::marker::PhantomData;
18
use std::str::FromStr;
19
20
use byte_unit::Byte;
21
use humantime::parse_duration;
22
use serde::{de, Deserialize, Deserializer};
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
0
pub fn convert_numeric_with_shellexpand<'de, D, T, E>(deserializer: D) -> Result<T, D::Error>
27
0
where
28
0
    D: Deserializer<'de>,
29
0
    E: fmt::Display,
30
0
    T: TryFrom<i64> + FromStr<Err = E>,
31
0
    <T as TryFrom<i64>>::Error: fmt::Display,
32
0
{
33
    // define a visitor that deserializes
34
    // `ActualData` encoded as json within a string
35
    struct USizeVisitor<T: TryFrom<i64>>(PhantomData<T>);
36
37
    impl<'de, T, FromStrErr> de::Visitor<'de> for USizeVisitor<T>
38
    where
39
        FromStrErr: fmt::Display,
40
        T: TryFrom<i64> + FromStr<Err = FromStrErr>,
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("a string containing json data")
47
0
        }
48
49
0
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
50
0
            v.try_into().map_err(de::Error::custom)
51
0
        }
52
53
0
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
54
0
            (*shellexpand::env(v).map_err(de::Error::custom)?)
55
0
                .parse::<T>()
56
0
                .map_err(de::Error::custom)
57
0
        }
58
    }
59
60
0
    deserializer.deserialize_any(USizeVisitor::<T>(PhantomData::<T> {}))
61
0
}
62
63
/// Same as convert_numeric_with_shellexpand, but supports `Option<T>`.
64
0
pub fn convert_optional_numeric_with_shellexpand<'de, D, T, E>(
65
0
    deserializer: D,
66
0
) -> Result<Option<T>, D::Error>
67
0
where
68
0
    D: Deserializer<'de>,
69
0
    E: fmt::Display,
70
0
    T: TryFrom<i64> + FromStr<Err = E>,
71
0
    <T as TryFrom<i64>>::Error: fmt::Display,
72
0
{
73
    // define a visitor that deserializes
74
    // `ActualData` encoded as json within a string
75
    struct USizeVisitor<T: TryFrom<i64>>(PhantomData<T>);
76
77
    impl<'de, T, FromStrErr> de::Visitor<'de> for USizeVisitor<T>
78
    where
79
        FromStrErr: fmt::Display,
80
        T: TryFrom<i64> + FromStr<Err = FromStrErr>,
81
        <T as TryFrom<i64>>::Error: fmt::Display,
82
    {
83
        type Value = Option<T>;
84
85
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
86
0
            formatter.write_str("a string containing json data")
87
0
        }
88
89
0
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
90
0
            Ok(Some(v.try_into().map_err(de::Error::custom)?))
91
0
        }
92
93
0
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
94
0
            if v.is_empty() {
  Branch (94:16): [Folded - Ignored]
  Branch (94:16): [Folded - Ignored]
95
0
                return Ok(None);
96
0
            }
97
0
            Ok(Some(
98
0
                (*shellexpand::env(v).map_err(de::Error::custom)?)
99
0
                    .parse::<T>()
100
0
                    .map_err(de::Error::custom)?,
101
            ))
102
0
        }
103
    }
104
105
0
    deserializer.deserialize_any(USizeVisitor::<T>(PhantomData::<T> {}))
106
0
}
107
108
/// Helper for serde macro so you can use shellexpand variables in the json configuration
109
/// files when the number is a numeric type.
110
0
pub fn convert_string_with_shellexpand<'de, D: Deserializer<'de>>(
111
0
    deserializer: D,
112
0
) -> Result<String, D::Error> {
113
0
    let value = String::deserialize(deserializer)?;
114
0
    Ok((*(shellexpand::env(&value).map_err(de::Error::custom)?)).to_string())
115
0
}
116
117
/// Same as convert_string_with_shellexpand, but supports `Vec<String>`.
118
0
pub fn convert_vec_string_with_shellexpand<'de, D: Deserializer<'de>>(
119
0
    deserializer: D,
120
0
) -> Result<Vec<String>, D::Error> {
121
0
    let vec = Vec::<String>::deserialize(deserializer)?;
122
0
    vec.into_iter()
123
0
        .map(|s| {
124
0
            shellexpand::env(&s)
125
0
                .map_err(de::Error::custom)
126
0
                .map(Cow::into_owned)
127
0
        })
128
0
        .collect()
129
0
}
130
131
/// Same as convert_string_with_shellexpand, but supports `Option<String>`.
132
0
pub fn convert_optional_string_with_shellexpand<'de, D: Deserializer<'de>>(
133
0
    deserializer: D,
134
0
) -> Result<Option<String>, D::Error> {
135
0
    let value = Option::<String>::deserialize(deserializer)?;
136
0
    if let Some(value) = value {
  Branch (136:12): [Folded - Ignored]
  Branch (136:12): [Folded - Ignored]
137
        Ok(Some(
138
0
            (*(shellexpand::env(&value).map_err(de::Error::custom)?)).to_string(),
139
        ))
140
    } else {
141
0
        Ok(None)
142
    }
143
0
}
144
145
2
pub fn convert_data_size_with_shellexpand<'de, D, T, E>(deserializer: D) -> Result<T, D::Error>
146
2
where
147
2
    D: Deserializer<'de>,
148
2
    E: fmt::Display,
149
2
    T: TryFrom<i64> + FromStr<Err = E>,
150
2
    <T as TryFrom<i64>>::Error: fmt::Display,
151
2
{
152
    // define a visitor that deserializes
153
    // `ActualData` encoded as json within a string
154
    struct USizeVisitor<T: TryFrom<i64>>(PhantomData<T>);
155
156
    impl<'de, T, FromStrErr> de::Visitor<'de> for USizeVisitor<T>
157
    where
158
        FromStrErr: fmt::Display,
159
        T: TryFrom<i64> + FromStr<Err = FromStrErr>,
160
        <T as TryFrom<i64>>::Error: fmt::Display,
161
    {
162
        type Value = T;
163
164
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
165
0
            formatter.write_str("a string containing json data")
166
0
        }
167
168
1
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
169
1
            v.try_into().map_err(de::Error::custom)
170
1
        }
171
172
1
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
173
1
            let expanded = (*shellexpand::env(v).map_err(de::Error::custom)
?0
).to_string();
174
1
            let byte_size = Byte::parse_str(expanded, true).map_err(de::Error::custom)
?0
;
175
1
            let byte_size_u128 = byte_size.as_u128();
176
1
            T::try_from(byte_size_u128.try_into().map_err(de::Error::custom)
?0
)
177
1
                .map_err(de::Error::custom)
178
1
        }
179
    }
180
181
2
    deserializer.deserialize_any(USizeVisitor::<T>(PhantomData::<T> {}))
182
2
}
183
184
2
pub fn convert_duration_with_shellexpand<'de, D, T, E>(deserializer: D) -> Result<T, D::Error>
185
2
where
186
2
    D: Deserializer<'de>,
187
2
    E: fmt::Display,
188
2
    T: TryFrom<i64> + FromStr<Err = E>,
189
2
    <T as TryFrom<i64>>::Error: fmt::Display,
190
2
{
191
    // define a visitor that deserializes
192
    // `ActualData` encoded as json within a string
193
    struct USizeVisitor<T: TryFrom<i64>>(PhantomData<T>);
194
195
    impl<'de, T, FromStrErr> de::Visitor<'de> for USizeVisitor<T>
196
    where
197
        FromStrErr: fmt::Display,
198
        T: TryFrom<i64> + FromStr<Err = FromStrErr>,
199
        <T as TryFrom<i64>>::Error: fmt::Display,
200
    {
201
        type Value = T;
202
203
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
204
0
            formatter.write_str("a string containing json data")
205
0
        }
206
207
1
        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
208
1
            v.try_into().map_err(de::Error::custom)
209
1
        }
210
211
1
        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
212
1
            let expanded = (*shellexpand::env(v).map_err(de::Error::custom)
?0
).to_string();
213
1
            let duration = parse_duration(&expanded).map_err(de::Error::custom)
?0
;
214
1
            let duration_secs = duration.as_secs();
215
1
            T::try_from(duration_secs.try_into().map_err(de::Error::custom)
?0
)
216
1
                .map_err(de::Error::custom)
217
1
        }
218
    }
219
220
2
    deserializer.deserialize_any(USizeVisitor::<T>(PhantomData::<T> {}))
221
2
}