vento icon indicating copy to clipboard operation
vento copied to clipboard

Add strict mode

Open rougsig opened this issue 10 months ago • 12 comments

If a required property is missing in the data, it defaults to an empty string. I propose adding strict: true to throw an exception when a property is not defined in the data argument.

rougsig avatar Feb 26 '25 16:02 rougsig

The option autoDataVarname is true by default. This function converts {{ varname }} to {{ it.varname }} (the it object is a global variable with the data, so if a variable doesn't exist, it returns undefined). If you set this option to false, this conversion is not performed, so {{ varname }} will throw an exception if it's not defined.

oscarotero avatar Feb 26 '25 16:02 oscarotero

But for get key from data i will need to write it.data, that fill be undefined. I want to be sure that user pass all variables in form to compile the template. How the best way to do it?

rougsig avatar Feb 26 '25 17:02 rougsig

hmmm, right.

If you only want to make some variables strict, a solution is to create a filter. For example:

{{ varname |> required }}

And the filter would be something like:

env.filters.required = (value) => {
  if (value === undefined) {
    throw new Error("A required value is undefined");
  }
  return value;
}

oscarotero avatar Feb 26 '25 18:02 oscarotero

Yeah filter will works, but in my opinion it should be a bit more sugar for that.

rougsig avatar Feb 26 '25 18:02 rougsig

Maybe automatically wrap into a proxy the data object? And add option {strict: boolean} to runString/runFile methods/

rougsig avatar Feb 26 '25 18:02 rougsig

hmm, that could work.

oscarotero avatar Feb 26 '25 19:02 oscarotero

By following instruction of this issues, I got a strange error.

Here the reproduce:

import vento from "jsr:@vento/vento";

let myEnv = vento({ dataVarname: "it",
  autoDataVarname: false,
  includes: Deno.cwd(),
  autoescape: false,})

let result = await myEnv.runString("<h1>{{ title }}</h1>", {
  title: "foo"
});

This returns the following error:

Uncaught TemplateError: Error in template <unknown>:1:5

<empty file>


    at eval (eval at compile (https://jsr.io/@vento/vento/1.12.16/src/environment.ts:151:25), <anonymous>:15:17)
    at Environment.runString (https://jsr.io/@vento/vento/1.12.16/src/environment.ts:100:18)
    at <anonymous>:1:43

With deno 1.46.3.

Heziode avatar Apr 24 '25 13:04 Heziode

@Heziode looks like is works as expected. You turn off the autoDataVarname. With false you should to {{ it.title }}, it will not auto discover properties.

https://vento.js.org/configuration/#autodatavarname

rougsig avatar Apr 24 '25 14:04 rougsig

Ok,

So, I have a mid-working program.

Now with this:

import vento from "jsr:@vento/vento";
let env = vento({autoDataVarname: true})
env.filters.required = (value) => {
  if (value === undefined) {
    throw new Error("A required value is undefined");
  }
  return value;
}
await env.runString("<h1>{{ title |> required }}</h1>", {   })

I got the same error:

Uncaught TemplateError: Error in template <unknown>:1:5

<empty file>


    at eval (eval at compile (https://jsr.io/@vento/vento/1.12.16/src/environment.ts:151:25), <anonymous>:19:17)
    at Environment.runString (https://jsr.io/@vento/vento/1.12.16/src/environment.ts:100:18)
    at <anonymous>:1:32

Instead of the expected error message. I got a similar error when using a template file:

Uncaught TemplateError: Error in template /path/to/file.vto:1:3

# {{ title |> required }}


    at eval (eval at compile (https://jsr.io/@vento/vento/1.12.16/src/environment.ts:151:25), <anonymous>:49:17)
    at Environment.run (https://jsr.io/@vento/vento/1.12.16/src/environment.ts:78:18)
    at eventLoopTick (ext:core/01_core.js:175:7)
    at async <anonymous>:1:30

Heziode avatar Apr 24 '25 15:04 Heziode

@Heziode It works fine in my computer:

Image

oscarotero avatar Apr 24 '25 15:04 oscarotero

Okey,

I found a better way (in my case) to achieve what I want. I added a frontmatter in the VTO file (my VTO is a Markdown file):

---
required:
  - name
  - age
optional:
  - extra
---

Hello {{name}}, your age is {{age}}.
{{#extra}}Note: {{extra}}{{/extra}}

And then, I can use it like this:

// Script to process a Vento template file (.vto)
import { extractYaml } from "jsr:@std/front-matter";
import vento from "jsr:@vento/vento";

// Read the template file
const filePath = "/path/to/my/template.vto";
const text = Deno.readTextFileSync(filePath);

// Extract YAML metadata and the template content
const parsed = extractYaml(text);
const template = parsed.body;

// Define the data to inject into the template
const data = {
  name: "Alice",
  age: "30",
  extra: "VIP member"
};

// Check for required variables
const missing = parsed.attrs.required.filter((key) => !(key in data));
if (missing.length) {
  throw new Error(`Missing variables: ${missing.join(", ")}`);
}

// Initialize the Vento environment and render the template
const env = vento();
const result = await env.runString(template, data);

// Display the result
console.log(result.content);

// Optionally: write the result to a file
// Deno.writeTextFileSync("output.md", result.content);

I am impressed that no major template engine (no offense @oscarotero, Handlebars and Cie are the same) natively supports a way of defining mandatory variables

Heziode avatar Apr 24 '25 19:04 Heziode

@Heziode I don't think template engines should validate the data you provide. That's a different job, with different requirements and definitely it's not responsibility of the template engine.

Vento's approach delegates almost everything to the JavaScript runtime, it doesn't know what you insert between {{ }}, it just pass it to JavaScript runtime to evaluate it (with the exception of known tags like for, import, include, etc). That's what makes Vento so flexible (if JS can execute it, Vento supports it) but also it loses control about what you run inside the templates.

If you want to validate the data passed to Vento, I recommend to use a validator library, so you can do:

if (!validatorLibrary(data)) {
   throw new Error("invalid data");
}

const result = await env.runString(template, data);

Note also that you can configure Vento's loader to parse the front matter automatically. The default loader returns the file content in the source property (here) but Lume for example returns the source and data properties (here) so you use the frontmatter to provide defaults for missing variables.

oscarotero avatar Apr 24 '25 22:04 oscarotero

I agree with the OP that this feature would be nice. Liquid has a strict mode and I always have it turned on as I've been bitten by subtle mistakes in the past.

Firstly, the autoDataVarname option really doesn't solve this problem; if I have it on, and write {{ desrciption }}, Vento silently renders nothing instead of a description (which, in a meta tag, may very well escape my notice). With autoDataVarname: false, then I might write {{ it.desrciption }} instead, and Vento again silently renders nothing. The fact that it throws on {{ desrciption }} is irrelevant, because it also throws on the correctly spelled {{ description }}.

Asking users to write {{ description |> required }} I think goes against the spirit of Vento; it's one of the most ergonomic templating languages and strives for a clean syntax with a great developer experience. Needing an extra |> required with every variable is simply not a satisfactory solution, and doesn't work in cases like {{ foo + bar }}.

For me, it's not so much about validating that the correct data is passed to templates, more that templates should reject things they don't recognize. JavaScript does the same thing; if you use a variable that you have not defined, JavaScript throws. Since Vento is so JavaScript-forward, new users might even expect this strict mode to be the default, only to discover it is not even a feature.

All that to say; I think this is a really valuable feature request. I'd be happy to open a PR for this in the future (but probably best to postpone after the removal of meriyah).

vrugtehagel avatar Jul 06 '25 15:07 vrugtehagel

@vrugtehagel I think the most simple way to implement strict mode, it just use js Proxy class. Yes it not the best for error handling, but still enough for fast implementation.

rougsig avatar Jul 06 '25 18:07 rougsig

@rougsig that is indeed a solution with the way it is currently implemented, but we're considering moving away from the it object altogether when autoDataVarname is set to true. That's why I suggested postponing implementation of this particular feature until that lands.

vrugtehagel avatar Jul 06 '25 20:07 vrugtehagel