Skip to content

Validate a formula someone typed

A formula typed by a person is wrong sooner or later. This guide checks one before it costs you a roll, and shows the reader what was wrong with it.

In JavaScript, parseFormula reads a formula and throws if it cannot. It does the parsing and none of the rolling, which costs about a hundredth as much.

import { parseFormula } from 'opendice'
function validate(text) {
try {
parseFormula(text)
return { ok: true }
} catch (error) {
return { ok: false, message: error.message }
}
}
validate('4d6kh3') // { ok: true }
validate('4d6kj3') // { ok: false, message: 'Cannot parse "4d6kj3" near "kj3"' }

Use this to enable a submit button, or to mark a field as you type. Over HTTP there is no parse-only endpoint — roll it and read the error, which costs one request either way.

A formula the API refuses comes back as a 400 with a code.

{
"error": {
"code": "invalid_formula",
"message": "Cannot parse \"4d6kj3\" near \"kj3\""
}
}

Branch on code, never on message:

const response = await fetch(url)
if (!response.ok) {
const { error } = await response.json()
if (error.code === 'invalid_formula' || error.code === 'invalid_request') {
return showToUser(error.message)
}
throw new Error(error.code)
}

invalid_request means the request did not match the schema at all — a missing formula, a formula over the length limit, a malformed tags list. invalid_formula means the formula was well-formed as a request and the parser still refused it. Both are the caller’s to fix and neither is worth retrying unchanged. Errors lists the rest.

You can render a parse error directly. Before quoting anything back, the parser replaces every character a formula cannot contain and truncates what is left, so a formula of 2d6<script> comes back describing a forbidden character rather than carrying the tag into your page.

{
error && <p className="error">{error.message}</p>
}

This holds for parse errors specifically, which are the ones that quote input. Treat it as one less thing to escape, not as a reason to stop escaping elsewhere.

A trailing word is a parse error unless you have said it is a tag. That is deliberate: an unrecognised word is far more often a typo than a tag.

roll('2d6 fire') // throws — 'fire' is a stray token
roll('2d6 fire', { tags: ['fire'] }) // rolls, result.tag === 'fire'

List every word you recognise, in lowercase. If your interface offers a fixed set of damage types or categories, that set is the list. See the grammar for what a tag may contain.

  • Errors — every code, and what to do about each.
  • Limits — the bounds a formula has to stay inside.