Validating Dice Notation

Introduction

In the previous lessons, you learned how to build a basic Express API for rolling dice and how to create a user-friendly landing page. Now, we will make your Express API more powerful and flexible by supporting dice notation and improving input validation.

Dice notation is a standard way to describe dice rolls — especially in games. By adding support for this notation, you will make your API easier to use and more familiar to many users. We will also ensure that the API handles bad input gracefully by returning clear error messages when something goes wrong.

By the end of this lesson, you'll be able to parse and validate dice notation such as 2d6, d20, or 4d8+2, enforce sensible limits on dice rolls and modifiers, and return clear error messages for invalid input. You'll also learn how to write unit tests to ensure that your code works as expected.

Let's get started!

What Is Dice Notation?

Dice notation is a shorthand way to describe rolling dice, which is often used in tabletop games. The most common format is:

plaintext
NdM[+/-K]

Where:

  • N is the number of dice to roll (optional, defaults to 1).
  • M is the number of sides on each die.
  • [+/-K] is an optional modifier to add to or subtract from the total.

Here are some examples:

  • 2d6 means roll 2 six-sided dice and add the results.
  • d20 means roll 1 twenty-sided die.
  • 4d8+2 means roll 4 eight-sided dice and add 2 to the total.
  • 3d10-1 means roll 3 ten-sided dice and subtract 1 from the total.

This notation is popular because it is concise and easy to read. By supporting it in your API, you make the service more convenient for users who are familiar with these conventions.

Why LLMs Excel at Standard Formats

Dice notation is a standard, well-established format used across many games and applications. This is important because Codex and other LLM agents are usually highly proficient at implementing standard formats and patterns. When you ask Codex to implement dice notation, it has likely seen countless examples of this pattern in its training data, so it can generate working code relatively easily.

This principle applies to many other standard formats: regular expressions, date/time formatting (ISO 8601, RFC 3339), JSON schemas, URL patterns, markdown parsing, and CSV formatting. However, LLMs can hallucinate and produce incorrect output. Always review, test, and verify generated code — especially for critical functionality — before deploying it to production.

Parsing and Validating Dice Notation

Let's use Codex to build a parser for dice notation. We'll guide it step by step with clear prompts.

Step 1: Create the Dice Parser

The first thing we need is a function that takes a string like "2d6+3" and extracts the count, sides, and modifier. Ask Codex to create this:

plaintext
Create src/dice-parser.ts with a function parseDice that takes a string in dice notation (like "2d6", "d20", "4d8+2", "3d10-1") and returns an object with count, sides, and modifier. Use a regular expression to match the pattern. If count is omitted (like "d20"), default to 1. If modifier is omitted, default to 0. Throw an error with a descriptive message if the input doesn't match valid dice notation.

Codex will generate a file with a regular expression like ^\s*(?:(\d*)d(\d+))(?:([+-])(\d+))?\s*$ that matches:

  • An optional number (the count of dice).
  • The letter d.
  • The number of sides.
  • An optional modifier (either + or - followed by a number).

For example, parsing 4d8+2 gives count = 4, sides = 8, modifier = 2. Parsing d20 gives count = 1 (default), sides = 20, modifier = 0 (default).

Step 2: Add Input Validation

Now let's add sensible limits to prevent abuse or errors. Prompt Codex:

plaintext
Update parseDice in src/dice-parser.ts to validate the extracted values. Count must be between 1 and 100, sides must be between 2 and 1000, and modifier must be between -1000 and 1000. If any value is out of range, throw an error with a message specifying which value is invalid and what the allowed range is.

These limits ensure that users can't request absurd rolls like 999999d999999, which would waste server resources or cause errors.

Step 3: Review the Generated Code

After Codex generates the parser, review the output carefully. You should verify that:

  • The regex correctly handles all valid formats (2d6, d20, 4d8+2, 3d10-1).
  • Missing count defaults to 1.
  • Missing modifier defaults to 0.
  • Out-of-range values produce clear error messages.
  • The function uses proper TypeScript type annotations for the return value.

This is a good example of where LLMs excel — dice notation is a well-known standard, so Codex is likely to produce a correct implementation. But always review before moving on.

Updating the API Endpoint

Now let's update the /roll/ endpoint to support dice notation alongside the existing count and sides parameters.

Step 1: Add Dice Notation Support to the Route

