• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 15184625711

22 May 2025 10:49AM UTC coverage: 57.408% (+0.2%) from 57.185%
15184625711

Pull #664

github

web-flow
Merge 8e9488e82 into 4b41e5c8a
Pull Request #664: feat(deserialize): Outcome to set subspans, Instruction to clear

144 of 180 new or added lines in 6 files covered. (80.0%)

2 existing lines in 2 files now uncovered.

9605 of 16731 relevant lines covered (57.41%)

140.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.44
/facet-args/src/fields.rs
1
use alloc::borrow::Cow;
2
use alloc::string::ToString;
3
use facet_core::{FieldAttribute, Shape, Type, UserType};
4
use facet_deserialize::{
5
    DeserErrorKind, Outcome, Raw, Scalar, Span, Spanned, Subspan, SubspanMeta,
6
};
7
use facet_reflect::Wip;
8

9
pub(crate) fn validate_field<'facet, 'shape>(
37✔
10
    field_name: &str,
37✔
11
    shape: &'shape Shape<'shape>,
37✔
12
    wip: &Wip<'facet, 'shape>,
37✔
13
) -> Result<(), DeserErrorKind<'shape>> {
37✔
14
    if let Type::User(UserType::Struct(_)) = &shape.ty {
37✔
15
        if wip.field_index(field_name).is_none() {
37✔
16
            return Err(DeserErrorKind::UnknownField {
2✔
17
                field_name: field_name.to_string(),
2✔
18
                shape,
2✔
19
            });
2✔
20
        }
35✔
21
    }
×
22
    Ok(())
35✔
23
}
37✔
24

25
// Find a positional field
26
pub(crate) fn find_positional_field<'facet, 'shape>(
9✔
27
    shape: &'shape Shape<'shape>,
9✔
28
    wip: &Wip<'facet, 'shape>,
9✔
29
) -> Result<&'shape str, DeserErrorKind<'shape>> {
9✔
30
    if let Type::User(UserType::Struct(st)) = &shape.ty {
9✔
31
        for (idx, field) in st.fields.iter().enumerate() {
11✔
32
            for attr in field.attributes.iter() {
12✔
33
                if let FieldAttribute::Arbitrary(a) = attr {
12✔
34
                    if a.contains("positional") {
12✔
35
                        // Check if this field is already set
36
                        let is_set = wip.is_field_set(idx).unwrap_or(false);
7✔
37
                        if !is_set {
7✔
38
                            return Ok(field.name);
6✔
39
                        }
1✔
40
                    }
5✔
41
                }
×
42
            }
43
        }
44
    }
×
45
    Err(DeserErrorKind::UnknownField {
3✔
46
        field_name: "positional argument".to_string(),
3✔
47
        shape,
3✔
48
    })
3✔
49
}
9✔
50

51
// Find an unset boolean field for implicit false handling
52
pub(crate) fn find_unset_bool_field<'facet, 'shape>(
24✔
53
    shape: &'shape Shape<'shape>,
24✔
54
    wip: &Wip<'facet, 'shape>,
24✔
55
) -> Option<&'shape str> {
24✔
56
    if let Type::User(UserType::Struct(st)) = &shape.ty {
24✔
57
        for (idx, field) in st.fields.iter().enumerate() {
50✔
58
            if !wip.is_field_set(idx).unwrap_or(false) && field.shape().is_type::<bool>() {
50✔
59
                return Some(field.name);
1✔
60
            }
49✔
61
        }
62
    }
×
63
    None
23✔
64
}
24✔
65

66
pub(crate) fn handle_unset_bool_field_error<'shape>(
24✔
67
    field_name_opt: Option<&'shape str>,
24✔
68
    span: Span<Raw>,
24✔
69
) -> Result<Spanned<Outcome<'shape>, Raw>, Spanned<DeserErrorKind<'shape>, Raw>> {
24✔
70
    Ok(Spanned {
71
        node: match field_name_opt {
24✔
72
            Some(field_name) => Outcome::Scalar(Scalar::String(Cow::Borrowed(field_name))),
1✔
73
            None => Outcome::ObjectEnded,
23✔
74
        },
75
        span,
24✔
76
    })
77
}
24✔
78

79
pub(crate) fn find_field_by_short_flag<'shape>(
19✔
80
    key: &str,
19✔
81
    shape: &'shape Shape<'shape>,
19✔
82
) -> Result<&'shape str, DeserErrorKind<'shape>> {
19✔
83
    match &shape.ty {
19✔
84
        Type::User(UserType::Struct(st)) => st
19✔
85
            .fields
19✔
86
            .iter()
19✔
87
            .find(|field| {
34✔
88
                field.attributes.iter().any(|attr| {
55✔
89
                    matches!(attr, FieldAttribute::Arbitrary(a) if a.contains("short") &&
55✔
90
                            (a.contains(key) || (key.len() == 1 && field.name == key)))
26✔
91
                })
55✔
92
            })
34✔
93
            .map(|field| field.name)
19✔
94
            .ok_or_else(|| DeserErrorKind::UnknownField {
19✔
95
                field_name: key.to_string(),
×
96
                shape,
×
97
            }),
×
98
        _ => Err(DeserErrorKind::UnsupportedType {
×
99
            got: shape,
×
100
            wanted: "struct",
×
101
        }),
×
102
    }
103
}
19✔
104

