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

naomijub / serde_json_shape / 15685826407

16 Jun 2025 04:01PM UTC coverage: 60.646% (+3.0%) from 57.679%
15685826407

push

github

web-flow
subset, from_sources, improve readme (#1)

* subset, improve readme

* is_superset of

* partial impl of from_sources

161 of 313 new or added lines in 6 files covered. (51.44%)

4 existing lines in 1 file now uncovered.

319 of 526 relevant lines covered (60.65%)

2.45 hits per line

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

76.67
/src/shape/mod.rs
1
use std::{
2
    collections::{BTreeMap, BTreeSet},
3
    str::FromStr,
4
};
5

6
use crate::{
7
    error::Error,
8
    lexer::Token,
9
    parser::{Cst, Node, NodeRef, Rule},
10
    value::Value,
11
};
12

13
pub(crate) mod merger;
14

15
pub fn parse_cst(cst: &Cst<'_>, source: &str) -> Result<Value, Error> {
4✔
16
    let Node::Rule(Rule::File, _) = cst.get(NodeRef::ROOT) else {
11✔
17
        let range = cst.span(NodeRef::ROOT);
×
18
        let content = &source[range.clone()];
×
19
        return Err(Error::InvalidJson(content.to_string(), range));
×
20
    };
21

22
    has_errors(cst, source, NodeRef::ROOT)?;
6✔
23
    if cst.children(NodeRef::ROOT).count() > 1 {
6✔
24
        return Err(Error::TooManyRootNodes);
×
25
    }
26
    let Some(first_node_ref) = cst.children(NodeRef::ROOT).find(|node_ref| {
20✔
27
        !matches!(
6✔
28
            cst.get(*node_ref),
8✔
29
            Node::Token(Token::Whitespace | Token::Newline, _)
30
        )
31
    }) else {
8✔
32
        let range = cst.span(NodeRef::ROOT);
×
33
        let content = &source[range.clone()];
×
34
        return Err(Error::InvalidJson(content.to_string(), range));
×
35
    };
36

37
    parse_rule(cst, first_node_ref, source)
8✔
38
}
39

40
fn has_errors(cst: &Cst<'_>, source: &str, root: NodeRef) -> Result<(), Error> {
8✔
41
    if cst
6✔
42
        .children(root)
6✔
43
        .any(|node_ref| matches!(cst.get(node_ref), Node::Token(Token::Error, _)))
22✔
44
    {
45
        let error = cst
46
            .children(NodeRef::ROOT)
3✔
47
            .find(|node_ref| matches!(cst.get(*node_ref), Node::Token(Token::Error, _)))
9✔
48
            .unwrap();
49
        let span = cst.span(error);
3✔
50
        let value = &source[span.clone()];
3✔
51
        return Err(Error::InvalidJson(value.to_string(), span));
3✔
52
    }
53
    Ok(())
8✔
54
}
55

56
#[allow(clippy::too_many_lines)]
57
fn parse_rule(cst: &Cst<'_>, node_ref: NodeRef, source: &str) -> Result<Value, Error> {
6✔
58
    match cst.get(node_ref) {
14✔
59
        Node::Rule(Rule::Literal, ..) => {
60
            has_errors(cst, source, node_ref)?;
3✔
61
            parse_token(
62
                cst,
63
                cst.children(node_ref)
6✔
64
                    .next()
3✔
65
                    .ok_or_else(|| Error::InvalidType("Empty".to_string()))?,
3✔
66
            )
67
        }
UNCOV
68
        Node::Rule(Rule::Boolean, ..) => Ok(Value::Bool { optional: false }),
×
69
        Node::Rule(Rule::Array, ..) => {
70
            has_errors(cst, source, node_ref)?;
2✔
71
            let mut content = BTreeSet::new();
2✔
72
            for sub_node in cst.children(node_ref).filter(|node_ref| {
8✔
73
                !matches!(
4✔
74
                    cst.get(*node_ref),
4✔
75
                    Node::Token(
76
                        Token::Whitespace
77
                            | Token::Newline
78
                            | Token::Comma
79
                            | Token::LBrak
80
                            | Token::RBrak,
81
                        _
82
                    )
83
                )
84
            }) {
85
                let shape = parse_rule(cst, sub_node, source)?;
4✔
86
                content.insert(shape);
2✔
87
            }
88

89
            if content.len() == 1 {
2✔
90
                Ok(Value::Array {
×
91
                    r#type: Box::new(content.first().cloned().unwrap()),
×
92
                    optional: false,
93
                })
94
            } else if content.len() > 1
6✔
95
                && content
2✔
96
                    .iter()
2✔
97
                    .all(|value| matches!(value, Value::Object { .. }))
6✔
98
            {
99
                let mut iter = content.iter();
2✔
100
                let Some(Value::Object { content, .. }) = iter.next().cloned() else {
4✔
101
                    return Err(Error::Unknown);
×
102
                };
103
                let content =
2✔
104
                    iter.clone()
105
                        .filter_map(Value::keys)
2✔
106
                        .fold(content, |mut acc, mut keys| {
4✔
107
                            for (key, value) in &mut acc {
4✔
108
                                if !keys.any(|k| k == key) {
8✔
109
                                    value.to_optional_mut();
2✔
110
                                }
111
                            }
112
                            acc
2✔
113
                        });
114
                let object = iter.fold(content, |mut acc, content| {
4✔
115
                    let Value::Object { content, .. } = content else {
2✔
116
                        return acc;
×
117
                    };
118
                    for (key, value) in content {
4✔
119
                        let old_value = acc
2✔
120
                            .entry(key.clone())
4✔
121
                            .or_insert_with(|| value.clone().as_optional());
6✔
122
                        if let Value::OneOf { variants, .. } = old_value {
2✔
123
                            variants.insert(value.clone());
×
124
                        }
125
                    }
126
                    acc
2✔
127
                });
128

129
                Ok(Value::Array {
2✔
130
                    r#type: Box::new(Value::Object {
2✔
131
                        content: object,
132
                        optional: false,
133
                    }),
134
                    optional: false,
135
                })
136
            } else if content.len() > 1 {
8✔
137
                Ok(Value::Array {
2✔
138
                    r#type: Box::new(Value::OneOf {
2✔
139
                        variants: content,
2✔
140
                        optional: false,
141
                    }),
142
                    optional: false,
143
                })
144
            } else {
145
                Ok(Value::Array {
×
146
                    r#type: Box::new(Value::Null),
×
147
                    optional: true,
148
                })
149
            }
