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

naomijub / serde_json_shape / 27514975501

14 Jun 2026 11:06PM UTC coverage: 62.305% (+0.6%) from 61.739%
27514975501

push

github

web-flow
Rust keywords are invalid in the json schema (#41)

21 of 24 new or added lines in 2 files covered. (87.5%)

1081 of 1735 relevant lines covered (62.31%)

3.0 hits per line

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

77.94
/json_shape/src/shape/mod.rs
1
use std::collections::BTreeMap;
2

3
use crate::{
4
    error::Error,
5
    lexer::Token,
6
    parser::{Cst, Node, NodeRef, Rule},
7
    value::Value,
8
};
9

10
pub(crate) mod merger;
11

12
pub fn parse_cst(cst: &Cst<'_>, source: &str) -> Result<Value, Error> {
17✔
13
    let Node::Rule(Rule::File, _) = cst.get(NodeRef::ROOT) else {
34✔
14
        let span = cst.span(NodeRef::ROOT);
×
15
        let value = source[span.clone()].to_string();
×
16
        return Err(Error::InvalidJson { value, span });
×
17
    };
18

19
    has_errors(cst, source, NodeRef::ROOT)?;
17✔
20
    if cst
21✔
21
        .children(NodeRef::ROOT)
18✔
22
        .filter(|node_ref| {
47✔
23
            !matches!(
30✔
24
                cst.get(*node_ref),
24✔
25
                Node::Token(Token::Whitespace | Token::Newline, _)
26
            )
27
        })
28
        .count()
21✔
29
        > 1
30
    {
31
        if let Some(err) = cst
×
32
            .children(NodeRef::ROOT)
×
33
            .find(|node_ref| has_errors(cst, source, *node_ref).is_err())
×
34
        {
35
            let span = cst.span(err);
×
36
            let value = source[span.clone()].to_string();
×
37
            return Err(Error::InvalidJson { value, span });
×
38
        }
39
        return Err(Error::TooManyRootNodes(cst.children(NodeRef::ROOT).count()));
×
40
    }
41
    let Some(first_node_ref) = cst.children(NodeRef::ROOT).find(|node_ref| {
64✔
42
        !matches!(
20✔
43
            cst.get(*node_ref),
23✔
44
            Node::Token(Token::Whitespace | Token::Newline, _)
45
        )
46
    }) else {
22✔
47
        let span = cst.span(NodeRef::ROOT);
1✔
48
        let value = source[span.clone()].to_string();
1✔
49
        return Err(Error::InvalidJson { value, span });
1✔
50
    };
51

52
    parse_rule(cst, first_node_ref, source)
22✔
53
}
54

55
fn has_errors(cst: &Cst<'_>, source: &str, root: NodeRef) -> Result<(), Error> {
19✔
56
    if cst.children(root).any(|node_ref| {
44✔
57
        matches!(
62✔
58
            cst.get(node_ref),
57✔
59
            Node::Token(Token::Error, _) | Node::Rule(Rule::Error, _)
60
        )
61
    }) && let Some(error) = cst.children(root).find(|node_ref| {
33✔
62
        matches!(
19✔
63
            cst.get(*node_ref),
19✔
64
            Node::Token(Token::Error, _) | Node::Rule(Rule::Error, _)
65
        )
66
    }) {
67
        let span = cst.span(error);
7✔
68
        let value = source[span.clone()].to_string();
7✔
69
        return Err(Error::InvalidJson { value, span });
7✔
70
    }
71
    Ok(())
20✔
72
}
73

74
#[allow(clippy::too_many_lines)]
75
fn parse_rule(cst: &Cst<'_>, node_ref: NodeRef, source: &str) -> Result<Value, Error> {
19✔
76
    match cst.get(node_ref) {
41✔
77
        Node::Rule(Rule::Literal, ..) => {
78
            has_errors(cst, source, node_ref)?;
11✔
79
            parse_token(
80
                cst,
81
                cst.children(node_ref)
22✔
82
                    .next()
11✔
83
                    .ok_or_else(|| Error::InvalidType("Empty".to_string()))?,
11✔
84
            )
85
        }
86
        Node::Rule(Rule::Boolean, ..) => Ok(Value::Bool { optional: false }),
×
87
        Node::Rule(Rule::Array, ..) => {
88
            has_errors(cst, source, node_ref)?;
12✔
89
            let mut elements = Vec::new();
11✔
90
            for sub_node in cst.children(node_ref).filter(|node_ref| {
43✔
91
                !matches!(
23✔
92
                    cst.get(*node_ref),
25✔
93
                    Node::Token(
94
                        Token::Whitespace
95
                            | Token::Newline
96
                            | Token::Comma
97
                            | Token::LBrak
98
                            | Token::RBrak,
99
                        _
100
                    )
101
                )
102
            }) {
103
                let shape = parse_rule(cst, sub_node, source)?;
21✔
104
                elements.push(shape);
9✔
105
            }
106

107
            if elements.len() == 1 || elements.windows(2).all(|w| w[0] == w[1]) {
45✔
108
                Ok(Value::Array {
6✔
109
                    r#type: Box::new(elements.first().cloned().unwrap()),
12✔
110
                    optional: false,
111
                })
112
            } else if elements.len() > 1
19✔
113
                && elements
31✔
114
                    .iter()
12✔
115
                    .all(|value| matches!(value, Value::Object { .. }))
33✔
116
            {
117
                let mut iter = elements.iter();
14✔
118
                let Some(Value::Object { content, .. }) = iter.next().cloned() else {
14✔
119
                    return Err(Error::Unknown);
×
120
                };
121
                let content =
7✔
122
                    iter.clone()
123
                        .filter_map(Value::keys)
7✔
124
                        .fold(content, |mut acc, mut keys| {
14✔
125
                            for (key, value) in &mut acc {
14✔
126
                                if !keys.any(|k| k == key) {
28✔
127
                                    value.to_optional_mut();
5✔
128
                                }
129
                            }
130
                            acc
6✔
131
                        });
132
                let object = iter.fold(content, |mut acc, content| {
12✔
133
                    let Value::Object { content, .. } = content else {
7✔
134
                        return acc;
×
135
                    };
136
                    for (key, value) in content {
14✔
137
                        let old_value = acc
6✔
138
                            .entry(key.clone())
14✔
139
                            .or_insert_with(|| value.clone().as_optional());
17✔
140
                        if let Value::OneOf { variants, .. } = old_value {
6✔
141
                            variants.insert(value.clone());
×
142
                        }
143
                    }
144
                    acc
7✔
145
                });
146

147
                Ok(Value::Array {
7✔
148
                    r#type: Box::new(Value::Object {
7✔
149
                        content: object,
150
                        optional: false,
151
                    }),
152
                    optional: false,
153
                })
154
            } else if elements.len() > 1 {
31✔
155
                Ok(Value::Tuple {
8✔
156
                    elements,
8✔
157
                    optional: false,
158
                })
159
            } else {
160
                Ok(Value::Array {
×
161
                    r#type: Box::new(Value::Null),
×
162
                    optional: true,
163
                })
164
            }
165
        }
166
        Node::Rule(Rule::Object, ..) => {
167
            let mut content = BTreeMap::default();
18✔
168
            has_errors(cst, source, node_ref)?;
34✔
169
            for sub_node in cst
14✔
170
                .children(node_ref)
15✔
171
                .filter(|node_ref| matches!(cst.get(*node_ref), Node::Rule(Rule::Member, _)))
47✔
172
            {
173
                parse_member(cst, sub_node, source, &mut content)?;
30✔
174
            }
175

176
            Ok(Value::Object {
12✔
177
                content,
12✔
178
                optional: false,
179
            })
180
        }
181
        _ => {
182
            let span = cst.span(node_ref);
×
183
            let value = source[span.clone()].to_string();
×
184
            Err(Error::InvalidJson { value, span })
×
185
        }
186
    }
