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

facet-rs / facet / 16482855623

23 Jul 2025 10:08PM UTC coverage: 58.447% (-0.2%) from 58.68%
16482855623

Pull #855

github

web-flow
Merge dca4c2302 into 5e8e214d1
Pull Request #855: wip: Remove 'shape lifetime

400 of 572 new or added lines in 70 files covered. (69.93%)

3 existing lines in 3 files now uncovered.

11939 of 20427 relevant lines covered (58.45%)

120.58 hits per line

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

61.48
/facet-args/src/format.rs
1
use crate::arg::{ArgType, extract_subspan};
2
use crate::deserialize::{
3
    DeserError, DeserErrorKind, Expectation, Format, NextData, NextResult, Outcome, Raw, Scalar,
4
    Span, Spanned,
5
};
6
use crate::fields::*;
7
use crate::parse::parse_scalar;
8
use crate::results::*;
9
use alloc::borrow::Cow;
10
use core::fmt;
11
use facet_core::Facet;
12

13
/// Command-line argument format for Facet deserialization
14
pub struct Cli;
15

16
impl fmt::Display for Cli {
17
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1✔
18
        write!(f, "Cli")
1✔
19
    }
1✔
20
}
21

22
/// Parse command line arguments into a Facet-compatible type
23
pub fn from_slice<'input, 'facet, T: Facet<'facet>>(
41✔
24
    args: &'input [&'input str],
41✔
25
) -> Result<T, DeserError<'input>>
41✔
26
where
41✔
27
    'input: 'facet,
41✔
28
{
29
    crate::deserialize::deserialize(args, Cli)
41✔
30
}
41✔
31

32
/// Parse command line arguments provided by std::env::args() into a Facet-compatible type
NEW
33
pub fn from_std_args<'input, 'facet, T: Facet<'facet>>() -> Result<T, DeserError<'input>>
×
34
where
×
NEW
35
    'input: 'facet,
×
36
{
37
    let args = std::env::args().skip(1).collect::<Vec<String>>();
×
38
    let args_str: Vec<&'static str> = args
×
39
        .into_iter()
×
40
        .map(|s| Box::leak(s.into_boxed_str()) as &str)
×
41
        .collect();
×
42

43
    from_slice(Box::leak(args_str.into_boxed_slice()))
×
44
}
×
45

46
impl Format for Cli {
47
    type Input<'input> = [&'input str];
48
    type SpanType = Raw;
49

50
    fn next<'input, 'facet>(
205✔
51
        &mut self,
205✔
52
        nd: NextData<'input, 'facet, Self::SpanType, Self::Input<'input>>,
205✔
53
        expectation: Expectation,
205✔
54
    ) -> NextResult<
205✔
55
        'input,
205✔
56
        'facet,
205✔
57
        Spanned<Outcome<'input>, Self::SpanType>,
205✔
58
        Spanned<DeserErrorKind, Self::SpanType>,
205✔
59
        Self::SpanType,
205✔
60
        Self::Input<'input>,
