diff --git a/leptos_dom/Cargo.toml b/leptos_dom/Cargo.toml index af8f0786ac..1969e09db7 100644 --- a/leptos_dom/Cargo.toml +++ b/leptos_dom/Cargo.toml @@ -31,6 +31,7 @@ smallvec = "1" tracing = "0.1" wasm-bindgen = { version = "0.2", features = ["enable-interning"] } wasm-bindgen-futures = "0.4.31" +serde = "1" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/leptos_dom/src/macro_helpers/mod.rs b/leptos_dom/src/macro_helpers/mod.rs index 009a41b591..a032f95bad 100644 --- a/leptos_dom/src/macro_helpers/mod.rs +++ b/leptos_dom/src/macro_helpers/mod.rs @@ -2,6 +2,8 @@ mod into_attribute; mod into_class; mod into_property; mod into_style; +#[doc(hidden)] +pub mod tracing_property; pub use into_attribute::*; pub use into_class::*; pub use into_property::*; diff --git a/leptos_dom/src/macro_helpers/tracing_property.rs b/leptos_dom/src/macro_helpers/tracing_property.rs new file mode 100644 index 0000000000..cbe30190e9 --- /dev/null +++ b/leptos_dom/src/macro_helpers/tracing_property.rs @@ -0,0 +1,166 @@ +use wasm_bindgen::UnwrapThrowExt; + +#[macro_export] +/// Use for tracing property +macro_rules! tracing_props { + () => { + ::leptos::leptos_dom::tracing::span!( + ::leptos::leptos_dom::tracing::Level::INFO, + "leptos_dom::tracing_props", + props = String::from("[]") + ); + }; + ($($prop:tt),+ $(,)?) => { + { + use ::leptos::leptos_dom::tracing_property::{Match, SerializeMatch, DefaultMatch}; + let mut props = String::from('['); + $( + let prop = (&&Match { + name: stringify!{$prop}, + value: std::cell::Cell::new(Some(&$prop)) + }).spez(); + props.push_str(&format!("{prop},")); + )* + props.pop(); + props.push(']'); + ::leptos::leptos_dom::tracing::span!( + ::leptos::leptos_dom::tracing::Level::INFO, + "leptos_dom::tracing_props", + props + ); + } + }; +} + +// Implementation based on spez +// see https://github.com/m-ou-se/spez + +pub struct Match { + pub name: &'static str, + pub value: std::cell::Cell>, +} + +pub trait SerializeMatch { + type Return; + fn spez(&self) -> Self::Return; +} +impl SerializeMatch for &Match<&T> { + type Return = String; + fn spez(&self) -> Self::Return { + let name = self.name; + serde_json::to_string(self.value.get().unwrap_throw()).map_or_else( + |err| format!(r#"{{"name": "{name}", "error": "{err}"}}"#), + |value| format!(r#"{{"name": "{name}", "value": {value}}}"#), + ) + } +} + +pub trait DefaultMatch { + type Return; + fn spez(&self) -> Self::Return; +} +impl DefaultMatch for Match<&T> { + type Return = String; + fn spez(&self) -> Self::Return { + let name = self.name; + format!( + r#"{{"name": "{name}", "error": "The trait `serde::Serialize` is not implemented"}}"# + ) + } +} + +#[test] +fn match_primitive() { + // String + let test = String::from("string"); + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!(prop, r#"{"name": "test", "value": "string"}"#); + + // &str + let test = "string"; + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!(prop, r#"{"name": "test", "value": "string"}"#); + + // u128 + let test: u128 = 1; + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!(prop, r#"{"name": "test", "value": 1}"#); + + // i128 + let test: i128 = -1; + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!(prop, r#"{"name": "test", "value": -1}"#); + + // f64 + let test = 3.14; + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!(prop, r#"{"name": "test", "value": 3.14}"#); + + // bool + let test = true; + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!(prop, r#"{"name": "test", "value": true}"#); +} + +#[test] +fn match_serialize() { + use serde::Serialize; + #[derive(Serialize)] + struct CustomStruct { + field: &'static str, + } + + let test = CustomStruct { field: "field" }; + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!(prop, r#"{"name": "test", "value": {"field":"field"}}"#); + // Verification of ownership + assert_eq!(test.field, "field"); +} + +#[test] +fn match_no_serialize() { + struct CustomStruct { + field: &'static str, + } + + let test = CustomStruct { field: "field" }; + let prop = (&&Match { + name: stringify! {test}, + value: std::cell::Cell::new(Some(&test)), + }) + .spez(); + assert_eq!( + prop, + r#"{"name": "test", "error": "The trait `serde::Serialize` is not implemented"}"# + ); + // Verification of ownership + assert_eq!(test.field, "field"); +} diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 15bf44e03b..887657140d 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -178,27 +178,38 @@ impl ToTokens for Model { let component_fn_prop_docs = generate_component_fn_prop_docs(props); - let (tracing_instrument_attr, tracing_span_expr, tracing_guard_expr) = - if cfg!(feature = "tracing") { - ( - quote! { - #[allow(clippy::let_with_type_underscore)] - #[cfg_attr( - any(debug_assertions, feature="ssr"), - ::leptos::leptos_dom::tracing::instrument(level = "info", name = #trace_name, skip_all) - )] - }, - quote! { - let span = ::leptos::leptos_dom::tracing::Span::current(); - }, + let ( + tracing_instrument_attr, + tracing_span_expr, + tracing_guard_expr, + tracing_props_expr, + ) = if cfg!(feature = "tracing") { + ( + quote! { + #[allow(clippy::let_with_type_underscore)] + #[cfg_attr( + any(debug_assertions, feature="ssr"), + ::leptos::leptos_dom::tracing::instrument(level = "info", name = #trace_name, skip_all) + )] + }, + quote! { + let span = ::leptos::leptos_dom::tracing::Span::current(); + }, + quote! { + #[cfg(debug_assertions)] + let _guard = span.entered(); + }, + if no_props { + quote! {} + } else { quote! { - #[cfg(debug_assertions)] - let _guard = span.entered(); - }, - ) - } else { - (quote! {}, quote! {}, quote! {}) - }; + ::leptos::leptos_dom::tracing_props![#prop_names]; + } + }, + ) + } else { + (quote! {}, quote! {}, quote! {}, quote! {}) + }; let component = if *is_transparent { quote! { @@ -211,6 +222,8 @@ impl ToTokens for Model { move |cx| { #tracing_guard_expr + #tracing_props_expr + #body_name(cx, #prop_names) } )