187
}
188

189
fn parse_token(cst: &Cst<'_>, node_ref: NodeRef) -> Result<Value, Error> {
11✔
190
    match cst.get(node_ref) {
11✔
191
        Node::Rule(Rule::Boolean, _) | Node::Token(Token::False | Token::True, _) => {
192
            Ok(Value::Bool { optional: false })
6✔
193
        }
194
        Node::Rule(..) => Err(Error::Unknown),
×
195
        Node::Token(Token::Null, _) => Ok(Value::Null),
9✔
196
        Node::Token(Token::String, _) => Ok(Value::String { optional: false }),
11✔
197
        Node::Token(Token::Number, _) => Ok(Value::Number { optional: false }),
11✔
198
        Node::Token(token, _) => Err(Error::InvalidType(token.to_string())),
×
199
    }
200
}
201

202
fn parse_json_object_key(source: &str, span: std::ops::Range<usize>) -> Result<String, Error> {
12✔
203
    let raw = &source[span];
14✔
204
    let value: serde_json::Value =
14✔
205
        serde_json::from_str(raw).map_err(|_| Error::InvalidObjectKey)?;
206
    match value {
15✔
207
        serde_json::Value::String(key) => Ok(key),
14✔
NEW
208
        _ => Err(Error::InvalidObjectKey),
×
209
    }
210
}
211

212
fn parse_member(
13✔
213
    cst: &Cst<'_>,
214
    sub_node: NodeRef,
215
    source: &str,
216
    content: &mut BTreeMap<String, Value>,
217
) -> Result<(), Error> {
218
    let Some(key) = cst
29✔
219
        .children(sub_node)
12✔
220
        .find(|node_ref| matches!(cst.get(*node_ref), Node::Token(Token::String, _)))
43✔
221
    else {
222
        return Err(Error::InvalidObjectKey);
×
223
    };
224

225
    let key = parse_json_object_key(source, cst.span(key))?;
14✔
226

227
    has_errors(cst, source, sub_node)?;
31✔
228
    let Some(member_value) = cst.children(sub_node).find(|node_ref| {
31✔
229
        matches!(
30✔
230
            cst.get(*node_ref),
30✔
231
            Node::Rule(
232
                Rule::Array | Rule::Boolean | Rule::Literal | Rule::Object,
233
                _
234
            )
235
        )
236
    }) else {
17✔
237
        return Err(Error::InvalidObjectValue);
×
238
    };
239

240
    let value = parse_rule(cst, member_value, source)?;
27✔
241
    match content.get(&key) {
27✔
242
        Some(Value::OneOf { variants, .. }) => {
×
243
            if !variants.contains(&value) {
×
244
                return Err(Error::InvalidObjectValueType(
×
245
                    value,
×
246
                    Value::OneOf {
×
247
                        variants: variants.clone(),
×
248
                        optional: false,
249
                    },
250
                ));
251
            }
252
        }
253
        Some(other) => {
2✔
254
            if value != *other {
4✔
255
                return Err(Error::InvalidObjectValueType(value, other.to_owned()));
2✔
256
            }
257
        }
258
        None => {
259
            content.insert(key, value);
13✔
260
        }
261
    }
262
    Ok(())
15✔
263
}
264

