Any templating engine that doesn't use curly brackets?

I need to generate some moderately complicated JSON strings (OpenSearch queries). Every template engine I've looked at (tera, handlebars, a few others) uses curly brackets to delimit the variables, which does not mix well with JSON. :slight_smile:

Are there any Rust-based template engines that don't use curly brackets for variables, or can be configured to use something other than curly brackets?

Given the high probability of bugs around quoting, escaping, and type conversion - or the cost of spending an inordinate amount of cognitive effort and test coverage to catch them before they hit your users - you may be better off using serde-json to generate the JSON strings from typed strutures, rather than using a template engine. Is that a workable option for you?

That looks approximately like (caution: written but not compiled or tested):

#[derive(Serialize, Debug)]
struct Request {
  query: Query,
  multi_match: Option<MultiMatch>,
}

/* repeat for Query and MultiMatch structs... */
fn example() -> Result<String> {
  let request = Request {
    query: Query { ... },
    multi_match: None,
  };
  Ok(serde_json::to_string(request)?)
}
1 Like

Good suggestion, thanks. I hadn't considered this, because OpenSearch queries are quite complex, but perhaps I have underestimated the power of serde! I'll give it a try.

1 Like

Partial success using derspiny's suggestion!

For anyone else who finds this post, I went from the first block of text (below) to the second. It's much cleaner and more maintainable. "painless" is the name of OpenSearch's query scripting language, BTW, and it doesn't allow newlines in the text, hence all the "XX_EOL" nonsense.

From this:

   let body = r#"{
       "script": {
           "source": "if (!ctx._source.list_indexes.contains(params.idx)) {XX_EOL
               ctx._source.list_indexes.add(params.idx);XX_EOL
               ctx._source.list_bodies.add(params.obj);XX_EOL
           }",
           "lang": "painless",
           "params": {
               "idx": "XX_IDX",
               "obj": {
                   "text": "XX_TEXT",
                   "n": XX_N
               }
           }
       }
   }"#
   .replace("XX_IDX", idx)
   .replace("XX_TEXT", &my_struct.text)
   .replace("XX_N", &my_struct.n.to_string())
   .replace("XX_EOL\n", "");
    let body: serde_json::Value = serde_json::from_str(&body).unwrap();

to this:

    #[derive(Serialize)]
    struct Query<'a> {
        script: Script<'a>,
    }

    #[derive(Serialize)]
    struct Script<'a> {
        source: String,
        lang: &'a str,
        params: Params<'a>,
    }

    #[derive(Serialize)]
    struct Params<'a> {
        idx: &'a str,
        obj: &'a MyStruct,
    }

    let painless_script =
        "if (!ctx._source.list_indexes.contains(params.idx)) {XX_EOL
            ctx._source.list_indexes.add(params.idx);XX_EOL
            ctx._source.list_bodies.add(params.obj);XX_EOL
        }"
        .replace("XX_EOL\n", "");

    let body = Query {
        script: Script {
            source: painless_script,
            lang: "painless",
            params: Params { idx, obj: &s },
        },
    };

If you escape newlines in literal strings, it will eat the newline and any leading whitespace on the next line.

2 Likes