I am trying to render a template with rust rocket and handlebars, this works fine but I can't get any arguments to the html file to work, the page loads, but the argument is not there. I am familiar with flask and jinja for python but not as much with rocket and handlebars. I have tried countless things but I can't find any documentation that works.
main.rs
#![feature(decl_macro)]
extern crate rocket;
extern crate rocket_contrib;
use rocket::{get, routes};
use rocket_contrib::templates::Template;
#[get("/one")]
fn one() -> String {
format!("One")
}
#[get("/page")]
fn page() -> Template {
let context = "string";
Template::render("page", &context)
}
fn main() {
rocket::ignite()
.mount("/", routes![one])
.attach(Template::fairing())
.launch();
}
page.html.hbs
<!DOCTYPE html>
<head></head>
<html>
<body>
<h1>Argument is: {{context}}</h1>
</body>
</html>
The template system doesn't know that the local variable is called context. It just knows that your context is a string, which doesn't have an attribute called context. You need to provide a type implementing Serialize, with a member called context for the template expression {{context}} to expand to anything. You could do this with a HashMap or a custom type.
With a HashMap:
#[get("/page")]
fn page() -> Template {
let mut context = HashMap::new();
context.insert("context", "string");
Template::render("page", &context)
}
With a custom type:
#[get("/page")]
fn page() -> Template {
#[derive(serde_derive::Serialize)]
struct PageContext {
pub context: String,
}
let context = PageContext { context: "string" };
Template::render("page", &context)
}
Custom types will generally perform better, and are self-documenting.
Related
This question is written for Yew v0.19
Asynchronous foreign JavaScript functions can be used in Rust through Closures, as the function to pass-in:
#[wasm_bindgen]
extern "C" {
fn setInterval(closure: &Closure<dyn FnMut()>, time: u32) -> i32;
}
// ...
let cb = Closure::new(|| {
log("interval elapsed!");
});
let interval_id = setInterval(&cb, 1_000);
This is nice for a pedantic examples, but Closures have a critical requirement - the function applied needs to have a 'static lifetime. Likewise, with Yew applications, a perfect mechanism for spontaneous response is the Message enum, and having it update() the Model. However, the link() mechanism in Context (which issues messages) does not have a static lifetime.
In an ideal world, the value submitted to the closure could just be applied as a Yew component message:
struct Model {
thing: Option<JsValue>,
}
enum Msg {
GotThing(JsValue),
}
#[wasm_bindgen]
extern "C" {
fn createThing(closure: &Closure<dyn FnMut(JsValue) -> ());
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Model {
thing: None, // initial value
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::GotThing(x) => { // handle the message
self.thing = Some(x);
true
},
}
}
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
if first_render {
let cb: Box<dyn FnMut(JsValue) -> ()> = Box::new(|x| {
// try and issue the message
ctx.link().send_message(Msg::GotThing(x));
// ^ doesn't have a `'static` lifetime! Won't compile
});
createThing(Closure::wrap(&cb));
}
}
// fn view() ... omitted, not relevant
}
I'm wondering if there's a way to convert a Callback into a Closure, or if there's a better, more canonical way to do this, to please correct me.
Another idea I had would use some kind of queue defined statically (which wouldn't be safe as it's a mutable static variable), but then it could be used as an intermediary data type between the Closure passed to createThing, and messages could be dispatched within the component.
Maybe there's an external way to interact with a Yew component that I'm overlooking? I'm not sure how to resolve this issue. What would be the most correct way to achieve this goal?
In rust I can put trait bounds on a template argument to guarantee it conforms to the functionality I want:
fn print<T:Debug>(t: T) {
println!("{:?}", t);
}
Can I do something similar with fields?
fn print_name<T:HasNameField>(t: T) {
println!("{:?}", t.name);
}
I'm not trying access a specific field (for which I could make some accessor trait). I am trying to promise a 3P function that I have certain fields, similar to how we promise functionality in a template w/Trait bounds.
My use case is that in yew I would like to create a form. Instead of just using <input type="text".../> I would like users to be able to create their own input fields and be able to build CustomForm. And then I could have:
#[function_component(CustomForm)]
fn custom_form<T: yew::Component>() -> Html {
<form>
<T name="field name"/>
</form>
}
Currently this fails with the following message:
error[E0609]: no field `name` on type `<T as yew::Component>::Properties`
As of Rust 1.62.0 (2022-06-30), traits cannot require fields.
Though, you can use a function instead.
trait Named {
fn get_name(&self) -> &str;
}
Then, just implement it as you would any other trait.
struct NameImpl;
impl Named for NameImpl {
fn get_name(&self) -> &str {
"my field name"
}
}
You can use it as a type bound, and call get_name() as you please.
fn do_stuff_with_named<N: Named>(n: N) {
println!("{}", n.get_name())
}
I serve an Actix Web Rust website using the TinyTemplate crate, where they keys and values are defined in a TOML file that is serialized using the config crate.
This works great with predefined keys (known at compile time) like "title" but I want to add a dynamic "see also" section and I can't figure out how to implement this.
config.toml
title= "Example Title" # this works great
[seealso] # dynamic values, is this possible?
"Project Homepage" = "https://my-project-page.eu"
"Repository" = "https://github.com/myorg/myrepo"
template.html
{title}
{{ for x in seealso }}
...
{{ endfor }}
main.rs
[...]
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub title: String,
pub seealso: HashMap<String, String>,
}
impl Config {
pub fn new() -> Result<Self, ConfigError> {
config::Config::builder()
.add_source(File::from_str(DEFAULT, FileFormat::Toml))
.build()?
.try_deserialize()
}
}
[...]
lazy_static! {
pub static ref CONFIG: Config = Config::new().expect("Error reading configuration.");
}
[...]
let mut template = TinyTemplate::new();
template.add_template("index", INDEX).expect("Could not parse default template");
let body = template().render("index", &*CONFIG).unwrap();
Output
thread 'actix-rt|system:0|arbiter:0' panicked at 'called `Result::unwrap()` on an `Err` value: RenderError { msg: "Expected an array for path 'seealso' but found a non-iterable value.", line: 32, column: 17 }', src/main.rs:80:53
I assume that serde deserializes the HashMap into a JSON object, with the HashMap keys as object keys, which is why I assume Rust won't let me iterate over them. The problem is that TinyTemplate is quite basic in functionality by design, and won't let me put arbitrary code between {{ }} so I can't transform the object (struct?) into something that TinyTemplate can iterate over. I'm quite new to Rust so I may be missing something obvious here, but is there a way to use dynamic values like this with TinyTemplates or is there no way to handle HashMaps? The result doesn't have to use HashMaps though.
The goal is to achieve something like the following pseudocode:
{{ if ! seealso.empty() }}
See also:
<ul>
{{ for (key, value) in seealso }}
<li>{key}</li>
{{ endfor }}
</ul>
{{ endif }}
I have took a look on the code on tinytemplate. It converts the given object into a json object with serde and works with the json representation for further processing. Given a Hashmap, it will create an plain object node which isn't iterable according to the code of the crate.
You cloud create a pull request for the crate; iterating over object fields should be straight forward.
Another possible solution could be to create a second representation of your config.
use config::{ConfigError, File};
use lazy_static::lazy_static;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::path::Path;
use tinytemplate::TinyTemplate;
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub title: String,
pub seealso: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct IterableConfig {
pub title: String,
pub seealso: Vec<(String, String)>,
}
impl Config {
pub fn new() -> Result<Self, ConfigError> {
config::Config::builder()
.add_source(File::from(Path::new("config.toml")))
.build()?
.try_deserialize()
}
}
lazy_static! {
pub static ref CONFIG: Config = Config::new().expect("Error reading configuration.");
}
fn main() {
let mut template = TinyTemplate::new();
template
.add_template(
"index",
"
{title}
{{ for x in seealso }}
{x.0}
{x.1}
{{ endfor }}
",
)
.expect("Could not parse default template");
let body = template
.render(
"index",
&IterableConfig {
title: CONFIG.title.clone(),
seealso: CONFIG
.seealso
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
},
)
.unwrap();
eprintln!("{}", body);
}
I'm working with rust, WASM, and yew as frontend framework. I'm building a wrapper around materialize-css, to use it on yew as dependency of reusable components.
To use some materialize-css components it's necesary initialize it. For example, to use a sidenav
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.sidenav');
var instances = M.Sidenav.init(elems, options);
});
The thing is that I'm trying to run that with wasm-bindgen as described in the wasm-bindgen docs, but it doesn't work. Or maybe I do not really understand whats happening.
according to the documentation, this should work:
#[wasm_bindgen( module = "/materialize/js/bin/materialize.js", js_namespace = ["M","Sidenav"] )]
extern "C" {
fn init(el: JsValue, options: JsValue);
}
pub unsafe fn init_sidenav(el: &str, options: &str) {
init(
JsValue::from(document().get_element_by_id(el).expect("not found")),
JsValue::from(options),
);
}
//to use it on yew component
unsafe { materialize::sidenav::init_sidenav(self.props.el_id, &options) };
or this:
#[wasm_bindgen( module = "/materialize/js/bin/materialize.js", js_namespace = M )]
extern "C" {
#[wasm_bindgen]
type Sidenav;
#[wasm_bindgen( static_method_of = Sidenav )]
fn init(el: JsValue, options: JsValue);
}
pub unsafe fn init_sidenav(el: &str, options: &str) {
Sidenav::init(
JsValue::from(document().get_element_by_id(el).expect("not found")),
JsValue::from(options),
);
}
//to use it on yew component
unsafe { materialize::sidenav::init_sidenav(self.props.el_id, &options) };
But neither of them works... In both cases the code compiles without problems but when executed in the browser it jumps errors.
in the first case the error is Uncaught SyntaxError: import not found: init lib.js:1:9
in the second case the error is Uncaught SyntaxError: import not found: Sidenav lib.js:1:9
honestly i dot not understand why this happens. I've been looking for some days for information in the MDN docs about WASM, and the wasm_bindgen docs, but I can't find anything that helps me.
Additionally
I'm working with --target web flag
My project structure
-
|Cargo.toml
|materialize/ (source of materialize-css dist)
|src/
|-components/ (wrappings of components on yew)
|-materialize/ (wasm-bindgen bindings of materialize)
|-lib.rs
|-...
...
I'm getting the follow compile error:
static optionsRegex: regex::Regex
= match regex::Regex::new(r###"$(~?[\w-]+(?:=[^,]*)?(?:,~?[\w-]+(?:=[^,]*)?)*)$"###) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics cannot evaluate destructors
Ok(r) => r,
Default => panic!("Invalid optionsRegex")
};
More details: I need to access a compiled regexp to be used by struct when creating. Any Rust documentation links or explanation appreciated.
P.S. I think I understand that Rust needs to know when to destruct it but I have no idea how to make it other than just avoid making it static and pass some struct with all the regexps every time it's needed when creating the struct.
Lazily initializing and safely re-using a static variable such as a regular expression is one of the primary use-cases of the once_cell crate. Here's an example of a validation regex that is only compiled once and re-used in a struct constructor function:
use once_cell::sync::OnceCell;
use regex::Regex;
struct Struct;
impl Struct {
fn new(options: &str) -> Result<Self, &str> {
static OPTIONS_REGEX: OnceCell<Regex> = OnceCell::new();
let options_regex = OPTIONS_REGEX.get_or_init(|| {
Regex::new(r###"$(~?[\w-]+(?:=[^,]*)?(?:,~?[\w-]+(?:=[^,]*)?)*)$"###).unwrap()
});
if options_regex.is_match(options) {
Ok(Struct)
} else {
Err("invalid options")
}
}
}
playground