Prompt Codex to update your existing route:

plaintext
Update the GET /roll/ route in src/index.ts to support a new query parameter called "expr" for dice notation (like "2d6+3"). If "expr" is provided, use parseDice to parse it. If "expr" is not provided, fall back to the existing "count" and "sides" query parameters. Import parseDice from dice-parser.ts.

This way, users can make requests like /roll/?expr=3d10-1 using dice notation, or stick with the older format /roll/?count=2&sides=6.

Step 2: Add Error Handling to the Route

Next, prompt Codex to handle errors gracefully:

plaintext
Update the GET /roll/ route to wrap the parsing logic in a try-catch block. If parseDice throws an error, return HTTP status 400 with a JSON error response in this format: { "error": { "code": "BAD_REQUEST", "message": "<the error message>" } }. If no expr, count, or sides are provided, use default values of 1 die with 6 sides.

Now, a request like /roll/?expr=banana will return:

JSON
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "Invalid dice expression. Use NdM or NdM±K (e.g., 2d6, d20, 4d8+2)."
  }
}

And a request like /roll/?expr=0d6 will return:

JSON
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "count must be between 1 and 100"
  }
}

Step 3: Verify the Success Response

For a valid request like /roll/?expr=2d6+3, the response should include the individual rolls, the total (including the modifier), and echo back the parameters:

JSON
{
  "rolls": [4, 5],
  "total": 12,
  "count": 2,
  "sides": 6,
  "modifier": 3
}

Test this using the platform's preview feature to confirm everything works.

Testing the Parser and Endpoint

Testing is essential to ensure that your code works and handles errors correctly. Let's use Codex to set up testing and write our test cases.

Step 1: Set Up the Testing Framework

First, ask Codex to configure testing for your project:

plaintext
Set up Jest with TypeScript for the diceroller project. Install jest, ts-jest, @types/jest, supertest, and @types/supertest as dev dependencies. Create a jest.config.js that uses ts-jest. Add a "test" script to package.json that runs jest.

Codex will install the dependencies and configure Jest to work with TypeScript. supertest is a library that lets you test Express endpoints without starting the server.

Step 2: Write Tests for the Parser

Now, prompt Codex to write unit tests for the parser:

plaintext
Create src/dice-parser.test.ts with Jest tests for parseDice. Test these cases:
- "2d6" should return count 2, sides 6, modifier 0
- "d20" should return count 1, sides 20, modifier 0
- "4d8+2" should return count 4, sides 8, modifier 2
- "3d10-1" should return count 3, sides 10, modifier -1
- " 2d6 " (with spaces) should still parse correctly
- "banana" should throw an error
- "0d6" should throw an error (count out of range)
- "2d1" should throw an error (sides out of range)

Codex will generate a test file using Jest's describe, test, expect, and toThrow patterns. Review the output to make sure all cases are covered.

Step 3: Write Tests for the Endpoint

Next, ask Codex to write endpoint tests:

plaintext
Create src/index.test.ts with Jest tests for the GET /roll/ endpoint using supertest. Test these cases:
- /roll/?expr=2d6 should return status 200 with rolls array and total
- /roll/?count=3&sides=8 should return status 200 (fallback parameters)
- /roll/?expr=banana should return status 400 with error.code "BAD_REQUEST"
- /roll/?expr=0d6 should return status 400
- /roll/ with no parameters should return status 200 with defaults (1d6)
Also mock Math.random using jest.spyOn to return 0.5 so the results are predictable. Restore the mock after each test.

Codex will generate tests using supertest to make HTTP requests against your Express app, and will use jest.spyOn(Math, 'random') to make the random results predictable.

Step 4: Run the Tests

Run the tests with:

plaintext
npm test

Review the results. If any tests fail, you can ask Codex to help fix the issues by describing the failure in your next prompt.

Summary and Practice Preview

In this lesson, you used Codex to enhance your Express API by adding support for standard dice notation, robust input validation, and clear error messages for invalid requests. You guided Codex through creating a parser, updating the API endpoint, and writing comprehensive tests — all through natural language prompts. You also learned that LLMs excel at implementing well-known standard formats like dice notation, while still requiring careful review of the generated output. In the upcoming exercises, you'll put these concepts into practice by guiding Codex to parse dice notation, validate input, and test your API to reinforce what you've learned.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal