Skip to content

Validation

Lee Yunjin edited this page Oct 7, 2026 · 1 revision

Validation

CWIST has two validators:

Binding (bind.h) Zod-style (zod.h)
Input request body (JSON or URL-encoded form) a cJSON object or JSON string
Output a filled C struct the parsed cJSON tree
Rules required, length, numeric range, regex, email, custom required fields and types
Errors per-field messages, optional automatic 400 response per-field messages

Binding request bodies into structs

Header: <cwist/core/validation/bind.h>

#include <cwist/core/validation/bind.h>

typedef struct {
    char email[128];
    int age;
} signup_t;

CWIST_BIND_RULES(email_rules, CWIST_RULE_REQUIRED(), CWIST_RULE_EMAIL());
CWIST_BIND_RULES(age_rules,   CWIST_RULE_MIN_VAL(18), CWIST_RULE_MAX_VAL(120));

static const cwist_bind_field_t signup_fields[] = {
    CWIST_BIND_FIELD(signup_t, email, "email", email_rules),
    CWIST_BIND_FIELD(signup_t, age,   "age",   age_rules),
};

static void signup(cwist_http_request *req, cwist_http_response *res) {
    signup_t in = {0};
    cwist_bind_schema_t schema = CWIST_BIND_SCHEMA(signup_t, signup_fields);
    if (!cwist_app_req_bind_json_or_400(req, res, &schema, &in))
        return;                         /* res is already a 400 with JSON errors */
    /* in.email, in.age are valid here */
}

A failed bind answers:

{"success":false,"errors":[{"field":"email","message":"..."}]}

Rules

Macro Rule
CWIST_RULE_REQUIRED() present and non-empty
CWIST_RULE_MIN_LEN(n), CWIST_RULE_MAX_LEN(n) string length
CWIST_RULE_MIN_VAL(v), CWIST_RULE_MAX_VAL(v) numeric range
CWIST_RULE_REGEX(pattern) POSIX extended regular expression (static string)
CWIST_RULE_EMAIL() simplified RFC 5322 address shape
CWIST_RULE_CUSTOM(fn, ctx) bool fn(const char *value, size_t len, void *ctx)

CWIST_BIND_RULES(name, ...) declares a terminated static rule array. CWIST_BIND_FIELD(Struct, member, "json_key", rules) infers the target type from the member with _Generic: bool, signed and unsigned integers, float, double, char * / char[] (copied, bounded by the member size), cwist_sstring *, and cJSON * (ownership passes to you). CWIST_BIND_SCHEMA(Struct, fields) builds the schema.

Functions

Function Description
bool cwist_app_req_bind_json(cwist_http_request *req, const cwist_bind_schema_t *schema, void *out, cwist_bind_result_t *result) Parse the JSON body once, validate and fill out. false fills result with up to 32 errors.
bool cwist_app_req_bind_form(cwist_http_request *req, const cwist_bind_schema_t *schema, void *out, cwist_bind_result_t *result) Same for application/x-www-form-urlencoded bodies.
void cwist_bind_write_error_response(cwist_http_response *res, const cwist_bind_result_t *result) Write the 400 JSON error response.
bool cwist_app_req_bind_json_or_400(req, res, schema, out) Bind JSON and write the 400 on failure.

Zod-style strict validation

Header: <cwist/core/utils/zod.h>

Uses the same cwist_schema_t as JSON healing but rejects non-conforming data instead of repairing it. Use it at trust boundaries where silent coercion is unwanted.

cJSON *parsed = NULL;
cwist_zod_result_t r = cwist_zod_parse(req->body->data, &schema, &parsed);
if (!r.valid) {
    res->status_code = CWIST_HTTP_BAD_REQUEST;
    /* r.errors[i].field, r.errors[i].message for i < r.error_count */
    return;
}
/* use parsed */
cJSON_Delete(parsed);
Function Description
cwist_zod_result_t cwist_zod_validate(const cJSON *json, const cwist_schema_t *schema) Check required fields and declared types of a parsed object. No coercion.
cwist_zod_result_t cwist_zod_parse(const char *raw, const cwist_schema_t *schema, cJSON **out) Parse and validate; *out is the tree on success (free with cJSON_Delete()) and NULL otherwise.
void cwist_zod_print_errors(const cwist_zod_result_t *r) Print the errors to stderr.

cwist_db_query_strict() drops result rows that fail a schema; see Database.

Clone this wiki locally