带有动态值的Rust TinyTemplate(hashmap)?

发布于 2025-02-13 13:19:31 字数 2121 浏览 2 评论 0原文

我使用TinyTemplate板条箱为Actix Web Rust网站提供服务,其中它们的键和值是在使用config crate序列化的TOML文件中定义的。 这与预定义的键(在编译时已知)非常有用,但是我想添加一个动态的“另请参见”部分,我不知道如何实现它。

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();

输出

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

我假设Serde可以将哈希截止归为JSON对象,并以Hashmap键为对象键,这就是为什么我假设Rust不会让我迭代它们。问题在于,TinyTemplate在设计上是基本的功能,并且不能让我在{{}}}之间放置任意代码,因此我无法将对象(struct?)转换为TinyTemplate可以迭代。我对Rust的新手很新,所以我可能在这里缺少一些明显的东西,但是有没有一种方法可以使用TinyTemplates这样的动态值,或者没有办法处理hashmaps?结果不必使用哈希图。

目标是实现以下伪代码之类的东西:

{{ if ! seealso.empty() }}
See also:
<ul>
{{ for (key, value) in seealso }}
  <li><a href="{value}">{key}</a></li>
{{ endfor }}
</ul>
{{ endif }}

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><a href="{value}">{key}</a></li>
{{ endfor }}
</ul>
{{ endif }}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

与之呼应 2025-02-20 13:19:31

我已经在tinyTemplate上查看了代码。它将给定对象转换为JSON对象,用serde将其与JSON表示形式一起工作以进行进一步处理。给定Hashmap,它将创建一个普通的对象节点,该节点是根据板条箱的代码而无法的。

云创建板条板的拉请请求;在对象字段上迭代应直接。

另一个可能的解决方案可能是创建您的配置的第二个表示。

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 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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文