105
// Create a missing value error
106
pub(crate) fn create_missing_value_error<'shape>(field: &str) -> DeserErrorKind<'shape> {
4✔
107
    DeserErrorKind::MissingValue {
4✔
108
        expected: "argument value",
4✔
109
        field: field.to_string(),
4✔
110
    }
4✔
111
}
4✔
112

113
// Handle boolean value parsing
114
pub(crate) fn handle_bool_value<'shape>(
6✔
115
    args_available: bool,
6✔
116
) -> Result<Scalar<'static>, DeserErrorKind<'shape>> {
6✔
117
    Ok(Scalar::Bool(args_available))
6✔
118
}
6✔
119

120
// Check if a value is available and valid (not a flag)
121
pub(crate) fn validate_value_available<'shape, 'input>(
45✔
122
    arg_idx: usize,
45✔
123
    args: &[&'input str],
45✔
124
) -> Result<&'input str, DeserErrorKind<'shape>> {
45✔
125
    if arg_idx >= args.len() {
45✔
126
        return Err(create_missing_value_error(args[arg_idx.saturating_sub(1)]));
2✔
127
    }
43✔
128

129
    let arg = args[arg_idx];
43✔
130
    if arg.starts_with('-') {
43✔
131
        return Err(create_missing_value_error(args[arg_idx.saturating_sub(1)]));
2✔
132
    }
41✔
133

134
    Ok(arg)
41✔
135
}
45✔
136

137
// Check if a list has reached its end
138
pub(crate) fn is_list_ended(arg_idx: usize, args: &[&str]) -> bool {
×
139
    arg_idx >= args.len() || args[arg_idx].starts_with('-')
×
140
}
×
141

142
// Validate a struct type and return appropriate error if it's not a struct
143
pub(crate) fn validate_struct_type<'shape>(
41✔
144
    shape: &'shape Shape<'shape>,
41✔
145
) -> Result<(), DeserErrorKind<'shape>> {
41✔
146
    if !matches!(shape.ty, Type::User(UserType::Struct(_))) {
41✔
147
        Err(DeserErrorKind::UnsupportedType {
1✔
148
            got: shape,
1✔
149
            wanted: "struct",
1✔
150
        })
1✔
151
    } else {
152
        Ok(())
40✔
153
    }
154
}
41✔
155

156
pub(crate) fn create_unknown_field_error<'shape>(
×
157
    field_name: &str,
×
158
    shape: &'shape Shape<'shape>,
×
159
) -> DeserErrorKind<'shape> {
×
160
    DeserErrorKind::UnknownField {
×
161
        field_name: field_name.to_string(),
×
162
        shape,
×
163
    }
×
164
}
×
165

166
/// Create subspans by splitting at all occurrences of a delimiter
167
pub(crate) fn create_delimited_subspans(
10✔
168
    arg: &str,
10✔
169
    delimiter: char,
10✔
170
    meta: Option<SubspanMeta>,
10✔
171
) -> Vec<Subspan> {
10✔
172
    // Find all positions of the delimiter
173
    let positions: Vec<usize> = arg.match_indices(delimiter).map(|(idx, _)| idx).collect();
10✔
174

175
    // Create ranges between delimiters
176
    let ranges = {
10✔
177
        let mut ranges = Vec::with_capacity(positions.len() + 1);
10✔
178

179
        // First range: from start to first delimiter (or end if no delimiters)
180
        let first_end = positions.first().copied().unwrap_or(arg.len());
10✔
181
        ranges.push(0..first_end);
10✔
182

183
        // Middle ranges: between consecutive delimiters
184
        for window in positions.windows(2) {
10✔
NEW
185
            let start = window[0] + delimiter.len_utf8();
×
NEW
186
            let end = window[1];
×
NEW
187
            ranges.push(start..end);
×
NEW
188
        }
×
189

190
        // Last range: from last delimiter to end (if there were any delimiters)
191
        if let Some(&last_pos) = positions.last() {
10✔
192
            ranges.push((last_pos + delimiter.len_utf8())..arg.len());
10✔
193
        }
10✔
194

195
        ranges
10✔
196
    };
197

198
    // Map ranges to subspans
199
    ranges
10✔
200
        .into_iter()
10✔
201
        .map(|range| Subspan {
10✔
202
            offset: range.start,
20✔
203
            len: range.end - range.start,
20✔
204
            meta: meta.clone(),
20✔
205
        })
20✔
206
        .collect()
10✔
207
}
10✔
208

209
/// Create key-value subspans from an argument with an equals sign
210
pub(crate) fn create_key_value_subspans(arg: &str) -> Option<Vec<Subspan>> {
10✔
211
    if arg.contains('=') {
10✔
212
        Some(create_delimited_subspans(
10✔
213
            arg,
10✔
214
            '=',
10✔
215
            Some(SubspanMeta::KeyValue),
10✔
216
        ))
10✔
217
    } else {
NEW
218
        None
×
219
    }
220
}
10✔
221

222
#[allow(unused)]
223
/// Create comma-separated value subspans
NEW
224
pub(crate) fn create_csv_subspans(arg: &str) -> Vec<Subspan> {
×
NEW
225
    create_delimited_subspans(arg, ',', Some(SubspanMeta::Delimiter(',')))
×
UNCOV
226
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc