Browse Source

Merge branch 'upstream' into router-typesafe

Evan Almloff 2 years ago
parent
commit
92755a381d

+ 1 - 1
Cargo.toml

@@ -72,7 +72,7 @@ tokio = { version = "1.16.1", features = ["full"] }
 reqwest = { version = "0.11.9", features = ["json"] }
 fern = { version = "0.6.0", features = ["colored"] }
 thiserror = "1.0.30"
-env_logger = "0.9.0"
+env_logger = "0.10.0"
 simple_logger = "4.0.0"
 
 [profile.release]

+ 63 - 0
docs/guide/examples/hello_world_ssr.rs

@@ -0,0 +1,63 @@
+#![allow(unused)]
+#![allow(non_snake_case)]
+// ANCHOR: all
+
+// ANCHOR: main
+#![allow(non_snake_case)]
+use axum::{response::Html, routing::get, Router};
+// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
+use dioxus::prelude::*;
+
+#[tokio::main]
+async fn main() {
+    let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
+    println!("listening on http://{}", addr);
+
+    axum::Server::bind(&addr)
+        .serve(
+            Router::new()
+                .route("/", get(app_endpoint))
+                .into_make_service(),
+        )
+        .await
+        .unwrap();
+}
+
+// ANCHOR_END: main
+
+// ANCHOR: endpoint
+async fn app_endpoint() -> Html<String> {
+    // render the rsx! macro to HTML
+    Html(dioxus_ssr::render_lazy(rsx! {
+        div { "hello world!" }
+    }))
+}
+// ANCHOR_END: endpoint
+
+// ANCHOR: second_endpoint
+async fn second_app_endpoint() -> Html<String> {
+    // create a component that renders a div with the text "hello world"
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx!(div { "hello world" }))
+    }
+    // create a VirtualDom with the app component
+    let mut app = VirtualDom::new(app);
+    // rebuild the VirtualDom before rendering
+    let _ = app.rebuild();
+
+    // render the VirtualDom to HTML
+    Html(dioxus_ssr::render(&app))
+}
+// ANCHOR_END: second_endpoint
+
+// ANCHOR: component
+// define a component that renders a div with the text "Hello, world!"
+fn App(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div {
+            "Hello, world!"
+        }
+    })
+}
+// ANCHOR_END: component
+// ANCHOR_END: all

+ 12 - 36
docs/guide/src/en/getting_started/ssr.md

@@ -40,54 +40,30 @@ tokio = { version = "1.15.0", features = ["full"] }
 Now, set up your Axum app to respond on an endpoint.
 
 ```rust
-use axum::{response::Html, routing::get, Router};
-use dioxus::prelude::*;
-
-#[tokio::main]
-async fn main() {
-    let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
-    println!("listening on http://{}", addr);
-
-    axum::Server::bind(&addr)
-        .serve(
-            Router::new()
-                .route("/", get(app_endpoint))
-                .into_make_service(),
-        )
-        .await
-        .unwrap();
-}
+{{#include ../../../examples/hello_world_ssr.rs:main}}
 ```
 
 And then add our endpoint. We can either render `rsx!` directly:
 
 ```rust
-async fn app_endpoint() -> Html<String> {
-    // render the rsx! macro to HTML
-    Html(dioxus_ssr::render_lazy(rsx! {
-        div { "hello world!" }
-    }))
-}
+{{#include ../../../examples/hello_world_ssr.rs:endpoint}}
 ```
 
 Or we can render VirtualDoms.
 
 ```rust
-async fn app_endpoint() -> Html<String> {
-    // create a component that renders a div with the text "hello world"
-    fn app(cx: Scope) -> Element {
-        cx.render(rsx!(div { "hello world" }))
-    }
-    // create a VirtualDom with the app component
-    let mut app = VirtualDom::new(app);
-    // rebuild the VirtualDom before rendering
-    let _ = app.rebuild();
-
-    // render the VirtualDom to HTML
-    Html(dioxus_ssr::render_vdom(&app))
-}
+{{#include ../../../examples/hello_world_ssr.rs:second_endpoint}}
 ```
 
+And then add our app component:
+
+```rust
+{{#include ../../../examples/hello_world_ssr.rs:component}}
+```
+
+And that's it!
+
+
 ## Multithreaded Support
 
 The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe.

+ 1 - 1
docs/guide/src/en/interactivity/hooks.md

@@ -44,7 +44,7 @@ This is only possible because the two hooks are always called in the same order,
 
 1. Hooks may be only used in components or other hooks (we'll get to that later)
 2. On every call to the component function
-   1. The same hooks must be called
+   1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
    2. In the same order
 3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
 

+ 13 - 0
examples/web_component.rs

@@ -0,0 +1,13 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! {
+        web-component {
+            "my-prop": "5%",
+        }
+    })
+}

+ 13 - 0
notes/CONTRIBUTING.md

@@ -0,0 +1,13 @@
+# Contributing
+
+On Linux, install the following packages:
+
+```bash
+sudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev
+```
+
+Then run:
+
+```bash
+cargo test --workspace --tests
+```

+ 1 - 0
packages/desktop/Cargo.toml

@@ -38,6 +38,7 @@ slab = "0.4"
 
 futures-util = "0.3.25"
 rfd = "0.11.3"
+urlencoding = "2.1.2"
 
 [target.'cfg(target_os = "ios")'.dependencies]
 objc = "0.2.7"

+ 4 - 2
packages/desktop/src/protocol.rs

@@ -43,7 +43,7 @@ fn module_loader(root_name: &str) -> String {
     let rootname = "{root_name}";
     let root = window.document.getElementById(rootname);
     if (root != null) {{
-        window.interpreter = new Interpreter(root);
+        window.interpreter = new Interpreter(root, new InterpreterConfig(true));
         window.ipc.postMessage(serializeIpcMessage("initialize"));
     }}
 </script>
@@ -87,7 +87,9 @@ pub(super) fn desktop_handler(
     }
 
     // Else, try to serve a file from the filesystem.
-    let path = PathBuf::from(request.uri().path().trim_start_matches('/'));
+    let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/'))
+        .expect("expected URL to be UTF-8 encoded");
+    let path = PathBuf::from(&*decoded);
 
     // If the path is relative, we'll try to serve it from the assets directory.
     let mut asset = get_asset_root()

+ 2 - 9
packages/desktop/src/shortcut.rs

@@ -41,17 +41,10 @@ impl Shortcut {
 
 impl ShortcutRegistry {
     pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
-        let myself = Self {
+        Self {
             manager: Rc::new(RefCell::new(ShortcutManager::new(target))),
             shortcuts: Rc::new(RefCell::new(HashMap::new())),
-        };
-        // prevent CTRL+R from reloading the page which breaks apps
-        let _ = myself.add_shortcut(
-            Some(ModifiersState::CONTROL),
-            KeyCode::KeyR,
-            Box::new(|| {}),
-        );
-        myself
+        }
     }
 
     pub(crate) fn call_handlers(&self, id: AcceleratorId) {

+ 1 - 1
packages/dioxus/Cargo.toml

@@ -35,7 +35,7 @@ log = "0.4.14"
 rand = { version = "0.8.4", features = ["small_rng"] }
 criterion = "0.3.5"
 thiserror = "1.0.30"
-env_logger = "0.9.0"
+env_logger = "0.10.0"
 tokio = { version = "1.21.2", features = ["full"] }
 # dioxus-edit-stream = { path = "../edit-stream" }
 

+ 6 - 0
packages/fermi/src/hooks/atom_ref.rs

@@ -70,6 +70,12 @@ impl<T: 'static> UseAtomRef<T> {
         self.value.borrow()
     }
 
+    /// This is silent operation
+    /// call `.force_update()` manually if required
+    pub fn with_mut_silent(&self, cb: impl FnOnce(&mut T)) {
+        cb(&mut *self.write_silent())
+    }
+
     pub fn write(&self) -> RefMut<T> {
         self.root.force_update(self.ptr);
         self.value.borrow_mut()

+ 4 - 2
packages/fullstack/Cargo.toml

@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"]
 
 [dependencies]
 # server functions
-server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", features = ["stable"] }
+server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", default-features = false, features = ["stable"] }
 dioxus_server_macro = { path = "server-macro" }
 
 # warp
@@ -56,9 +56,11 @@ dioxus-hot-reload = { path = "../hot-reload" }
 web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
 
 [features]
-default = ["hot-reload"]
+default = ["hot-reload", "default-tls"]
 hot-reload = ["serde_json", "tokio-stream", "futures-util"]
 warp = ["dep:warp", "http-body", "ssr"]
 axum = ["dep:axum", "tower-http", "ssr"]
 salvo = ["dep:salvo", "ssr"]
 ssr = ["server_fn/ssr", "tokio", "dioxus-ssr", "hyper", "http"]
+default-tls = ["server_fn/default-tls"]
+rustls = ["server_fn/rustls"]

+ 1 - 1
packages/hot-reload/Cargo.toml

@@ -18,7 +18,7 @@ dioxus-html = { path = "../html", features = ["hot-reload-context"], version = "
 
 interprocess = { version = "1.2.1" }
 notify = "5.0.0"
-chrono = "0.4.23"
+chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
 serde_json = "1.0.91"
 serde = { version = "1", features = ["derive"] }
 execute = "0.2.11"

+ 5 - 1
packages/interpreter/src/bindings.rs

@@ -6,10 +6,14 @@ use web_sys::Element;
 
 #[wasm_bindgen(module = "/src/interpreter.js")]
 extern "C" {
+    pub type InterpreterConfig;
+    #[wasm_bindgen(constructor)]
+    pub fn new(intercept_link_redirects: bool) -> InterpreterConfig;
+
     pub type Interpreter;
 
     #[wasm_bindgen(constructor)]
-    pub fn new(arg: Element) -> Interpreter;
+    pub fn new(arg: Element, config: InterpreterConfig) -> Interpreter;
 
     #[wasm_bindgen(method)]
     pub fn SaveTemplate(this: &Interpreter, template: JsValue);

+ 31 - 22
packages/interpreter/src/interpreter.js

@@ -53,8 +53,15 @@ class ListenerMap {
   }
 }
 
+class InterpreterConfig {
+  constructor(intercept_link_redirects) {
+    this.intercept_link_redirects = intercept_link_redirects;
+  }
+}
+
 class Interpreter {
-  constructor(root) {
+  constructor(root, config) {
+    this.config = config;
     this.root = root;
     this.listeners = new ListenerMap(root);
     this.nodes = [root];
@@ -397,7 +404,7 @@ class Interpreter {
           );
         } else {
           this.NewEventListener(edit.name, edit.id, bubbles, (event) => {
-            handler(event, edit.name, bubbles);
+            handler(event, edit.name, bubbles, this.config);
           });
         }
         break;
@@ -407,32 +414,34 @@ class Interpreter {
 
 // this handler is only provided on the desktop and liveview implementations since this
 // method is not used by the web implementation
-function handler(event, name, bubbles) {
+function handler(event, name, bubbles, config) {
   let target = event.target;
   if (target != null) {
     let preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
 
     if (event.type === "click") {
       // todo call prevent default if it's the right type of event
-      let a_element = target.closest("a");
-      if (a_element != null) {
-        event.preventDefault();
-
-        let elementShouldPreventDefault =
-          preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
-        let aElementShouldPreventDefault = a_element.getAttribute(
-          `dioxus-prevent-default`
-        );
-        let linkShouldPreventDefault =
-          aElementShouldPreventDefault &&
-          aElementShouldPreventDefault.includes(`onclick`);
-
-        if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
-          const href = a_element.getAttribute("href");
-          if (href !== "" && href !== null && href !== undefined) {
-            window.ipc.postMessage(
-              serializeIpcMessage("browser_open", { href })
-            );
+      if (config.intercept_link_redirects) {
+        let a_element = target.closest("a");
+        if (a_element != null) {
+          event.preventDefault();
+
+          let elementShouldPreventDefault =
+            preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
+          let aElementShouldPreventDefault = a_element.getAttribute(
+            `dioxus-prevent-default`
+          );
+          let linkShouldPreventDefault =
+            aElementShouldPreventDefault &&
+            aElementShouldPreventDefault.includes(`onclick`);
+
+          if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
+            const href = a_element.getAttribute("href");
+            if (href !== "" && href !== null && href !== undefined) {
+              window.ipc.postMessage(
+                serializeIpcMessage("browser_open", { href })
+              );
+            }
           }
         }
       }

+ 1 - 1
packages/liveview/Cargo.toml

@@ -46,7 +46,7 @@ once_cell = "1.17.1"
 # actix-ws = { version = "0.2.5", optional = true }
 
 [dev-dependencies]
-pretty_env_logger = { version = "0.4.0" }
+pretty_env_logger = { version = "0.5.0" }
 tokio = { version = "1.22.0", features = ["full"] }
 dioxus = { path = "../dioxus", version = "0.3.0" }
 warp = "0.3.3"

+ 1 - 1
packages/liveview/src/main.js

@@ -8,7 +8,7 @@ function main() {
 class IPC {
   constructor(root) {
     // connect to the websocket
-    window.interpreter = new Interpreter(root);
+    window.interpreter = new Interpreter(root, new InterpreterConfig(false));
 
     let ws = new WebSocket(WS_ADDR);
 

+ 2 - 2
packages/rsx-rosetta/src/lib.rs

@@ -1,6 +1,6 @@
 use convert_case::{Case, Casing};
 use dioxus_rsx::{
-    BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, IfmtInput,
+    BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
 };
 pub use html_parser::{Dom, Node};
 use proc_macro2::{Ident, Span};
@@ -21,7 +21,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
         Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
         Node::Element(el) => {
             let el_name = el.name.to_case(Case::Snake);
-            let el_name = Ident::new(el_name.as_str(), Span::call_site());
+            let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site()));
 
             let mut attributes: Vec<_> = el
                 .attributes

+ 109 - 12
packages/rsx/src/element.rs

@@ -1,9 +1,13 @@
+use std::fmt::{Display, Formatter};
+
 use super::*;
 
 use proc_macro2::{Span, TokenStream as TokenStream2};
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     parse::{Parse, ParseBuffer, ParseStream},
+    punctuated::Punctuated,
+    spanned::Spanned,
     Error, Expr, Ident, LitStr, Result, Token,
 };
 
@@ -12,7 +16,7 @@ use syn::{
 // =======================================
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct Element {
-    pub name: Ident,
+    pub name: ElementName,
     pub key: Option<IfmtInput>,
     pub attributes: Vec<ElementAttrNamed>,
     pub children: Vec<BodyNode>,
@@ -22,7 +26,7 @@ pub struct Element {
 
 impl Parse for Element {
     fn parse(stream: ParseStream) -> Result<Self> {
-        let el_name = Ident::parse(stream)?;
+        let el_name = ElementName::parse(stream)?;
 
         // parse the guts
         let content: ParseBuffer;
@@ -181,7 +185,7 @@ impl ToTokens for Element {
 
         tokens.append_all(quote! {
             __cx.element(
-                dioxus_elements::#name,
+                #name,
                 __cx.bump().alloc([ #(#listeners),* ]),
                 __cx.bump().alloc([ #(#attr),* ]),
                 __cx.bump().alloc([ #(#children),* ]),
@@ -191,6 +195,75 @@ impl ToTokens for Element {
     }
 }
 
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
+pub enum ElementName {
+    Ident(Ident),
+    Custom(LitStr),
+}
+
+impl ElementName {
+    pub(crate) fn tag_name(&self) -> TokenStream2 {
+        match self {
+            ElementName::Ident(i) => quote! { dioxus_elements::#i::TAG_NAME },
+            ElementName::Custom(s) => quote! { #s },
+        }
+    }
+}
+
+impl ElementName {
+    pub fn span(&self) -> Span {
+        match self {
+            ElementName::Ident(i) => i.span(),
+            ElementName::Custom(s) => s.span(),
+        }
+    }
+}
+
+impl PartialEq<&str> for ElementName {
+    fn eq(&self, other: &&str) -> bool {
+        match self {
+            ElementName::Ident(i) => i == *other,
+            ElementName::Custom(s) => s.value() == *other,
+        }
+    }
+}
+
+impl Display for ElementName {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ElementName::Ident(i) => write!(f, "{}", i),
+            ElementName::Custom(s) => write!(f, "{}", s.value()),
+        }
+    }
+}
+
+impl Parse for ElementName {
+    fn parse(stream: ParseStream) -> Result<Self> {
+        let raw = Punctuated::<Ident, Token![-]>::parse_separated_nonempty(stream)?;
+        if raw.len() == 1 {
+            Ok(ElementName::Ident(raw.into_iter().next().unwrap()))
+        } else {
+            let span = raw.span();
+            let tag = raw
+                .into_iter()
+                .map(|ident| ident.to_string())
+                .collect::<Vec<_>>()
+                .join("-");
+            let tag = LitStr::new(&tag, span);
+            Ok(ElementName::Custom(tag))
+        }
+    }
+}
+
+impl ToTokens for ElementName {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        match self {
+            ElementName::Ident(i) => tokens.append_all(quote! { dioxus_elements::#i }),
+            ElementName::Custom(s) => tokens.append_all(quote! { #s }),
+        }
+    }
+}
+
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum ElementAttr {
     /// `attribute: "value"`
@@ -234,7 +307,7 @@ impl ElementAttr {
 
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct ElementAttrNamed {
-    pub el_name: Ident,
+    pub el_name: ElementName,
     pub attr: ElementAttr,
 }
 
@@ -242,24 +315,46 @@ impl ToTokens for ElementAttrNamed {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let ElementAttrNamed { el_name, attr } = self;
 
-        tokens.append_all(match attr {
+        let ns = |name| match el_name {
+            ElementName::Ident(i) => quote! { dioxus_elements::#i::#name.1 },
+            ElementName::Custom(_) => quote! { None },
+        };
+        let volitile = |name| match el_name {
+            ElementName::Ident(_) => quote! { #el_name::#name.2 },
+            ElementName::Custom(_) => quote! { false },
+        };
+        let attribute = |name: &Ident| match el_name {
+            ElementName::Ident(_) => quote! { #el_name::#name.0 },
+            ElementName::Custom(_) => {
+                let as_string = name.to_string();
+                quote!(#as_string)
+            }
+        };
+
+        let attribute = match attr {
             ElementAttr::AttrText { name, value } => {
+                let ns = ns(name);
+                let volitile = volitile(name);
+                let attribute = attribute(name);
                 quote! {
                     __cx.attr(
-                        dioxus_elements::#el_name::#name.0,
+                        #attribute,
                         #value,
-                        dioxus_elements::#el_name::#name.1,
-                        dioxus_elements::#el_name::#name.2
+                        #ns,
+                        #volitile
                     )
                 }
             }
             ElementAttr::AttrExpression { name, value } => {
+                let ns = ns(name);
+                let volitile = volitile(name);
+                let attribute = attribute(name);
                 quote! {
                     __cx.attr(
-                        dioxus_elements::#el_name::#name.0,
+                        #attribute,
                         #value,
-                        dioxus_elements::#el_name::#name.1,
-                        dioxus_elements::#el_name::#name.2
+                        #ns,
+                        #volitile
                     )
                 }
             }
@@ -288,7 +383,9 @@ impl ToTokens for ElementAttrNamed {
                     dioxus_elements::events::#name(__cx, #tokens)
                 }
             }
-        });
+        };
+
+        tokens.append_all(attribute);
     }
 }
 

+ 19 - 4
packages/rsx/src/lib.rs

@@ -426,13 +426,25 @@ impl<'a> DynamicContext<'a> {
         match root {
             BodyNode::Element(el) => {
                 let el_name = &el.name;
+                let ns = |name| match el_name {
+                    ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
+                    ElementName::Custom(_) => quote! { None },
+                };
                 let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
                     ElementAttr::AttrText { name, value } if value.is_static() => {
                         let value = value.to_static().unwrap();
+                        let ns = ns(quote!(#name.1));
+                        let name = match el_name {
+                            ElementName::Ident(_) => quote! { #el_name::#name.0 },
+                            ElementName::Custom(_) => {
+                                let as_string = name.to_string();
+                                quote! { #as_string }
+                            }
+                        };
                         quote! {
                             ::dioxus::core::TemplateAttribute::Static {
-                                name: dioxus_elements::#el_name::#name.0,
-                                namespace: dioxus_elements::#el_name::#name.1,
+                                name: #name,
+                                namespace: #ns,
                                 value: #value,
 
                                 // todo: we don't diff these so we never apply the volatile flag
@@ -479,10 +491,13 @@ impl<'a> DynamicContext<'a> {
                 let _opt = el.children.len() == 1;
                 let children = quote! { #(#children),* };
 
+                let ns = ns(quote!(NAME_SPACE));
+                let el_name = el_name.tag_name();
+
                 quote! {
                     ::dioxus::core::TemplateNode::Element {
-                        tag: dioxus_elements::#el_name::TAG_NAME,
-                        namespace: dioxus_elements::#el_name::NAME_SPACE,
+                        tag: #el_name,
+                        namespace: #ns,
                         attrs: &[ #attrs ],
                         children: &[ #children ],
                     }

+ 9 - 0
packages/rsx/src/node.rs

@@ -50,7 +50,16 @@ impl Parse for BodyNode {
             return Ok(BodyNode::Text(stream.parse()?));
         }
 
+        // if this is a dash-separated path, it's a web component (custom element)
         let body_stream = stream.fork();
+        if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
+            if name.value().contains('-') && body_stream.peek(token::Brace) {
+                return Ok(BodyNode::Element(stream.parse::<Element>()?));
+            }
+        }
+
+        let body_stream = stream.fork();
+
         if let Ok(path) = body_stream.parse::<syn::Path>() {
             // this is an Element if path match of:
             // - one ident