• 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

81.18
/src/value.rs
1
#![allow(clippy::match_same_arms)]
2
use std::{
3
    collections::{BTreeMap, BTreeSet, btree_map::Keys},
4
    fmt::Display,
5
};
6

7
use serde::{Deserialize, Serialize};
8

9
pub mod subset;
10
pub mod subtypes;
11

12
/// Represents any valid JSON value shape.
13
///
14
/// See the [`serde_json_shape::value` module documentation](self) for usage examples.
15
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
16
pub enum Value {
17
    /// Represents a JSON null value.
18
    Null,
19

20
    /// Represents a JSON boolean.
21
    Bool {
22
        /// If type is optional
23
        optional: bool,
24
    },
25

26
    /// Represents a JSON number.
27
    Number {
28
        /// If type is optional
29
        optional: bool,
30
    },
31
    /// Represents a JSON string.
32
    String {
33
        /// If type is optional
34
        optional: bool,
35
    },
36
    /// Represents a JSON array.
37
    Array {
38
        /// Type contained in the Array
39
        r#type: Box<Value>,
40
        /// If type is optional
41
        optional: bool,
42
    },
43

44
    /// Represents a JSON object.
45
    Object {
46
        /// Object internal members map, with key as `String` and value as [`JsonShape`]
47
        content: BTreeMap<String, Value>,
48
        /// If type is optional
49
        optional: bool,
50
    },
51

52
    /// Represents a JSON Value that can assume one of the Values described.
53
    /// Similar to an enum containing diffenrent internal types in Rust.
54
    OneOf {
55
        /// All possible [`JsonShape`] values
56
        variants: BTreeSet<Value>,
57
        /// If type is optional
58
        optional: bool,
59
    },
60
}
61

62
impl Value {
63
    /// Is this [`JsonShape`] optional? eg, `Option<String>`
64
    #[must_use]
65
    pub const fn is_optional(&self) -> bool {
4✔
66
        match self {
4✔
67
            Value::Null => true,
1✔
68
            Value::Bool { optional } => *optional,
1✔
69
            Value::Number { optional } => *optional,
2✔
70
            Value::String { optional } => *optional,
3✔
71
            Value::Array { optional, .. } => *optional,
1✔
72
            Value::Object { optional, .. } => *optional,
1✔
73
            Value::OneOf { optional, .. } => *optional,
1✔
74
        }
75
    }
76

77
    pub(crate) const fn is_single(&self) -> bool {
1✔
78
        match self {
1✔
79
            Value::Null => true,
1✔
80
            Value::Bool { .. } => true,
1✔
81
            Value::Number { .. } => true,
1✔
82
            Value::String { .. } => true,
1✔
NEW
83
            Value::Array { .. } => false,
×
NEW
84
            Value::Object { .. } => false,
×
NEW
85
            Value::OneOf { .. } => false,
×
86
        }
87
    }
88

89
    #[allow(clippy::wrong_self_convention)]
90
    pub(crate) fn as_optional(self) -> Self {
3✔
91
        match self {
3✔
92
            Value::Null => Value::Null,
×
93
            Value::Bool { .. } => Value::Bool { optional: true },
94
            Value::Number { .. } => Value::Number { optional: true },
95
            Value::String { .. } => Value::String { optional: true },
96
            Value::Array { r#type, .. } => Value::Array {
97
                optional: true,
98
                r#type,
99
            },
100
            Value::Object { content, .. } => Value::Object {
101
                optional: true,
102
                content,
103
            },
104
            Value::OneOf { variants, .. } => Value::OneOf {
105
                optional: true,
106
                variants,
107
            },
108
        }
109
    }
110

111
    pub(crate) const fn to_optional_mut(&mut self) {
3✔
112
        match self {
3✔
113
            Value::Null => (),
114
            Value::Bool { optional } => {
3✔
115
                *optional = true;
3✔
116
            }
117
            Value::Number { optional } => {
1✔
118
                *optional = true;
1✔
119
            }
120
            Value::String { optional } => {
1✔
121
                *optional = true;
1✔
122
            }
123
            Value::Array { optional, .. } => {
×
124
                *optional = true;
×
125
            }
126
            Value::Object { optional, .. } => {
×
127
                *optional = true;
×
128
            }
129
            Value::OneOf { optional, .. } => {
×
130
                *optional = true;
×
131
            }
132
        }
133
    }
134

135
    /// Return the keys contained in a [`JsonShape::Object`]
136
    #[must_use]
137
    pub fn keys(&self) -> Option<Keys<String, Value>> {
3✔
138
        if let Self::Object { content, .. } = self {
6✔
139
            Some(content.keys())
3✔
140
        } else {
141
            None
1✔
142
        }
143
    }
144

145
    /// Checks if Json Node is null
146
    #[must_use]
147
    pub const fn is_null(&self) -> bool {
1✔
148
        matches!(self, Self::Null)
1✔
149
    }
150

151
    /// Checks if Json Node is boolean
152
    #[must_use]
153
    pub const fn is_boolean(&self) -> bool {
2✔
154
        matches!(self, Self::Bool { .. })
1✔
155
    }
156

157
    /// Checks if Json Node is number
158
    #[must_use]
159
    pub const fn is_number(&self) -> bool {
4✔
160
        matches!(self, Self::Number { .. })
4✔
161
    }
162

163
    /// Checks if Json Node is string
164
    #[must_use]
165
    pub const fn is_string(&self) -> bool {
3✔
166
        matches!(self, Self::String { .. })
3✔
167
    }
168

169
    /// Checks if Json Node is array
170
    #[must_use]
NEW
171
    pub const fn is_array(&self) -> bool {
×
NEW
172
        matches!(self, Self::Array { .. })
×
173
    }
174

175
    /// Checks if Json Node is object
176
    #[must_use]
NEW
177
    pub const fn is_object(&self) -> bool {
×
NEW
178
        matches!(self, Self::Object { .. })
×
179
    }
180

181
    /// Checks if Json Node is one of
182
    #[must_use]
NEW
183
    pub const fn is_oneof(&self) -> bool {
×
NEW
184
        matches!(self, Self::OneOf { .. })
×
185
    }
186
}
187

188
impl Display for Value {
189
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3✔
190
        match self {
4✔
191
            Value::Null => write!(f, "Null"),
3✔
192
            Value::Bool { optional } => write!(
6✔
193
                f,
194
                "{}",
195
                if *optional {
7✔
196
                    "Option<Boolean>"
3✔
197
                } else {
198
                    "Boolean"
3✔
199
                }
200
            ),
201
            Value::Number { optional } => write!(
7✔
202
                f,
203
                "{}",
204
                if *optional {
7✔
205
                    "Option<Number>"
3✔
206
                } else {
207
                    "Number"
3✔
208
                }
209
            ),
210
            Value::String { optional } => write!(
7✔
211
                f,
212
                "{}",
213
                if *optional {
7✔
214
                    "Option<String>"
1✔
215
                } else {
216
                    "String"
3✔
217
                }
218
            ),
219
            Value::Array { r#type, optional } => {
4✔
220
                if *optional {
4✔
221
                    write!(f, "Option<Array<{type}>>")
1✔
222
                } else {
223
                    write!(f, "Array<{type}>")
3✔
224
                }
225
            }
226
            Value::Object { content, optional } => {
4✔
227
                if *optional {
4✔
228
                    write!(f, "Option<Object{{{}}}>", display_object_content(content))
1✔
229
                } else {
230
                    write!(f, "Object{{{}}}", display_object_content(content))
3✔
231
                }
232
            }
233
            Value::OneOf { variants, optional } => {
4✔
234
                let variants = variants
4✔
235
                    .iter()
236
                    .map(ToString::to_string)
4✔
237
                    .collect::<Vec<_>>()
238
                    .join(" | ");
239
                if *optional {
4✔
240
                    write!(f, "Option<OneOf[{variants}]>",)
2✔
241
                } else {
242
                    write!(f, "OneOf[{variants}]",)
6✔
243
                }
244
            }
245
        }
246
    }
247
}
248

249
fn display_object_content(content: &BTreeMap<String, Value>) -> String {
4✔
250
    content
4✔
251
        .iter()
252
        .map(|(key, value)| format!("\"{key}\": {value}"))
12✔
253
        .collect::<Vec<_>>()
254
        .join(", ")
255
}
256

257
#[cfg(test)]
258
mod tests {
259
    use super::*;
260

261
    #[test]
262
    fn is_optional_returns_true_when_values_are_optional() {
263
        assert!(Value::Null.is_optional());
264
        assert!(Value::Bool { optional: true }.is_optional());
265
        assert!(Value::Number { optional: true }.is_optional());
266
        assert!(Value::String { optional: true }.is_optional());
267
        assert!(
268
            Value::Array {
269
                optional: true,
270
                r#type: Box::new(Value::Null)
271
            }
272
            .is_optional()
273
        );
274
        assert!(
275
            Value::Object {
276
                optional: true,
277
                content: BTreeMap::default()
278
            }
279
            .is_optional()
280
        );
281
        assert!(
282
            Value::OneOf {
283
                optional: true,
284
                variants: BTreeSet::default()
285
            }
286
            .is_optional()
287
        );
288
    }
289

290
    #[test]
291
    fn is_optional_returns_false_when_values_are_not_optional() {
292
        assert!(!Value::Bool { optional: false }.is_optional());
293
        assert!(!Value::Number { optional: false }.is_optional());
294
        assert!(!Value::String { optional: false }.is_optional());
295
        assert!(
296
            !Value::Array {
297
                optional: false,
298
                r#type: Box::new(Value::Null)
299
            }
300
            .is_optional()
301
        );
302
        assert!(
303
            !Value::Object {
304
                optional: false,
305
                content: BTreeMap::default()
306
            }
307
            .is_optional()
308
        );
309
        assert!(
310
            !Value::OneOf {
311
                optional: false,
312
                variants: BTreeSet::default()
313
            }
314
            .is_optional()
315
        );
316
    }
317

318
    #[test]
319
    fn as_optional_returns_optional_version_of_values() {
320
        assert!(Value::Bool { optional: false }.as_optional().is_optional());
321
        assert!(
322
            Value::Number { optional: false }
323
                .as_optional()
324
                .is_optional()
325
        );
326
        assert!(
327
            Value::String { optional: false }
328
                .as_optional()
329
                .is_optional()
330
        );
331
        assert!(
332
            Value::Array {
333
                optional: false,
334
                r#type: Box::new(Value::Null)
335
            }
336
            .as_optional()
337
            .is_optional()
338
        );
339
        assert!(
340
            Value::Object {
341
                optional: false,
342
                content: BTreeMap::default()
343
            }
344
            .as_optional()
345
            .is_optional()
346
        );
347
        assert!(
348
            Value::OneOf {
349
                optional: false,
350
                variants: BTreeSet::default()
351
            }
352
            .as_optional()
353
            .is_optional()
354
        );
355
    }
356

357
    #[test]
358
    fn keys_returns_keys_only_for_object() {
359
        assert!(Value::Null.keys().is_none());
360
        assert!(Value::Bool { optional: true }.keys().is_none());
361
        assert!(Value::Number { optional: true }.keys().is_none());
362
        assert!(Value::String { optional: true }.keys().is_none());
363
        assert!(
364
            Value::Array {
365
                optional: true,
366
                r#type: Box::new(Value::Null)
367
            }
368
            .keys()
369
            .is_none()
370
        );
371
        assert!(
372
            Value::OneOf {
373
                optional: true,
374
                variants: BTreeSet::default()
375
            }
376
            .keys()
377
            .is_none()
378
        );
379
        assert_eq!(
380
            Value::Object {
381
                optional: true,
382
                content: [
383
                    ("key_1".to_string(), Value::Null),
384
                    ("key_2".to_string(), Value::Null),
385
                ]
386
                .into()
387
            }
388
            .keys()
389
            .unwrap()
390
            .collect::<Vec<_>>(),
391
            vec!["key_1", "key_2"]
392
        );
393
    }
394

395
    #[test]
396
    fn to_string_for_optional_values() {
397
        assert_eq!(Value::Null.to_string(), "Null");
398
        assert_eq!(
399
            Value::Bool { optional: true }.to_string(),
400
            "Option<Boolean>"
401
        );
402
        assert_eq!(
403
            Value::Number { optional: true }.to_string(),
404
            "Option<Number>"
405
        );
406
        assert_eq!(
407
            Value::String { optional: true }.to_string(),
408
            "Option<String>"
409
        );
410
        assert_eq!(
411
            Value::Array {
412
                optional: true,
413
                r#type: Box::new(Value::Null)
414
            }
415
            .to_string(),
416
            "Option<Array<Null>>"
417
        );
418
        assert_eq!(
419
            Value::Object {
420
                optional: true,
421
                content: BTreeMap::default()
422
            }
423
            .to_string(),
424
            "Option<Object{}>"
425
        );
426
        assert_eq!(
427
            Value::Object {
428
                optional: true,
429
                content: [
430
                    ("key_1".to_string(), Value::Null),
431
                    ("key_2".to_string(), Value::Number { optional: true }),
432
                    ("key_3".to_string(), Value::Number { optional: false })
433
                ]
434
                .into()
435
            }
436
            .to_string(),
437
            "Option<Object{\"key_1\": Null, \"key_2\": Option<Number>, \"key_3\": Number}>"
438
        );
439
        assert_eq!(
440
            Value::OneOf {
441
                optional: true,
442
                variants: [
443
                    Value::Null,
444
                    Value::Number { optional: true },
445
                    Value::Number { optional: false }
446
                ]
447
                .into()
448
            }
449
            .to_string(),
450
            "Option<OneOf[Null | Number | Option<Number>]>"
451
        );
452
    }
453

454
    #[test]
455
    fn to_string_for_non_optional_values() {
456
        assert_eq!(Value::Bool { optional: false }.to_string(), "Boolean");
457
        assert_eq!(Value::Number { optional: false }.to_string(), "Number");
458
        assert_eq!(Value::String { optional: false }.to_string(), "String");
459
        assert_eq!(
460
            Value::Array {
461
                optional: false,
462
                r#type: Box::new(Value::Null)
463
            }
464
            .to_string(),
465
            "Array<Null>"
466
        );
467
        assert_eq!(
468
            Value::Object {
469
                optional: false,
470
                content: BTreeMap::default()
471
            }
472
            .to_string(),
473
            "Object{}"
474
        );
475
        assert_eq!(
476
            Value::Object {
477
                optional: false,
478
                content: [
479
                    ("key_1".to_string(), Value::Null),
480
                    ("key_2".to_string(), Value::Number { optional: true }),
481
                    ("key_3".to_string(), Value::Number { optional: false })
482
                ]
483
                .into()
484
            }
485
            .to_string(),
486
            "Object{\"key_1\": Null, \"key_2\": Option<Number>, \"key_3\": Number}"
487
        );
488
        assert_eq!(
489
            Value::OneOf {
490
                optional: false,
491
                variants: [
492
                    Value::Null,
493
                    Value::Number { optional: false },
494
                    Value::Number { optional: true }
495
                ]
496
                .into()
497
            }
498
            .to_string(),
499
            "OneOf[Null | Number | Option<Number>]"
500
        );
501
    }
502

503
    #[test]
504
    fn to_optional_mut_transforms_value_inline_as_ref_mut() {
505
        let mut v = Value::Bool { optional: false };
506
        assert!(!v.is_optional());
507
        v.to_optional_mut();
508
        assert!(v.is_optional());
509
        let mut v = Value::Number { optional: false };
510
        assert!(!v.is_optional());
511
        v.to_optional_mut();
512
        assert!(v.is_optional());
513
        let mut v = Value::String { optional: false };
514
        assert!(!v.is_optional());
515
        v.to_optional_mut();
516
        assert!(v.is_optional());
517
    }
518
}
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