1use crate::display::{SuperConsole, renderers::ConsoleOutputFeatures};
4use chrono::prelude::*;
5use fnv::FnvHashMap;
6use serde_json::{Value as JSONValue, json};
7use std::{
8 error::Error as StdError,
9 fmt::{Debug, Display, Formatter, Result as FmtResult},
10 hash::BuildHasherDefault,
11 io::Write as IoWrite,
12 sync::Arc,
13};
14use tokio::runtime::Handle;
15use tracing::{
16 Event, Level, Metadata, Subscriber,
17 field::{Field, Visit},
18 span::{Attributes as SpanAttributes, Id as SpanId},
19 subscriber::Interest,
20};
21use tracing_subscriber::{Layer, layer::Context, registry::LookupSpan};
22use valuable::{
23 Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit as ValuableVisit,
24};
25use valuable_serde::Serializable;
26
27pub struct TracableSuperConsole<
29 StdoutTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
30 StderrTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
31> {
32 console: Arc<SuperConsole<StdoutTy, StderrTy>>,
34}
35
36impl<
37 StdoutTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
38 StderrTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
39> TracableSuperConsole<StdoutTy, StderrTy>
40{
41 #[must_use]
42 pub fn new(console: Arc<SuperConsole<StdoutTy, StderrTy>>) -> Self {
43 Self { console }
44 }
45}
46
47impl<
48 StdoutTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
49 StderrTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
50 SubscriberTy,
51> Layer<SubscriberTy> for TracableSuperConsole<StdoutTy, StderrTy>
52where
53 SubscriberTy: Subscriber + for<'lookup> LookupSpan<'lookup>,
54{
55 fn register_callsite(&self, _metadata: &'static Metadata<'static>) -> Interest {
56 Interest::always()
57 }
58
59 fn on_new_span(&self, attrs: &SpanAttributes<'_>, id: &SpanId, ctx: Context<'_, SubscriberTy>) {
60 let mut span_data = SuperConsoleSpanData::new();
61 span_data.reserve(attrs.fields().len());
62 attrs.record(&mut span_data);
63
64 if let Some(span) = ctx.span(id) {
65 span.extensions_mut().insert(span_data);
66 }
67 }
68
69 fn on_event(&self, event: &Event<'_>, ctxt: Context<'_, SubscriberTy>) {
70 let mut visitor = SuperConsoleLogMessage::new();
71
72 let mut optional_span = ctxt.event_span(event);
73 while let Some(current_span) = optional_span {
74 if let Some(ext) = current_span.extensions().get::<SuperConsoleSpanData>() {
75 visitor.visit_span(ext);
76 }
77 optional_span = current_span.parent();
78 }
79
80 visitor.set_level(*event.metadata().level());
81 visitor.reserve_metadata_space(event.metadata().fields().len());
82 event.record(&mut visitor);
83 if let Ok(h) = Handle::try_current() {
85 tokio::task::block_in_place(move || {
86 h.block_on(async { self.console.log(visitor).await });
87 });
88 } else {
89 _ = self.console.log_sync(visitor);
90 }
91 }
92}
93
94#[allow(
100 clippy::struct_excessive_bools,
103)]
104#[derive(Clone, Debug, PartialEq)]
105pub struct SuperConsoleLogMessage {
106 at: DateTime<Utc>,
108 color: Option<String>,
110 force_combine: bool,
113 hide_fields_for_humans: bool,
116 id: Option<String>,
121 level: Level,
123 subsystem: Option<String>,
125 should_decorate: bool,
127 target_stdout: bool,
129 message: Option<String>,
131 metadata: FnvHashMap<&'static str, FlattenedTracingField>,
133}
134
135impl SuperConsoleLogMessage {
136 #[must_use]
138 pub(self) fn new() -> Self {
139 Self {
140 at: Utc::now(),
141 color: None,
142 id: None,
143 force_combine: false,
144 hide_fields_for_humans: false,
145 level: Level::INFO,
146 subsystem: None,
147 should_decorate: true,
148 target_stdout: false,
149 message: None,
150 metadata: FnvHashMap::with_capacity_and_hasher(0, BuildHasherDefault::default()),
151 }
152 }
153
154 pub(self) fn reserve_metadata_space(&mut self, additional: usize) {
156 self.metadata.reserve(additional);
157 }
158
159 #[must_use]
161 pub const fn at(&self) -> &DateTime<Utc> {
162 &self.at
163 }
164
165 #[must_use]
168 pub fn color(&self) -> Option<&str> {
169 self.color.as_deref()
170 }
171
172 #[must_use]
174 pub const fn should_hide_fields_for_humans(&self) -> bool {
175 self.hide_fields_for_humans
176 }
177
178 #[must_use]
183 pub fn id(&self) -> Option<&str> {
184 self.id.as_deref()
185 }
186
187 #[must_use]
189 pub const fn force_combine(&self) -> bool {
190 self.force_combine
191 }
192
193 #[must_use]
195 pub fn subsytem(&self) -> Option<&str> {
196 self.subsystem.as_deref()
197 }
198
199 #[must_use]
201 pub fn message(&self) -> Option<&str> {
202 self.message.as_deref()
203 }
204
205 #[must_use]
207 pub const fn level(&self) -> &Level {
208 &self.level
209 }
210
211 #[must_use]
212 pub const fn should_decorate(&self) -> bool {
213 self.should_decorate
214 }
215
216 #[must_use]
217 pub const fn towards_stdout(&self) -> bool {
218 self.target_stdout
219 }
220
221 #[must_use]
226 pub const fn metadata(&self) -> &FnvHashMap<&'static str, FlattenedTracingField> {
227 &self.metadata
228 }
229
230 pub(self) fn set_level(&mut self, level: Level) {
232 self.level = level;
233 }
234
235 pub(self) fn visit_span(&mut self, ext: &SuperConsoleSpanData) {
241 for (key, value) in ext.fields() {
242 match *key {
243 "id" => {
244 if self.id.is_none() {
245 _ = self.id.insert(Self::get_field_as_string(value));
246 }
247 }
248 "lisa.force_combine_fields" => {
249 self.force_combine = Self::get_field_as_bool(value);
250 }
251 "lisa.hide_fields_for_humans" => {
252 self.hide_fields_for_humans = Self::get_field_as_bool(value);
253 }
254 "lisa.subsystem" => {
255 if self.subsystem.is_none() {
256 _ = self.subsystem.insert(Self::get_field_as_string(value));
257 }
258 }
259 "lisa.stdout" => {
260 self.target_stdout = Self::get_field_as_bool(value);
261 }
262 "lisa.decorate" => {
263 self.should_decorate = Self::get_field_as_bool(value);
264 }
265 key_value => {
266 if !self.metadata.contains_key(key_value) {
267 self.metadata.insert(key_value, value.clone());
268 }
269 }
270 }
271 }
272 }
273
274 fn get_field_as_bool(value: &FlattenedTracingField) -> bool {
276 match value {
277 FlattenedTracingField::Null => false,
278 FlattenedTracingField::Boolean(val) => *val,
279 FlattenedTracingField::Bytes(val) => !val.is_empty() && val[0] > 0,
280 FlattenedTracingField::Float(val) => *val > 0.0,
281 FlattenedTracingField::Int(val) => *val > 0,
282 FlattenedTracingField::IntLarge(val) => *val > 0,
283 FlattenedTracingField::Str(val) => val.eq_ignore_ascii_case("true") || val == "1",
284 FlattenedTracingField::UnsignedInt(val) => *val > 0,
285 FlattenedTracingField::UnsignedIntLarge(val) => *val > 0,
286 FlattenedTracingField::List(val) => !val.is_empty(),
287 FlattenedTracingField::Object(val) => !val.is_empty(),
288 }
289 }
290
291 fn get_field_as_string(value: &FlattenedTracingField) -> String {
293 match value {
294 FlattenedTracingField::Null => String::with_capacity(0),
295 FlattenedTracingField::Boolean(val) => format!("{val}"),
296 FlattenedTracingField::Float(val) => format!("{val}"),
297 FlattenedTracingField::Int(val) => format!("{val}"),
298 FlattenedTracingField::IntLarge(val) => format!("{val}"),
299 FlattenedTracingField::UnsignedInt(val) => format!("{val}"),
300 FlattenedTracingField::UnsignedIntLarge(val) => format!("{val}"),
301 FlattenedTracingField::Str(val) => val.clone(),
302 FlattenedTracingField::Bytes(val) => format!("{val:02x?}"),
303 FlattenedTracingField::List(_) | FlattenedTracingField::Object(_) => {
304 format!("{value}")
305 }
306 }
307 }
308
309 fn do_field_visit(&mut self, field: &Field, value: FlattenedTracingField) {
310 match field.name() {
311 "id" => {
312 _ = self.id.insert(Self::get_field_as_string(&value));
313 }
314 "lisa.subsystem" => {
315 _ = self.subsystem.insert(Self::get_field_as_string(&value));
316 }
317 "lisa.stdout" => {
318 self.target_stdout = Self::get_field_as_bool(&value);
319 }
320 "lisa.decorate" => {
321 self.should_decorate = Self::get_field_as_bool(&value);
322 }
323 "lisa.force_combine_fields" => {
324 self.force_combine = Self::get_field_as_bool(&value);
325 }
326 "lisa.hide_fields_for_humans" => {
327 self.hide_fields_for_humans = Self::get_field_as_bool(&value);
328 }
329 "message" => {
330 _ = self.message.insert(Self::get_field_as_string(&value));
331 }
332 key_value => {
333 self.metadata.insert(key_value, value);
334 }
335 }
336 }
337}
338
339impl Visit for SuperConsoleLogMessage {
340 fn record_f64(&mut self, field: &Field, value: f64) {
341 self.do_field_visit(field, FlattenedTracingField::Float(value));
342 }
343
344 fn record_i64(&mut self, field: &Field, value: i64) {
345 self.do_field_visit(field, FlattenedTracingField::Int(value));
346 }
347
348 fn record_u64(&mut self, field: &Field, value: u64) {
349 self.do_field_visit(field, FlattenedTracingField::UnsignedInt(value));
350 }
351
352 fn record_i128(&mut self, field: &Field, value: i128) {
353 self.do_field_visit(field, FlattenedTracingField::IntLarge(value));
354 }
355
356 fn record_u128(&mut self, field: &Field, value: u128) {
357 self.do_field_visit(field, FlattenedTracingField::UnsignedIntLarge(value));
358 }
359
360 fn record_bool(&mut self, field: &Field, value: bool) {
361 self.do_field_visit(field, FlattenedTracingField::Boolean(value));
362 }
363
364 fn record_str(&mut self, field: &Field, value: &str) {
365 self.do_field_visit(field, FlattenedTracingField::Str(value.to_owned()));
366 }
367
368 fn record_bytes(&mut self, field: &Field, value: &[u8]) {
369 self.do_field_visit(field, FlattenedTracingField::Bytes(Vec::from(value)));
370 }
371
372 fn record_error(&mut self, field: &Field, value: &(dyn StdError + 'static)) {
373 self.do_field_visit(field, FlattenedTracingField::Str(format!("{value}")));
374 }
375
376 fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
377 self.do_field_visit(field, FlattenedTracingField::Str(format!("{value:?}")));
378 }
379
380 fn record_value(&mut self, field: &Field, value: Value<'_>) {
381 self.do_field_visit(field, valuable_to_flattened(value));
382 }
383}
384
385const SUPER_CONSOLE_LOG_MESSAGE_FIELDS: &[NamedField<'static>] = &[
386 NamedField::new("id"),
387 NamedField::new("level"),
388 NamedField::new("subsystem"),
389 NamedField::new("should_decorate"),
390 NamedField::new("target_stdout"),
391 NamedField::new("metadata"),
392];
393
394impl Structable for SuperConsoleLogMessage {
395 fn definition(&self) -> StructDef<'_> {
396 StructDef::new_static(
397 "SuperConsoleLogMessage",
398 Fields::Named(SUPER_CONSOLE_LOG_MESSAGE_FIELDS),
399 )
400 }
401}
402
403impl Valuable for SuperConsoleLogMessage {
404 fn as_value(&self) -> Value<'_> {
405 Value::Structable(self)
406 }
407
408 fn visit(&self, visitor: &mut dyn ValuableVisit) {
409 visitor.visit_named_fields(&NamedValues::new(
410 SUPER_CONSOLE_LOG_MESSAGE_FIELDS,
411 &[
412 Valuable::as_value(&self.id),
413 Valuable::as_value(&format!("{}", self.level())),
414 Valuable::as_value(&self.subsystem),
415 Valuable::as_value(&self.should_decorate),
416 Valuable::as_value(&self.target_stdout),
417 Valuable::as_value(&self.metadata),
418 ],
419 ));
420 }
421}
422
423#[derive(Clone, PartialEq, Valuable)]
436pub enum FlattenedTracingField {
437 Null,
438 Float(f64),
439 Int(i64),
440 UnsignedInt(u64),
441 IntLarge(i128),
442 UnsignedIntLarge(u128),
443 Boolean(bool),
444 Str(String),
445 Bytes(Vec<u8>),
446 List(Vec<FlattenedTracingField>),
447 Object(FnvHashMap<String, FlattenedTracingField>),
448}
449
450impl FlattenedTracingField {
451 #[must_use]
453 pub fn field_count(&self) -> usize {
454 match self {
455 FlattenedTracingField::Boolean(_)
456 | FlattenedTracingField::Bytes(_)
457 | FlattenedTracingField::Float(_)
458 | FlattenedTracingField::Int(_)
459 | FlattenedTracingField::IntLarge(_)
460 | FlattenedTracingField::Null
461 | FlattenedTracingField::Str(_)
462 | FlattenedTracingField::UnsignedInt(_)
463 | FlattenedTracingField::UnsignedIntLarge(_) => 1,
464 FlattenedTracingField::List(list) => {
465 list.iter().map(FlattenedTracingField::field_count).sum()
466 }
467 FlattenedTracingField::Object(obj) => {
468 obj.values().map(FlattenedTracingField::field_count).sum()
469 }
470 }
471 }
472}
473
474impl Display for FlattenedTracingField {
475 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
476 match self {
477 Self::Null => write!(fmt, ""),
478 Self::Boolean(value) => write!(fmt, "{value}"),
479 Self::Bytes(value) => write!(fmt, "{value:02x?}"),
480 Self::Float(value) => write!(fmt, "{value}"),
481 Self::Int(value) => write!(fmt, "{value}"),
482 Self::IntLarge(value) => write!(fmt, "{value}"),
483 Self::Str(value) => write!(fmt, "{value}"),
484 Self::UnsignedInt(value) => write!(fmt, "{value}"),
485 Self::UnsignedIntLarge(value) => write!(fmt, "{value}"),
486 Self::List(value) => {
487 for (idx, val) in value.iter().enumerate() {
488 if idx != 0 {
489 write!(fmt, ",")?;
490 }
491 write!(fmt, "{val}")?;
492 }
493 Ok(())
494 }
495 Self::Object(value) => {
496 write!(fmt, "{{")?;
497 let mut written_once = false;
498 for (key, val) in value {
499 if written_once {
500 write!(fmt, ",")?;
501 }
502 written_once = true;
503 write!(fmt, "{key}={val}")?;
504 }
505 write!(fmt, "}}")
506 }
507 }
508 }
509}
510
511impl Debug for FlattenedTracingField {
512 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
513 match self {
514 Self::Null => write!(fmt, ""),
515 Self::Boolean(value) => write!(fmt, "{value:?}"),
516 Self::Bytes(value) => write!(fmt, "{value:02x?}"),
517 Self::Float(value) => write!(fmt, "{value:?}"),
518 Self::Int(value) => write!(fmt, "{value:?}"),
519 Self::IntLarge(value) => write!(fmt, "{value:?}"),
520 Self::Str(value) => write!(fmt, "{value:?}"),
521 Self::UnsignedInt(value) => write!(fmt, "{value:?}"),
522 Self::UnsignedIntLarge(value) => write!(fmt, "{value:?}"),
523 Self::List(value) => {
524 let mut has_written = false;
525 for val in value {
526 if has_written {
527 write!(fmt, ",")?;
528 } else {
529 has_written = true;
530 }
531 write!(fmt, "{val:?}")?;
532 }
533 Ok(())
534 }
535 Self::Object(value) => {
536 write!(fmt, "{{")?;
537 let mut written_once = false;
538 for (key, val) in value {
539 if written_once {
540 write!(fmt, ",")?;
541 }
542 written_once = true;
543 write!(fmt, "{key}={val:?}")?;
544 }
545 write!(fmt, "}}")
546 }
547 }
548 }
549}
550
551#[derive(Clone, Debug, PartialEq, Valuable)]
559struct SuperConsoleSpanData {
560 fields: FnvHashMap<&'static str, FlattenedTracingField>,
562}
563
564impl SuperConsoleSpanData {
565 #[must_use]
566 pub fn new() -> Self {
567 Self {
568 fields: FnvHashMap::with_capacity_and_hasher(0, BuildHasherDefault::default()),
569 }
570 }
571
572 pub fn reserve(&mut self, additional: usize) {
574 self.fields.reserve(additional);
575 }
576
577 #[must_use]
578 pub fn fields(&self) -> &FnvHashMap<&'static str, FlattenedTracingField> {
579 &self.fields
580 }
581}
582
583impl Visit for SuperConsoleSpanData {
584 fn record_f64(&mut self, field: &Field, value: f64) {
585 self.fields
586 .insert(field.name(), FlattenedTracingField::Float(value));
587 }
588
589 fn record_i64(&mut self, field: &Field, value: i64) {
590 self.fields
591 .insert(field.name(), FlattenedTracingField::Int(value));
592 }
593
594 fn record_u64(&mut self, field: &Field, value: u64) {
595 self.fields
596 .insert(field.name(), FlattenedTracingField::UnsignedInt(value));
597 }
598
599 fn record_i128(&mut self, field: &Field, value: i128) {
600 self.fields
601 .insert(field.name(), FlattenedTracingField::IntLarge(value));
602 }
603
604 fn record_u128(&mut self, field: &Field, value: u128) {
605 self.fields
606 .insert(field.name(), FlattenedTracingField::UnsignedIntLarge(value));
607 }
608
609 fn record_bool(&mut self, field: &Field, value: bool) {
610 self.fields
611 .insert(field.name(), FlattenedTracingField::Boolean(value));
612 }
613
614 fn record_str(&mut self, field: &Field, value: &str) {
615 self.fields
616 .insert(field.name(), FlattenedTracingField::Str(value.to_owned()));
617 }
618
619 fn record_bytes(&mut self, field: &Field, value: &[u8]) {
620 self.fields
621 .insert(field.name(), FlattenedTracingField::Bytes(Vec::from(value)));
622 }
623
624 fn record_error(&mut self, field: &Field, value: &(dyn StdError + 'static)) {
625 self.fields
626 .insert(field.name(), FlattenedTracingField::Str(format!("{value}")));
627 }
628
629 fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
630 self.fields.insert(
631 field.name(),
632 FlattenedTracingField::Str(format!("{value:?}")),
633 );
634 }
635
636 fn record_value(&mut self, field: &Field, value: Value<'_>) {
637 self.fields
638 .insert(field.name(), valuable_to_flattened(value));
639 }
640}
641
642#[must_use]
648fn valuable_to_flattened(value: Value<'_>) -> FlattenedTracingField {
649 json_value_to_flattened(json!(Serializable::new(value)))
650}
651
652#[must_use]
654fn json_value_to_flattened(as_json_value: JSONValue) -> FlattenedTracingField {
655 match as_json_value {
656 JSONValue::Null => FlattenedTracingField::Null,
657 JSONValue::Bool(val) => FlattenedTracingField::Boolean(val),
658 JSONValue::Number(val) => {
659 if let Some(float) = val.as_f64() {
660 FlattenedTracingField::Float(float)
661 } else if let Some(uint) = val.as_u64() {
662 FlattenedTracingField::UnsignedInt(uint)
663 } else if let Some(int) = val.as_i64() {
664 FlattenedTracingField::Int(int)
665 } else if let Some(uint) = val.as_u128() {
666 FlattenedTracingField::UnsignedIntLarge(uint)
667 } else if let Some(int) = val.as_i128() {
668 FlattenedTracingField::IntLarge(int)
669 } else {
670 FlattenedTracingField::Null
671 }
672 }
673 JSONValue::String(val) => FlattenedTracingField::Str(val.clone()),
674 JSONValue::Array(val) => {
675 let mut list = Vec::with_capacity(val.len());
676 for item in val {
677 list.push(json_value_to_flattened(item));
678 }
679 FlattenedTracingField::List(list)
680 }
681 JSONValue::Object(val) => {
682 let mut obj =
683 FnvHashMap::with_capacity_and_hasher(val.len(), BuildHasherDefault::default());
684 for (key, value) in val {
685 obj.insert(key, json_value_to_flattened(value));
686 }
687 FlattenedTracingField::Object(obj)
688 }
689 }
690}
691
692#[cfg(test)]
693mod unit_tests {
694 use super::*;
695 use serde_json::{Value as JSONValue, json};
696
697 #[test]
698 pub fn super_console_span_data_reservation() {
699 let mut span = SuperConsoleSpanData::new();
700 assert_eq!(
701 span.fields().capacity(),
702 0,
703 "SuperConsoleSpanData new should start with a 0 capacity item!",
704 );
705 span.reserve(10);
706 assert!(
707 span.fields().capacity() >= 10,
708 "SuperConsoleSpanData did not our first 10 items: {}",
709 span.fields.capacity(),
710 );
711 }
712
713 #[test]
714 pub fn valuable_to_flattened_simple_conversion() {
715 #[derive(Valuable)]
716 struct InnerObject {
717 array: Vec<u64>,
718 }
719 #[derive(Valuable)]
720 struct SimpleObject {
721 null: Option<bool>,
722 bool: bool,
723 real: u64,
724 float: f64,
725 str: String,
726 array: Vec<String>,
727 inner_obj: InnerObject,
728 }
729
730 let serializable = SimpleObject {
731 null: None,
732 bool: true,
733 real: 10,
734 float: 10.5,
735 str: "hello".to_owned(),
736 array: vec!["oh".to_owned(), "hello".to_owned(), "there".to_owned()],
737 inner_obj: InnerObject {
738 array: vec![10, 11, 12],
739 },
740 };
741
742 let mut global_object = FnvHashMap::default();
743 global_object.insert("null".to_owned(), FlattenedTracingField::Null);
744 global_object.insert("bool".to_owned(), FlattenedTracingField::Boolean(true));
745 global_object.insert("real".to_owned(), FlattenedTracingField::Float(10.0));
746 global_object.insert("float".to_owned(), FlattenedTracingField::Float(10.5));
747 global_object.insert(
748 "str".to_owned(),
749 FlattenedTracingField::Str("hello".to_owned()),
750 );
751 global_object.insert(
752 "array".to_owned(),
753 FlattenedTracingField::List(vec![
754 FlattenedTracingField::Str("oh".to_owned()),
755 FlattenedTracingField::Str("hello".to_owned()),
756 FlattenedTracingField::Str("there".to_owned()),
757 ]),
758 );
759
760 let mut inner_object = FnvHashMap::default();
761 inner_object.insert(
762 "array".to_owned(),
763 FlattenedTracingField::List(vec![
764 FlattenedTracingField::Float(10.0),
765 FlattenedTracingField::Float(11.0),
766 FlattenedTracingField::Float(12.0),
767 ]),
768 );
769 global_object.insert(
770 "inner_obj".to_owned(),
771 FlattenedTracingField::Object(inner_object),
772 );
773
774 assert_eq!(
775 valuable_to_flattened(serializable.as_value()),
776 FlattenedTracingField::Object(global_object),
777 "Valuable to flattened differed: {:?}",
778 valuable_to_flattened(serializable.as_value()),
779 );
780 }
781
782 #[test]
787 pub fn json_to_flattened_simple_conversion() {
788 let json_nul = JSONValue::Null;
789 let json_bool = json!(true);
790 let json_real = json!(100);
791 let json_flot = json!(10.5);
792 let json_strg = json!("hello world!");
793 let json_arry = json!(["oh no", "why did", "we do this"]);
794 let json_objc = json!({
795 "a": false,
796 "b": 10,
797 "c": 10.2,
798 "d": "hello world",
799 "e": ["a", "b", "c"],
800 "f": {
801 "g": [10, 11, 12],
802 },
803 });
804
805 assert_eq!(
806 json_value_to_flattened(json_nul.clone()),
807 FlattenedTracingField::Null,
808 "Converting JSON NULL did not produce FTF Null: {}",
809 json_value_to_flattened(json_nul),
810 );
811 assert_eq!(
812 json_value_to_flattened(json_bool.clone()),
813 FlattenedTracingField::Boolean(true),
814 "Converting JSON BOOL did not produce FTF Bool: {}",
815 json_value_to_flattened(json_bool),
816 );
817 assert_eq!(
818 json_value_to_flattened(json_real.clone()),
819 FlattenedTracingField::Float(100.0),
820 "Converting JSON REAL did not produce FTF UnsignedInt: {}",
821 json_value_to_flattened(json_real),
822 );
823 assert_eq!(
824 json_value_to_flattened(json_flot.clone()),
825 FlattenedTracingField::Float(10.5),
826 "Converting JSON Float did not produce FTF Float: {}",
827 json_value_to_flattened(json_flot),
828 );
829 assert_eq!(
830 json_value_to_flattened(json_strg.clone()),
831 FlattenedTracingField::Str("hello world!".to_owned()),
832 "Converting JSON String did not produce FTF String: {}",
833 json_value_to_flattened(json_strg),
834 );
835 assert_eq!(
836 json_value_to_flattened(json_arry.clone()),
837 FlattenedTracingField::List(vec![
838 FlattenedTracingField::Str("oh no".to_owned()),
839 FlattenedTracingField::Str("why did".to_owned()),
840 FlattenedTracingField::Str("we do this".to_owned()),
841 ]),
842 "Converting JSON List did not produce FTF List: {}",
843 json_value_to_flattened(json_arry),
844 );
845
846 let mut f_object = FnvHashMap::default();
847 f_object.insert(
848 "g".to_owned(),
849 FlattenedTracingField::List(vec![
850 FlattenedTracingField::Float(10.0),
851 FlattenedTracingField::Float(11.0),
852 FlattenedTracingField::Float(12.0),
853 ]),
854 );
855 let mut global_object = FnvHashMap::default();
856 global_object.insert("a".to_owned(), FlattenedTracingField::Boolean(false));
857 global_object.insert("b".to_owned(), FlattenedTracingField::Float(10.0));
858 global_object.insert("c".to_owned(), FlattenedTracingField::Float(10.2));
859 global_object.insert(
860 "d".to_owned(),
861 FlattenedTracingField::Str("hello world".to_owned()),
862 );
863 global_object.insert(
864 "e".to_owned(),
865 FlattenedTracingField::List(vec![
866 FlattenedTracingField::Str("a".to_owned()),
867 FlattenedTracingField::Str("b".to_owned()),
868 FlattenedTracingField::Str("c".to_owned()),
869 ]),
870 );
871 global_object.insert("f".to_owned(), FlattenedTracingField::Object(f_object));
872 assert_eq!(
873 json_value_to_flattened(json_objc.clone()),
874 FlattenedTracingField::Object(global_object),
875 "Converting JSON List did not produce FTF Object: {}",
876 json_value_to_flattened(json_objc),
877 );
878 }
879}