Input validation and hard limits
Validate every request before it costs anything, build a clean copy with defaults, and put hard limits on model output size, slide count and nesting depth.
Once your tool is on the internet, some requests will be strange. A topic that's an entire book pasted by accident. A slide count of 5,000. A theme called toString. And now and then, a model reply that nests elements 200 levels deep. Each one can cost real money or freeze the server, unless it's stopped at the door.
Request body
topic, slideCount, theme
Model output
SlideML text
checkTreeLimits
escapeHtml
Pipeline
clean, bounded input
Layout + preview
safe to render
Validating requests
happens at the : the first line of our code that touches the request. validateRequest(body) returns either a clean value or a clear error:
- topic: a string, trimmed, between 3 and 2,000 characters.
- slideCount: a whole number from 3 to 12, defaulting to 6.
- theme: one of the themes that exist, defaulting to light. Checked with
Object.hasOwn, because"toString" in THEMESistrue. - research:
trueonly if it's exactlytrue.
Limiting model output
This lesson also updates compile.js to check the deck before doing real work: at most 60,000 characters, at most 14 slides, and per slide at most 16 levels of nesting and 150 elements (checkTreeLimits, written for you). A reply that breaks a limit becomes a problem, like any other.
Your project so far
26 files · 2 new or changed in this lesson
src/limits.js
// Everything that crosses into our system from outside gets checked: what users
// send us, and what the model sends back. Limits keep one bad request from
// costing a fortune, freezing the server, or crashing the layout engine.
import { THEMES } from "./theme.js";
export const LIMITS = {
topicChars: 2000, // a longer brief is almost always a paste accident
minSlides: 3,
maxSlides: 12,
xmlChars: 60_000, // about 15k tokens: far more than any real deck needs
depth: 16, // how deeply elements may nest inside one slide
elementsPerSlide: 150,
};
/**
* Check a request body and return a clean copy with defaults filled in:
* { ok: true, value: { topic, slideCount, theme, research } } or { ok: false, error }
*/
export function validateRequest(body) {
if (!body || typeof body !== "object") return { ok: false, error: "Send a JSON object." };
const topic = typeof body.topic === "string" ? body.topic.trim() : "";
if (topic.length < 3) return { ok: false, error: "Describe the deck in at least a few words." };
if (topic.length > LIMITS.topicChars) return { ok: false, error: `Keep the description under ${LIMITS.topicChars} characters.` };
const slideCount = body.slideCount === undefined ? 6 : Number(body.slideCount);
if (!Number.isInteger(slideCount) || slideCount < LIMITS.minSlides || slideCount > LIMITS.maxSlides) {
return { ok: false, error: `Choose between ${LIMITS.minSlides} and ${LIMITS.maxSlides} slides.` };
}
const theme = body.theme ?? "light";
if (!Object.hasOwn(THEMES, theme)) return { ok: false, error: `Unknown theme "${theme}".` };
return { ok: true, value: { topic, slideCount, theme, research: body.research === true } };
}
/** Throw if a slide tree is too deep or has too many elements to lay out safely. */
export function checkTreeLimits(tree) {
let count = 0;
let deepest = 0;
const walk = (node, depth) => {
if (node.tag === "#text") return;
count += 1;
deepest = Math.max(deepest, depth);
if (depth > LIMITS.depth) return; // no need to go further: it's already too deep
for (const child of node.children) walk(child, depth + 1);
};
walk(tree, 1);
if (deepest > LIMITS.depth) throw new Error(`Elements are nested more than ${LIMITS.depth} levels deep.`);
if (count > LIMITS.elementsPerSlide) throw new Error(`The slide has ${count} elements; the limit is ${LIMITS.elementsPerSlide}.`);
}Key takeaways
- Validate at the boundary, before any model call, and return a message a person can act on.
- Build a new, clean object from the request; never pass the raw body deeper into the system.
- Model output is untrusted too: cap its size, slide count and nesting depth.
Sign in to run the exercise
Reading is free. Writing code here needs an account so we have somewhere to keep your Gemini key and the +35 XP you are about to earn.