265
#[cfg(test)]
266
mod tests {
267
    use crate::parser::Parser;
268

269
    use super::*;
270

271
    #[test]
272
    fn parse_null() {
273
        let source = "null";
274
        let cst = Parser::parse(source, &mut Vec::new());
275

276
        let value = parse_cst(&cst, source).unwrap();
277

278
        assert_eq!(value, Value::Null);
279
    }
280

281
    #[test]
282
    fn parse_number() {
283
        let source = "123";
284
        let cst = Parser::parse(source, &mut Vec::new());
285

286
        let value = parse_cst(&cst, source).unwrap();
287

288
        assert_eq!(value, Value::Number { optional: false });
289
    }
290

291
    #[test]
292
    fn parse_string() {
293
        let source = "\"123\"";
294
        let cst = Parser::parse(source, &mut Vec::new());
295

296
        let value = parse_cst(&cst, source).unwrap();
297

298
        assert_eq!(value, Value::String { optional: false });
299
    }
300

301
    #[test]
302
    fn parse_bool() {
303
        let source = "true";
304
        let cst = Parser::parse(source, &mut Vec::new());
305

306
        let value = parse_cst(&cst, source).unwrap();
307

308
        assert_eq!(value, Value::Bool { optional: false });
309
    }
310

311
    #[test]
312
    fn parse_array() {
313
        let source = "[12, 34, 56, 78]";
314
        let cst = Parser::parse(source, &mut Vec::new());
315

316
        let value = parse_cst(&cst, source).unwrap();
317

318
        assert_eq!(
319
            value,
320
            Value::Array {
321
                r#type: Box::new(Value::Number { optional: false }),
322
                optional: false
323
            }
324
        );
325
    }
326

327
    #[test]
328
    fn parse_array_other() {
329
        let source = "[true, false, true]";
330
        let cst = Parser::parse(source, &mut Vec::new());
331

332
        let value = parse_cst(&cst, source).unwrap();
333

334
        assert_eq!(
335
            value,
336
            Value::Array {
337
                r#type: Box::new(Value::Bool { optional: false }),
338
                optional: false
339
            }
340
        );
341
    }
342

343
    #[test]
344
    fn parse_tuple() {
345
        let source = "[12, true, \"str\"]";
346
        let cst = Parser::parse(source, &mut Vec::new());
347

348
        let value = parse_cst(&cst, source).unwrap();
349

350
        assert_eq!(
351
            value,
352
            Value::Tuple {
353
                elements: vec![
354
                    Value::Number { optional: false },
355
                    Value::Bool { optional: false },
356
                    Value::String { optional: false }
357
                ],
358
                optional: false
359
            }
360
        );
361
    }
362

363
    #[test]
364
    fn parse_object() {
365
        let source = r#"{"key": 123, "key2": true}"#;
366
        let cst = Parser::parse(source, &mut Vec::new());
367

368
        let value = parse_cst(&cst, source).unwrap();
369

370
        assert_eq!(
371
            value,
372
            Value::Object {
373
                content: [
374
                    ("key".to_string(), Value::Number { optional: false }),
375
                    ("key2".to_string(), Value::Bool { optional: false })
376
                ]
377
                .into(),
378
                optional: false
379
            }
380
        );
381
    }
382

383
    #[test]
384
    fn parse_array_of_objects_diff() {
385
        let source = r#"[{"a": 1}, {"b": 2}, {"c": 3}, {}]"#;
386
        let cst = Parser::parse(source, &mut Vec::new());
387

388
        let value = parse_cst(&cst, source).unwrap();
389

390
        assert_eq!(
391
            value,
392
            Value::Array {
393
                r#type: Box::new(Value::Object {
394
                    content: [
395
                        ("a".to_string(), Value::Number { optional: true }),
396
                        ("b".to_string(), Value::Number { optional: true }),
397
                        ("c".to_string(), Value::Number { optional: true })
398
                    ]
399
                    .into(),
400
                    optional: false
401
                }),
402
                optional: false
403
            }
404
        );
405
    }
406

407
    #[test]
408
    fn parse_array_of_objects_same() {
409
        let source = r#"[{"a": 1}, {"a": 2}, {"a": 3}]"#;
410
        let cst = Parser::parse(source, &mut Vec::new());
411

412
        let value = parse_cst(&cst, source).unwrap();
413

414
        assert_eq!(
415
            value,
416
            Value::Array {
417
                r#type: Box::new(Value::Object {
418
                    content: [("a".to_string(), Value::Number { optional: false })].into(),
419
                    optional: false
420
                }),
421
                optional: false
422
            }
423
        );
424
    }
425

426
    #[test]
427
    fn parse_array_of_objects_diff_single_key() {
428
        let source = r#"[{"a": 1}, {"a": 4, "b": 2}, {"a" : 5, "c": 3}]"#;
429
        let cst = Parser::parse(source, &mut Vec::new());
430

431
        let value = parse_cst(&cst, source).unwrap();
432

433
        assert_eq!(
434
            value,
435
            Value::Array {
436
                r#type: Box::new(Value::Object {
437
                    content: [
438
                        ("a".to_string(), Value::Number { optional: false }),
439
                        ("b".to_string(), Value::Number { optional: true }),
440
                        ("c".to_string(), Value::Number { optional: true })
441
                    ]
442
                    .into(),
443
                    optional: false
444
                }),
445
                optional: false
446
            }
