Skip to main content

Validation

Parameter validation is critical for security, data integrity, and API reliability. flAPI ships with seven built-in validator types — int, string, enum, email, uuid, date, and time.

When a field carries a typed validator and is referenced via double-brace {{ params.X }} at top level of the template, flAPI rewrites the reference to a DuckDB ? placeholder and binds the value via duckdb_bind_* — the value travels as a primitive, never as SQL text. This is the hard boundary; the legacy keyword regex is a fallback for non-bindable sites (triple-brace {{{ }}} and untyped fields).

Why Validation Matters

Without validation: unclear 400s, weak typing, regex relies on keyword heuristics. With validation: strict type parsing (1; DROP TABLE cannot slip through as 1), DuckDB prepared-statement binding for typed fields, clear error messages, automatic OpenAPI docs.

Basic Validation

Add one or more validators to any request field:

request:
- field-name: customer_id
field-in: query
required: true
validators:
- type: int
min: 1
max: 999999
preventSqlInjection: true

preventSqlInjection defaults to true. For numeric/temporal bindable types (int, double, boolean, date, time) the SQL-keyword regex is now demoted to a debug log — the prepared-statement bind is the real defense, and the regex's notorious false positives (e.g. latitude=1.111) are gone. Varchar-bindable types (string, uuid, email, enum) keep the regex because flAPI templates routinely embed them via triple-brace {{{ }}} for LIKE patterns. Set preventSqlInjection: false only when you have a deliberate reason (e.g. a field that holds an opaque token).

Validator Types

flAPI supports exactly seven validator types. Anything else is silently ignored by src/request_validator.cpp.

Integer (int)

For whole numbers. Range bounds default to INT_MIN/INT_MAX.

validators:
- type: int
min: 1
max: 1000
preventSqlInjection: true

String (string)

Length is controlled with min and max (in characters), and pattern matching uses regex (full match required).

validators:
- type: string
min: 3
max: 50
regex: '^[A-Za-z0-9_-]+$'
preventSqlInjection: true

Use a separate enum validator if the field should be one of a fixed set of values — string does not accept an enum: key.

Enum (enum)

Whitelist of allowed values.

validators:
- type: enum
allowedValues: [draft, published, archived]

Email (email)

Built-in regex: [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}.

validators:
- type: email

UUID (uuid)

Standard 36-character form (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).

validators:
- type: uuid

Date (date)

Format YYYY-MM-DD. Optional min / max are strings in the same format.

validators:
- type: date
min: "2000-01-01"
max: "2025-12-31"

Time (time)

Format HH:MM:SS. Optional min / max are strings in the same format.

validators:
- type: time
min: "08:00:00"
max: "18:00:00"

SQL Injection Prevention

When preventSqlInjection is true (the default), flAPI rejects values that contain SQL keywords (SELECT, INSERT, UPDATE, DELETE, DROP, UNION, ...), comment markers (--, /*, */), or common injection patterns (' OR 1=1, '; ...).

Only set it to false for fields where you intentionally accept SQL-looking content (very rare).

Multiple Validators on One Field

Stack validators when you need multiple checks:

- field-name: username
field-in: query
required: true
validators:
- type: string
min: 3
max: 20
regex: '^[a-zA-Z0-9_]+$'

All declared validators must pass for the parameter to be accepted.

Common Validation Patterns

Email Address

- field-name: email
validators:
- type: email

Phone Number (E.164)

- field-name: phone
validators:
- type: string
regex: '^\+?[1-9]\d{1,14}$'

Country Code

- field-name: country
validators:
- type: string
regex: '^[A-Z]{2}$'

Status Enum

- field-name: status
validators:
- type: enum
allowedValues: [active, inactive, pending]

Customer UUID

- field-name: customer_id
validators:
- type: uuid

Date Range

- field-name: registration_date
validators:
- type: date
min: "2000-01-01"
max: "2025-12-31"

Strict Mode: Reject Unknown Parameters

By default flAPI accepts any extra query parameter and silently ignores it. Set request-fields-validation: true on the endpoint to reject requests that include parameters not declared under request:.

url-path: /customers/
method: GET
request-fields-validation: true # default: false
request:
- field-name: id
field-in: query
validators:
- type: int
min: 1

A request containing ?id=1&unknown=foo returns a 400 with the offending field name. The pagination parameters offset and limit are always allowed.

Error Responses

Validation errors come back as HTTP 400 with one entry per failed field:

{
"errors": [
{ "field": "segment", "message": "Invalid enum value" },
{ "field": "id", "message": "Integer is less than the minimum allowed value" }
]
}

The exact messages come from src/request_validator.cpp:

ValidatorMessage
int (out of range)Integer is less than/greater than the minimum/maximum allowed value
int (non-numeric)Invalid integer value
string (too short/long)String is shorter/longer than the minimum/maximum allowed length
string (regex mismatch)Invalid string format
enumInvalid enum value
emailInvalid email format
uuidInvalid UUID format
date / timeInvalid date/time format, Date/Time is before/after the minimum/maximum allowed date/time
any (SQL pattern)Potential SQL injection detected
missing requiredRequired field is missing
unknown (strict mode)Unknown parameter not defined in endpoint configuration

Security Best Practices

1. Always validate user input

validators:
- type: enum
allowedValues: [active, inactive]

2. Whitelist with enum where possible

validators:
- type: enum
allowedValues: [name, email, created_at]

3. Limit string length and tighten regex

validators:
- type: string
min: 1
max: 100
regex: '^[A-Za-z0-9_ \-]+$'

4. Restrict numeric ranges

validators:
- type: int
min: 1
max: 1000

5. Enable strict field mode for sensitive endpoints

request-fields-validation: true

Real-World Examples

url-path: /products/search/
method: GET

request:
- field-name: query
field-in: query
required: true
validators:
- type: string
min: 3
max: 100

- field-name: category
field-in: query
required: false
validators:
- type: enum
allowedValues: [electronics, clothing, books, home]

- field-name: min_price
field-in: query
required: false
validators:
- type: int
min: 0
max: 1000000

- field-name: limit
field-in: query
required: false
validators:
- type: int
min: 1
max: 100

Date Range Analytics

url-path: /analytics/revenue/
method: GET

request:
- field-name: start_date
field-in: query
required: true
validators:
- type: date
min: "2020-01-01"

- field-name: end_date
field-in: query
required: true
validators:
- type: date

- field-name: granularity
field-in: query
required: false
default: day
validators:
- type: enum
allowedValues: [day, week, month, quarter, year]

Customer Lookup by UUID

url-path: /customers/:customer_uuid
method: GET

request:
- field-name: customer_uuid
field-in: path
required: true
validators:
- type: uuid

Common Issues

Validators not firing

  • Check indentation under validators: — it must be a list of mappings, not a single mapping.
  • Make sure the type is one of the seven supported names (int, string, enum, email, uuid, date, time).

Regex rejects valid input

  • The pattern must match the whole value (anchored implicitly).
  • Escape backslashes once for YAML, then once more if needed for the regex engine.

"Potential SQL injection detected" on a clean value

  • The default scanner blocks SQL keywords as whole words and certain patterns. If the value is legitimate (e.g. a column name in a CMS search), set preventSqlInjection: false on that validator and rely on a regex / enum instead.

Next Steps

🍪 Cookie Settings