205✔
61
    > {
205✔
62
        let arg_idx = nd.start();
205✔
63
        let shape = nd.wip.shape();
205✔
64
        let args = nd.input();
205✔
65
        let subspans = nd.substack().get();
205✔
66
        let has_subspans = !subspans.is_empty();
205✔
67

68
        let stay_put = Span::new(arg_idx, 0);
205✔
69
        let step_forth = Span::new(arg_idx, 1);
205✔
70

71
        let span = match expectation {
205✔
72
            Expectation::Value => stay_put,
42✔
73
            Expectation::ObjectKeyOrObjectClose
74
            | Expectation::ObjectVal
75
            | Expectation::ListItemOrListClose => step_forth,
163✔
76
        };
77

78
        let result = match expectation {
205✔
79
            // Top-level value
80
            Expectation::Value => {
81
                // Check if it's a struct type
82
                wrap_outcome_result(validate_struct_type(shape), Outcome::ObjectStarted, span)
42✔
83
            }
84

85
            // Object key (or finished)
86
            Expectation::ObjectKeyOrObjectClose => {
87
                /* Check if we have more arguments */
88
                if arg_idx < args.len() {
101✔
89
                    let arg = args[arg_idx];
76✔
90

91
                    // Check if we need to resegment an arg with '='
92
                    if arg.starts_with("-") && arg.contains('=') && !has_subspans {
76✔
93
                        // This is an argument with '=' that needs resegmentation
94
                        if let Some(key_value_subspans) = create_key_value_subspans(arg) {
10✔
95
                            return (nd, wrap_resegmented_result(key_value_subspans, stay_put));
10✔
96
                        }
×
97
                    }
66✔
98

99
                    // Regular argument or subspan processing
100
                    let effective_arg = if has_subspans {
66✔
101
                        extract_subspan(&subspans[0], arg)
10✔
102
                    } else {
103
                        arg
56✔
104
                    };
105

106
                    // Parse the argument type
107
                    match ArgType::parse(effective_arg) {
66✔
108
                        ArgType::LongFlag(key) => {
38✔
109
                            // Validate field exists
110
                            wrap_string_result(
38✔
111
                                validate_field(&key, shape, &nd.wip).map(|_| key),
38✔
112
                                if has_subspans { stay_put } else { span },
38✔
113
                            )
114
                        }
115
                        ArgType::ShortFlag(key) => {
19✔
116
                            // Convert short argument to field name via shape
117
                            wrap_field_result(
19✔
118
                                find_field_by_short_flag(key, shape),
19✔
119
                                if has_subspans { stay_put } else { span },
19✔
120
                            )
121
                        }
122
                        ArgType::Positional => {
123
                            // Handle positional argument
124
                            wrap_field_result(find_positional_field(shape, &nd.wip), stay_put)
9✔
125
                        }
126
                        ArgType::None => {
127
                            // Handle empty argument (shouldn't happen normally)
128
                            let err = create_unknown_field_error("empty argument", shape);
×
129
                            Err(Spanned { node: err, span })
×
130
                        }
131
                    }
132
                } else {
133
                    // EOF: inject implicit-false-if-absent bool flags, if there are any
134
                    handle_unset_bool_field_error(find_unset_bool_field(shape, &nd.wip), span)
25✔
135
                }
136
            }
137

138
            // Value for the current key
139
            Expectation::ObjectVal => {
140
                // Determine what to do based on the type and available arguments
141
                if shape.is_type::<bool>() {
62✔
142
                    // Handle boolean values (true if we have an arg, false if EOF)
143
                    let has_arg = arg_idx < args.len();
6✔
144
                    wrap_result(handle_bool_value(has_arg), Outcome::Scalar, stay_put)
6✔
145
                } else {
146
                    // For non-boolean types, check if we have subspans
147
                    let result = if has_subspans && arg_idx < args.len() {
56✔
148
                        let arg = args[arg_idx];
10✔
149
                        let subspan = &subspans[1];
10✔
150
                        let arg_type: ArgType = (subspan, arg).into();
10✔
151

152
                        // If this isn't a flag type (neither ShortFlag nor LongFlag), use it as a value
153
                        match arg_type {
10✔
154
                            ArgType::ShortFlag(_) | ArgType::LongFlag(_) => {
155
                                // It's a flag, not a value - continue to validation
156
                                None
×
157
                            }
158
                            _ => {
159
                                // Extract the actual substring to use
160
                                let part = extract_subspan(subspan, arg);
10✔
161
                                Some(Ok(parse_scalar(part, span)))
10✔
162
                            }
163
                        }
164
                    } else {
165
                        None
46✔
166
                    };
167

168
                    // Use the result from above if available, otherwise fall back to regular validation
169
                    result.unwrap_or_else(|| {
56✔
170
                        // No usable subspans, fall back to regular validation
171
                        match validate_value_available(arg_idx, args) {
46✔
172
                            Ok(arg) => Ok(parse_scalar(arg, span)),
42✔
173
                            Err(err) => Err(Spanned {
4✔
174
                                node: err,
4✔
175
                                span: Span::new(arg_idx.saturating_sub(1), 0),
4✔
176
                            }),
4✔
177
                        }
178
                    })
46✔
179
                }
180
            }
181

182
            // List items
183
            Expectation::ListItemOrListClose => {
184
                // End the list if we're out of arguments, or if it's a new flag
185
                if is_list_ended(arg_idx, args) {
×
186
                    // End the list
187
                    Ok(Spanned {
×
188
                        node: Outcome::ListEnded,
×
189
                        span,
×
190
                    })
×
191
                } else {
192
                    // Process the next item
193
                    Ok(Spanned {
×
194
                        node: Outcome::Scalar(Scalar::String(Cow::Borrowed(args[arg_idx]))),
×
195
                        span: step_forth,
×
196
                    })
×
197
                }
198
            }
199
        };
200

201
        (nd, result)
195✔
202
    }
205✔
203

NEW
204
    fn skip<'input, 'facet>(
×
205
        &mut self,
×
NEW
206
        nd: NextData<'input, 'facet, Self::SpanType, Self::Input<'input>>,
×
207
    ) -> NextResult<
×
208
        'input,
×
209
        'facet,
×
210
        Span<Self::SpanType>,
×
NEW
211
        Spanned<DeserErrorKind, Self::SpanType>,
×
212
        Self::SpanType,
×
213
        Self::Input<'input>,
×
NEW
214
    > {
×
215
        let arg_idx = nd.start();
×
216
        let args = nd.input();
×
217
        let span = Span::new(arg_idx, 1);
×
218

219
        let result = if arg_idx < args.len() {
×
220
            // Simply skip one position
221
            Ok(span)
×
222
        } else {
223
            // No argument to skip
224
            Err(Spanned {
×
225
                node: DeserErrorKind::UnexpectedEof {
×
226
                    wanted: "argument to skip",
×
227
                },
×
228
                span,
×
229
            })
×
230
        };
231

232
        (nd, result)
×
233
    }
×
234
}
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