150
        }
151
        Node::Rule(Rule::Object, ..) => {
152
            let mut content = BTreeMap::default();
4✔
153
            has_errors(cst, source, node_ref)?;
8✔
154
            for sub_node in cst
4✔
155
                .children(node_ref)
4✔
156
                .filter(|node_ref| matches!(cst.get(*node_ref), Node::Rule(Rule::Member, _)))
12✔
157
            {
158
                parse_member(cst, sub_node, source, &mut content)?;
10✔
159
            }
160

161
            Ok(Value::Object {
4✔
162
                content,
4✔
163
                optional: false,
164
            })
165
        }
166
        _ => {
UNCOV
167
            let range = cst.span(node_ref);
×
UNCOV
168
            let content = &source[range.clone()];
×
UNCOV
169
            Err(Error::InvalidJson(content.to_string(), range))
×
170
        }
171
    }
172
}
173

174
fn parse_token(cst: &Cst<'_>, node_ref: NodeRef) -> Result<Value, Error> {
3✔
175
    match cst.get(node_ref) {
3✔
176
        Node::Rule(Rule::Boolean, _) | Node::Token(Token::False | Token::True, _) => {
177
            Ok(Value::Bool { optional: false })
2✔
178
        }
179
        Node::Rule(..) => Err(Error::Unknown),
×
180
        Node::Token(Token::Null, _) => Ok(Value::Null),
2✔
181
        Node::Token(Token::String, _) => Ok(Value::String { optional: false }),
3✔
182
        Node::Token(Token::Number, _) => Ok(Value::Number { optional: false }),
3✔
183
        Node::Token(token, _) => Err(Error::InvalidType(token.to_string())),
×
184
    }
185
}
186

187
fn parse_member(
5✔
188
    cst: &Cst<'_>,
189
    sub_node: NodeRef,
190
    source: &str,
191
    content: &mut BTreeMap<String, Value>,
192
) -> Result<(), Error> {
193
    let Some(key) = cst
10✔
194
        .children(sub_node)
5✔
195
        .find(|node_ref| matches!(cst.get(*node_ref), Node::Token(Token::String, _)))
15✔
196
    else {
197
        return Err(Error::InvalidObjectKey);
×
198
    };
199

200
    let key_span = cst.span(key);
5✔
201
    let key = String::from_str(&source[key_span.start + 1..key_span.end - 1]).unwrap_or_default();
11✔
202

203
    has_errors(cst, source, sub_node)?;
11✔
204
    let Some(member_value) = cst.children(sub_node).find(|node_ref| {
11✔
205
        matches!(
11✔
206
            cst.get(*node_ref),
11✔
207
            Node::Rule(
208
                Rule::Array | Rule::Boolean | Rule::Literal | Rule::Object,
209
                _
210
            )
211
        )
212
    }) else {
5✔
213
        return Err(Error::InvalidObjectValue);
×
214
    };
215

216
    let value = parse_rule(cst, member_value, source)?;
11✔
217
    match content.get(&key) {
10✔
218
        Some(Value::OneOf { variants, .. }) => {
×
219
            if !variants.contains(&value) {
×
220
                return Err(Error::InvalidObjectValueType(
×
221
                    value,
×
222
                    Value::OneOf {
×
223
                        variants: variants.clone(),
×
224
                        optional: false,
225
                    },
226
                ));
227
            }
228
        }
229
        Some(other) => {
2✔
230
            if value != *other {
4✔
231
                return Err(Error::InvalidObjectValueType(value, other.to_owned()));
2✔
232
            }
233
        }
234
        None => {
235
            content.insert(key, value);
6✔
236
        }
237
    }
238
    Ok(())
5✔
239
}
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