447
        );
448
    }
449

450
    #[test]
451
    fn parse_array_of_emptyobject() {
452
        let source = "[{}]";
453
        let cst = Parser::parse(source, &mut Vec::new());
454

455
        let value = parse_cst(&cst, source).unwrap();
456

457
        assert_eq!(
458
            value,
459
            Value::Array {
460
                r#type: Box::new(Value::Object {
461
                    content: BTreeMap::default(),
462
                    optional: false
463
                }),
464
                optional: false
465
            }
466
        );
467
    }
468

469
    #[test]
470
    fn parse_escaped_fields() {
471
        let source = r#"{"a\\nb":1, "a\\u0041": 2, "quoted_key\\\"x\\\"": true, "\\u0000": "x"}"#;
472
        let cst = Parser::parse(source, &mut Vec::new());
473

474
        let value = parse_cst(&cst, source).unwrap();
475

476
        assert_eq!(
477
            value,
478
            Value::Object {
479
                content: [
480
                    ("\\u0000".to_string(), Value::String { optional: false }),
481
                    ("a\\nb".to_string(), Value::Number { optional: false }),
482
                    ("a\\u0041".to_string(), Value::Number { optional: false }),
483
                    (
484
                        "quoted_key\\\"x\\\"".to_string(),
485
                        Value::Bool { optional: false }
486
                    )
487
                ]
488
                .into_iter()
489
                .collect(),
490
                optional: false
491
            }
492
        );
493
    }
494
}
495

496
#[cfg(test)]
497
mod test_errors {
498
    use crate::parser::Parser;
499

500
    use super::*;
501

502
    #[test]
503
    fn parse_multiple_roots() {
504
        let source = "123 true \"str\"";
505
        let cst = Parser::parse(source, &mut Vec::new());
506

507
        let err = parse_cst(&cst, source).unwrap_err();
508

509
        assert_eq!(err.to_string(), "invalid JSON `true \"str\"`: 4..14");
510
    }
511

512
    #[test]
513
    fn parse_only_ws() {
514
        let source = "         ";
515
        let cst = Parser::parse(source, &mut Vec::new());
516

517
        let err = parse_cst(&cst, source).unwrap_err();
518

519
        assert_eq!(err.to_string(), "invalid JSON `         `: 0..9");
520
    }
521

522
    #[test]
523
    fn parse_only_mismatch() {
524
        let source = "{ 123: 123 }";
525
        let cst = Parser::parse(source, &mut Vec::new());
526

527
        let err = parse_cst(&cst, source).unwrap_err();
528

529
        assert_eq!(err.to_string(), "invalid JSON `123: 123 }`: 2..12");
530
    }
531

532
    #[test]
533
    fn parse_only_mismatch_unterminated_key() {
534
        let source = "{ \"123: 123 }";
535
        let cst = Parser::parse(source, &mut Vec::new());
536

537
        let err = parse_cst(&cst, source).unwrap_err();
538

539
        assert_eq!(err.to_string(), "invalid JSON `\"123: 123 }`: 2..13");
540
    }
541

542
    #[test]
543
    fn parse_unterminated_string() {
544
        let source = "\"123";
545
        let cst = Parser::parse(source, &mut Vec::new());
546

547
        let err = parse_cst(&cst, source).unwrap_err();
548

549
        assert_eq!(err.to_string(), "invalid JSON `\"123`: 0..4");
550
    }
551

552
    #[test]
553
    fn parse_uninit_string() {
554
        let source = "123\"";
555
        let cst = Parser::parse(source, &mut Vec::new());
556

557
        let err = parse_cst(&cst, source).unwrap_err();
558

559
        assert_eq!(err.to_string(), "invalid JSON `\"`: 3..4");
560
    }
561

562
    #[test]
563
    fn parse_invalid_number() {
564
        let source = "123..43";
565
        let cst = Parser::parse(source, &mut Vec::new());
566

567
        let err = parse_cst(&cst, source).unwrap_err();
568

569
        assert_eq!(err.to_string(), "invalid JSON `.`: 3..4");
570
    }
571
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc