1use crate::{ArrayContent, DiagnosticsHierarchy, ExponentialHistogram, LinearHistogram, Property};
6use base64::engine::Engine as _;
7use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
8use serde::de::{self, MapAccess, SeqAccess, Visitor};
9use serde::{Deserialize, Deserializer};
10use std::collections::HashMap;
11use std::fmt;
12use std::hash::Hash;
13use std::marker::PhantomData;
14use std::str::FromStr;
15
16#[cfg(feature = "json_schema")]
17use schemars::Schema;
18
19struct RootVisitor<Key> {
20 marker: PhantomData<Key>,
22}
23
24impl<'de, Key> Visitor<'de> for RootVisitor<Key>
25where
26 Key: FromStr + Clone + Hash + Eq + AsRef<str>,
27{
28 type Value = DiagnosticsHierarchy<Key>;
29
30 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31 formatter.write_str("there should be a single root")
32 }
33
34 fn visit_map<V>(self, mut map: V) -> Result<DiagnosticsHierarchy<Key>, V::Error>
35 where
36 V: MapAccess<'de>,
37 {
38 let result = match map.next_entry::<String, FieldValue<Key>>()? {
39 Some((map_key, value)) => {
40 let key = Key::from_str(&map_key)
41 .map_err(|_| de::Error::custom("failed to parse key"))?;
42 value.into_node(&key)
43 }
44 None => return Err(de::Error::invalid_length(0, &"expected a root node")),
45 };
46
47 let mut found = 1;
48 while map.next_key::<String>()?.is_some() {
49 found += 1;
50 }
51
52 if found > 1 {
53 return Err(de::Error::invalid_length(found, &"expected a single root"));
54 }
55
56 result.ok_or_else(|| de::Error::custom("expected node for root"))
57 }
58}
59
60impl<'de, Key> Deserialize<'de> for DiagnosticsHierarchy<Key>
61where
62 Key: FromStr + Clone + Hash + Eq + AsRef<str>,
63{
64 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
65 where
66 D: Deserializer<'de>,
67 {
68 deserializer.deserialize_map(RootVisitor { marker: PhantomData })
69 }
70}
71
72trait IntoProperty<Key> {
73 fn into_property(self, key: &Key) -> Option<Property<Key>>;
74}
75
76enum FieldValue<Key> {
78 String(String),
79 Bytes(Vec<u8>),
80 Int(i64),
81 Uint(u64),
82 Double(f64),
83 Bool(bool),
84 Array(Vec<NumericValue>),
85 LinearIntHistogram(LinearHistogram<i64>),
86 LinearUintHistogram(LinearHistogram<u64>),
87 LinearDoubleHistogram(LinearHistogram<f64>),
88 ExponentialIntHistogram(ExponentialHistogram<i64>),
89 ExponentialUintHistogram(ExponentialHistogram<u64>),
90 ExponentialDoubleHistogram(ExponentialHistogram<f64>),
91 Node(HashMap<Key, FieldValue<Key>>),
92 StringList(Vec<String>),
93}
94
95impl<Key: Clone> IntoProperty<Key> for FieldValue<Key> {
96 fn into_property(self, key: &Key) -> Option<Property<Key>> {
97 match self {
98 Self::String(value) => Some(Property::String(key.clone(), value)),
99 Self::Bytes(value) => Some(Property::Bytes(key.clone(), value)),
100 Self::Int(value) => Some(Property::Int(key.clone(), value)),
101 Self::Uint(value) => Some(Property::Uint(key.clone(), value)),
102 Self::Double(value) => Some(Property::Double(key.clone(), value)),
103 Self::Bool(value) => Some(Property::Bool(key.clone(), value)),
104 Self::Array(values) => values.into_property(key),
105 Self::ExponentialIntHistogram(histogram) => {
106 Some(Property::IntArray(key.clone(), ArrayContent::ExponentialHistogram(histogram)))
107 }
108 Self::ExponentialUintHistogram(histogram) => Some(Property::UintArray(
109 key.clone(),
110 ArrayContent::ExponentialHistogram(histogram),
111 )),
112 Self::ExponentialDoubleHistogram(histogram) => Some(Property::DoubleArray(
113 key.clone(),
114 ArrayContent::ExponentialHistogram(histogram),
115 )),
116 Self::LinearIntHistogram(histogram) => {
117 Some(Property::IntArray(key.clone(), ArrayContent::LinearHistogram(histogram)))
118 }
119 Self::LinearUintHistogram(histogram) => {
120 Some(Property::UintArray(key.clone(), ArrayContent::LinearHistogram(histogram)))
121 }
122 Self::LinearDoubleHistogram(histogram) => {
123 Some(Property::DoubleArray(key.clone(), ArrayContent::LinearHistogram(histogram)))
124 }
125 Self::StringList(list) => Some(Property::StringList(key.clone(), list)),
126 Self::Node(_) => None,
127 }
128 }
129}
130
131impl<Key: AsRef<str> + Clone> FieldValue<Key> {
132 fn is_property(&self) -> bool {
133 !matches!(self, Self::Node(_))
134 }
135
136 fn into_node(self, key: &Key) -> Option<DiagnosticsHierarchy<Key>> {
137 match self {
138 Self::Node(map) => {
139 let mut properties = vec![];
140 let mut children = vec![];
141 for (map_key, value) in map {
142 if value.is_property() {
143 properties.push(value.into_property(&map_key).unwrap());
144 } else {
145 children.push(value.into_node(&map_key).unwrap());
146 }
147 }
148 Some(DiagnosticsHierarchy::new(key.as_ref(), properties, children))
149 }
150 _ => None,
151 }
152 }
153}
154
155impl<'de, Key> Deserialize<'de> for FieldValue<Key>
156where
157 Key: FromStr + Hash + Eq,
158{
159 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
160 where
161 D: Deserializer<'de>,
162 {
163 deserializer.deserialize_any(FieldVisitor { marker: PhantomData })
164 }
165}
166
167struct FieldVisitor<Key> {
168 marker: PhantomData<Key>,
169}
170
171fn value_as_u64<Key>(value: &FieldValue<Key>) -> Option<u64> {
186 match value {
187 FieldValue::Uint(value) => Some(*value),
188 FieldValue::Int(value) => u64::try_from(*value).ok(),
189 _ => None,
190 }
191}
192
193fn value_as_i64<Key>(value: &FieldValue<Key>) -> Option<i64> {
194 match value {
195 FieldValue::Int(value) => Some(*value),
196 FieldValue::Uint(value) => i64::try_from(*value).ok(),
197 _ => None,
198 }
199}
200
201fn sanitize_histogram_parameters<Key>(
205 indexes: Option<&FieldValue<Key>>,
206 n_parameters: usize,
207 dict_len: usize,
208 counts: &FieldValue<Key>,
209 size: &FieldValue<Key>,
210) -> Result<(Option<Vec<usize>>, usize), ()> {
211 let size = match size {
212 FieldValue::Uint(size) => *size as usize,
213 FieldValue::Int(size) if *size >= 0 => *size as usize,
214 _ => return Err(()),
215 };
216 let counts_len = match counts {
220 FieldValue::Array(counts) => counts.len(),
221 FieldValue::StringList(counts) if counts.is_empty() => 0,
222 _ => return Err(()),
223 };
224 if size < 3 {
225 return Err(());
227 }
228 match indexes {
231 None => {
232 if counts_len != size || dict_len != n_parameters - 1 {
233 return Err(());
234 }
235 Ok((None, size))
236 }
237 Some(FieldValue::StringList(indexes)) if indexes.is_empty() => {
238 if counts_len != 0 || dict_len != n_parameters {
239 return Err(());
240 }
241 Ok((Some(vec![]), size))
242 }
243 Some(FieldValue::Array(indexes)) => {
244 if indexes.len() != counts_len || counts_len > size || dict_len != n_parameters {
245 return Err(());
246 }
247 let indexes = indexes
248 .iter()
249 .map(|value| match value.as_u64() {
250 None => None,
251 Some(i) if (i as usize) < size => Some(i as usize),
252 _ => None,
253 })
254 .collect::<Option<Vec<_>>>();
255 match indexes {
256 None => Err(()),
257 Some(indexes) => Ok((Some(indexes), size)),
258 }
259 }
260 _ => Err(()),
261 }
262}
263
264fn match_linear_histogram<Key>(
265 floor: &FieldValue<Key>,
266 step: &FieldValue<Key>,
267 counts: &FieldValue<Key>,
268 indexes: Option<&FieldValue<Key>>,
269 size: &FieldValue<Key>,
270 dict_len: usize,
271) -> Option<FieldValue<Key>> {
272 let (indexes, size) = match sanitize_histogram_parameters(indexes, 5, dict_len, counts, size) {
273 Ok((indexes, size)) => (indexes, size),
274 Err(()) => return None,
275 };
276 match (floor, step, counts) {
279 (FieldValue::Double(floor), FieldValue::Double(step), counts) => {
280 let counts = parse_f64_list(counts)?;
281 Some(FieldValue::LinearDoubleHistogram(LinearHistogram {
282 floor: *floor,
283 step: *step,
284 counts,
285 indexes,
286 size,
287 }))
288 }
289 (floor, step, counts) => {
290 let counts_i64 = parse_i64_list(counts);
291 if let (Some(counts), Some(floor), Some(step)) =
292 (counts_i64, value_as_i64(floor), value_as_i64(step))
293 {
294 return Some(FieldValue::LinearIntHistogram(LinearHistogram {
295 floor,
296 step,
297 counts,
298 indexes,
299 size,
300 }));
301 }
302 if let (Some(counts), Some(floor), Some(step)) =
304 (parse_u64_list(counts), value_as_u64(floor), value_as_u64(step))
305 {
306 return Some(FieldValue::LinearUintHistogram(LinearHistogram {
307 floor,
308 step,
309 counts,
310 indexes,
311 size,
312 }));
313 }
314 None
315 }
316 }
317}
318
319fn match_exponential_histogram<Key>(
320 floor: &FieldValue<Key>,
321 initial_step: &FieldValue<Key>,
322 step_multiplier: &FieldValue<Key>,
323 counts: &FieldValue<Key>,
324 indexes: Option<&FieldValue<Key>>,
325 size: &FieldValue<Key>,
326 dict_len: usize,
327) -> Option<FieldValue<Key>> {
328 let (indexes, size) = match sanitize_histogram_parameters(indexes, 6, dict_len, counts, size) {
329 Ok((indexes, size)) => (indexes, size),
330 Err(()) => return None,
331 };
332 match (floor, initial_step, step_multiplier, counts) {
333 (
334 FieldValue::Double(floor),
335 FieldValue::Double(initial_step),
336 FieldValue::Double(step_multiplier),
337 counts,
338 ) => {
339 let counts = parse_f64_list(counts)?;
340 Some(FieldValue::ExponentialDoubleHistogram(ExponentialHistogram {
341 floor: *floor,
342 initial_step: *initial_step,
343 step_multiplier: *step_multiplier,
344 counts,
345 indexes,
346 size,
347 }))
348 }
349 (floor, initial_step, step_multiplier, counts) => {
350 let counts_i64 = parse_i64_list(counts);
351 if let (Some(counts), Some(floor), Some(initial_step), Some(step_multiplier)) = (
352 counts_i64,
353 value_as_i64(floor),
354 value_as_i64(initial_step),
355 value_as_i64(step_multiplier),
356 ) {
357 return Some(FieldValue::ExponentialIntHistogram(ExponentialHistogram {
358 floor,
359 initial_step,
360 step_multiplier,
361 counts,
362 indexes,
363 size,
364 }));
365 }
366 if let (Some(counts), Some(floor), Some(initial_step), Some(step_multiplier)) = (
368 parse_u64_list(counts),
369 value_as_u64(floor),
370 value_as_u64(initial_step),
371 value_as_u64(step_multiplier),
372 ) {
373 return Some(FieldValue::ExponentialUintHistogram(ExponentialHistogram {
374 floor,
375 initial_step,
376 step_multiplier,
377 counts,
378 indexes,
379 size,
380 }));
381 }
382 None
383 }
384 }
385}
386
387fn match_histogram<Key>(dict: &HashMap<Key, FieldValue<Key>>) -> Option<FieldValue<Key>>
388where
389 Key: FromStr + Hash + Eq,
390{
391 let dict_len = dict.len();
393 if !(4..=6).contains(&dict_len) {
394 return None;
395 }
396 let get_key = |name: &str| -> Option<&FieldValue<Key>> {
397 let key = Key::from_str(name).ok()?;
398 dict.get(&key)
399 };
400 let floor = get_key("floor")?;
401 let step = get_key("step");
402 let initial_step = get_key("initial_step");
403 let step_multiplier = get_key("step_multiplier");
404 let counts = get_key("counts");
405 let indexes = get_key("indexes");
406 let size = get_key("size");
407 match (step, initial_step, step_multiplier, counts, indexes, size) {
409 (Some(step), None, None, Some(counts), indexes, Some(size)) => {
410 match_linear_histogram(floor, step, counts, indexes, size, dict_len)
411 }
412 (None, Some(initial_step), Some(step_multiplier), Some(counts), indexes, Some(size)) => {
413 match_exponential_histogram(
414 floor,
415 initial_step,
416 step_multiplier,
417 counts,
418 indexes,
419 size,
420 dict_len,
421 )
422 }
423 _ => None,
424 }
425}
426
427impl<'de, Key> Visitor<'de> for FieldVisitor<Key>
428where
429 Key: FromStr + Hash + Eq,
430{
431 type Value = FieldValue<Key>;
432
433 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
434 formatter.write_str("failed to field")
435 }
436
437 fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
438 where
439 V: MapAccess<'de>,
440 {
441 let mut entries = vec![];
442 while let Some(entry) = map.next_entry::<String, FieldValue<Key>>()? {
443 entries.push(entry);
444 }
445
446 let node = entries
447 .into_iter()
448 .map(|(key, value)| Key::from_str(&key).map(|key| (key, value)))
449 .collect::<Result<HashMap<Key, FieldValue<Key>>, _>>()
450 .map_err(|_| de::Error::custom("failed to parse key"))?;
451 if let Some(histogram) = match_histogram(&node) {
452 Ok(histogram)
453 } else {
454 Ok(FieldValue::Node(node))
455 }
456 }
457
458 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
459 Ok(FieldValue::Int(value))
460 }
461
462 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
463 Ok(FieldValue::Uint(value))
464 }
465
466 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> {
467 Ok(FieldValue::Double(value))
468 }
469
470 fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
471 Ok(FieldValue::Bool(value))
472 }
473
474 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
475 where
476 E: de::Error,
477 {
478 if value.starts_with("b64:") {
479 let bytes64 = value.replace("b64:", "");
480 let bytes = BASE64_STANDARD
481 .decode(&bytes64)
482 .map_err(|_| de::Error::custom("failed to decode bytes"))?;
483 return Ok(FieldValue::Bytes(bytes));
484 }
485 Ok(FieldValue::String(value.to_string()))
486 }
487
488 fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
489 where
490 S: SeqAccess<'de>,
491 {
492 let mut result = vec![];
493 while let Some(elem) = seq.next_element::<SeqItem>()? {
494 result.push(elem);
495 }
496 let mut array = vec![];
500 let mut strings = vec![];
501 for item in result {
502 match item {
503 SeqItem::Value(x) => array.push(x),
504 SeqItem::StringValue(x) => strings.push(x),
505 }
506 }
507
508 match (!array.is_empty(), !strings.is_empty()) {
509 (true, false) => Ok(FieldValue::Array(array)),
510 (false, _) => {
511 Ok(FieldValue::StringList(strings))
515 }
516 _ => Err(de::Error::custom("unexpected sequence containing mixed values")),
517 }
518 }
519}
520
521#[derive(Deserialize)]
522#[serde(untagged)]
523enum SeqItem {
524 Value(NumericValue),
525 StringValue(String),
526}
527
528enum NumericValue {
529 Positive(u64),
530 Negative(i64),
531 Double(f64),
532}
533
534impl NumericValue {
535 #[inline]
536 fn as_i64(&self) -> Option<i64> {
537 match self {
538 Self::Positive(x) if *x <= i64::MAX as u64 => Some(*x as i64),
539 Self::Negative(x) => Some(*x),
540 _ => None,
541 }
542 }
543
544 #[inline]
545 fn as_u64(&self) -> Option<u64> {
546 match self {
547 Self::Positive(x) => Some(*x),
548 _ => None,
549 }
550 }
551
552 #[inline]
553 fn as_f64(&self) -> Option<f64> {
554 match self {
555 Self::Double(x) => Some(*x),
556 _ => None,
557 }
558 }
559}
560
561impl<'de> Deserialize<'de> for NumericValue {
562 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
563 where
564 D: Deserializer<'de>,
565 {
566 deserializer.deserialize_any(NumericValueVisitor)
567 }
568}
569
570impl<Key: Clone> IntoProperty<Key> for Vec<NumericValue> {
571 fn into_property(self, key: &Key) -> Option<Property<Key>> {
572 if let Some(values) = parse_f64_vec(&self) {
573 return Some(Property::DoubleArray(key.clone(), ArrayContent::Values(values)));
574 }
575 if let Some(values) = parse_i64_vec(&self) {
576 return Some(Property::IntArray(key.clone(), ArrayContent::Values(values)));
577 }
578 if let Some(values) = parse_u64_vec(&self) {
579 return Some(Property::UintArray(key.clone(), ArrayContent::Values(values)));
580 }
581 None
582 }
583}
584
585macro_rules! parse_numeric_vec_impls {
586 ($($type:ty),*) => {
587 $(
588 paste::paste! {
589 fn [<parse_ $type _vec>](vec: &[NumericValue]) -> Option<Vec<$type>> {
590 vec.iter().map(|value| value.[<as_ $type>]()).collect::<Option<Vec<_>>>()
591 }
592
593 fn [<parse_ $type _list>]<Key>(list: &FieldValue<Key>) -> Option<Vec<$type>> {
597 match list {
598 FieldValue::StringList(list) if list.len() == 0 => Some(vec![]),
599 FieldValue::Array(vec) => [<parse_ $type _vec>](vec),
600 _ => None,
601 }
602 }
603 }
604 )*
605 };
606}
607
608parse_numeric_vec_impls!(f64, u64, i64);
616
617struct NumericValueVisitor;
618
619impl Visitor<'_> for NumericValueVisitor {
620 type Value = NumericValue;
621
622 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
623 formatter.write_str("failed to deserialize bucket array")
624 }
625
626 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
627 if value < 0 {
628 return Ok(NumericValue::Negative(value));
629 }
630 Ok(NumericValue::Positive(value as u64))
631 }
632
633 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
634 Ok(NumericValue::Positive(value))
635 }
636
637 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> {
638 Ok(NumericValue::Double(value))
639 }
640}
641
642#[cfg(feature = "json_schema")]
645impl<T> schemars::JsonSchema for DiagnosticsHierarchy<T> {
646 fn schema_name() -> std::borrow::Cow<'static, str> {
647 "DiagnosticsHierarchy".into()
648 }
649
650 fn json_schema(generator: &mut schemars::SchemaGenerator) -> Schema {
651 let defs_path =
652 generator.settings().definitions_path.trim_start_matches('#').trim_end_matches('/');
653 let self_ref = format!("#{}/{}", defs_path, Self::schema_name());
654 let property_schema = schemars::json_schema!({
655 "description": "A property, which can be any standard object, or an array, or a histogram",
656 "anyOf": [
657 String::json_schema(generator),
658 Vec::<f64>::json_schema(generator),
659 Vec::<i64>::json_schema(generator),
660 Vec::<u64>::json_schema(generator),
661 Vec::<String>::json_schema(generator),
662 LinearHistogram::<u64>::json_schema(generator),
663 LinearHistogram::<i64>::json_schema(generator),
664 LinearHistogram::<f64>::json_schema(generator),
665 ExponentialHistogram::<u64>::json_schema(generator),
666 ExponentialHistogram::<i64>::json_schema(generator),
667 ExponentialHistogram::<f64>::json_schema(generator),
668 i64::json_schema(generator),
669 u64::json_schema(generator),
670 f64::json_schema(generator),
671 bool::json_schema(generator),
672 {
674 "type": "object",
675 "$ref": self_ref
676 }
677 ]
678 });
679 schemars::json_schema!({
680 "type": "object",
681 "minProperties": 1,
682 "patternProperties": {
683 "^.*$": property_schema
684 }
685 })
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692 use crate::ArrayFormat;
693
694 #[cfg(feature = "json_schema")]
695 use ffx_writer::VerifiedMachineWriter;
696
697 #[fuchsia::test]
698 fn deserialize_json() {
699 let json_string = get_single_json_hierarchy();
700 let mut parsed_hierarchy: DiagnosticsHierarchy =
701 serde_json::from_str(&json_string).expect("deserialized");
702 let mut expected_hierarchy = get_unambigious_deserializable_hierarchy();
703 parsed_hierarchy.sort();
704 expected_hierarchy.sort();
705 assert_eq!(expected_hierarchy, parsed_hierarchy);
706 }
707
708 #[cfg(feature = "json_schema")]
709 #[fuchsia::test]
710 fn verify_schema() {
711 let json_string = get_single_json_hierarchy();
712 let data: serde_json::Value =
713 serde_json::from_str(&json_string).expect("valid json string");
714 let root = data.get("root").expect("expected root node");
715 if let Err(e) = VerifiedMachineWriter::<DiagnosticsHierarchy>::verify_schema(root) {
716 panic!("Error verifying schema of `{data:#?}`: {e:#?}")
717 }
718 }
719
720 #[fuchsia::test]
721 fn reversible_deserialize() {
722 let mut original_hierarchy = get_unambigious_deserializable_hierarchy();
723 let result =
724 serde_json::to_string(&original_hierarchy).expect("failed to format hierarchy");
725 let mut parsed_hierarchy: DiagnosticsHierarchy =
726 serde_json::from_str(&result).expect("deserialized");
727 parsed_hierarchy.sort();
728 original_hierarchy.sort();
729 assert_eq!(original_hierarchy, parsed_hierarchy);
730 }
731
732 #[fuchsia::test]
733 fn test_exp_histogram() {
734 let mut hierarchy = DiagnosticsHierarchy::new(
735 "root".to_string(),
736 vec![Property::IntArray(
737 "histogram".to_string(),
738 ArrayContent::new(
739 vec![1000, 1000, 2, 1, 2, 3, 4, 5, 6],
740 ArrayFormat::ExponentialHistogram,
741 )
742 .unwrap(),
743 )],
744 vec![],
745 );
746 let expected_json = serde_json::json!({
747 "root": {
748 "histogram": {
749 "floor": 1000,
750 "initial_step": 1000,
751 "step_multiplier": 2,
752 "counts": [1, 2, 3, 4, 5, 6],
753 "size": 6
754 }
755 }
756 });
757 let result_json = serde_json::json!(hierarchy);
758 assert_eq!(result_json, expected_json);
759 let mut parsed_hierarchy: DiagnosticsHierarchy =
760 serde_json::from_value(result_json).expect("deserialized");
761 parsed_hierarchy.sort();
762 hierarchy.sort();
763 assert_eq!(hierarchy, parsed_hierarchy);
764 }
765
766 fn get_unambigious_deserializable_hierarchy() -> DiagnosticsHierarchy {
768 DiagnosticsHierarchy::new(
769 "root",
770 vec![
771 Property::UintArray(
772 "array".to_string(),
773 ArrayContent::Values(vec![0, 2, u64::MAX]),
774 ),
775 Property::Bool("bool_true".to_string(), true),
776 Property::Bool("bool_false".to_string(), false),
777 Property::StringList(
778 "string_list".to_string(),
779 vec!["foo".to_string(), "bar".to_string()],
780 ),
781 Property::StringList("empty_string_list".to_string(), vec![]),
782 ],
783 vec![
784 DiagnosticsHierarchy::new(
785 "a",
786 vec![
787 Property::Double("double".to_string(), 2.5),
788 Property::DoubleArray(
789 "histogram".to_string(),
790 ArrayContent::new(
791 vec![0.0, 2.0, 4.0, 1.0, 3.0, 4.0, 7.0],
792 ArrayFormat::ExponentialHistogram,
793 )
794 .unwrap(),
795 ),
796 ],
797 vec![],
798 ),
799 DiagnosticsHierarchy::new(
800 "b",
801 vec![
802 Property::Int("int".to_string(), -2),
803 Property::String("string".to_string(), "some value".to_string()),
804 Property::IntArray(
805 "histogram".to_string(),
806 ArrayContent::new(vec![0, 2, 4, 1, 3], ArrayFormat::LinearHistogram)
807 .unwrap(),
808 ),
809 ],
810 vec![],
811 ),
812 ],
813 )
814 }
815
816 pub fn get_single_json_hierarchy() -> String {
817 "{ \"root\": {
818 \"a\": {
819 \"double\": 2.5,
820 \"histogram\": {
821 \"floor\": 0.0,
822 \"initial_step\": 2.0,
823 \"step_multiplier\": 4.0,
824 \"counts\": [1.0, 3.0, 4.0, 7.0],
825 \"size\": 4
826 }
827 },
828 \"array\": [
829 0,
830 2,
831 18446744073709551615
832 ],
833 \"string_list\": [\"foo\", \"bar\"],
834 \"empty_string_list\": [],
835 \"b\": {
836 \"histogram\": {
837 \"floor\": 0,
838 \"step\": 2,
839 \"counts\": [4, 1, 3],
840 \"size\": 3
841 },
842 \"int\": -2,
843 \"string\": \"some value\"
844 },
845 \"bool_false\": false,
846 \"bool_true\": true
847 }}"
848 .to_string()
849 }
850}