> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Validation

> Define type-safe schemas with @ooneex/validation and built-in constraints that validate route input automatically before a controller runs.

The `@ooneex/validation` component is a type-safe validation layer. You describe the shape of your data once with `Assert({...})`, and the same schema both narrows your TypeScript types and validates values at runtime. The package provides `Assert`, `scope`, `union`, and `intersection`, adds a set of ready-made constraints (email, URL, port, country code, and more), and exposes an abstract `Validation` class for building your own reusable validators. When a schema is attached to a route, the framework runs it for you before the controller is reached.

## Why this component

* **One schema, two jobs.** An `Assert({...})` definition is both a runtime validator and a static TypeScript type — no duplicate interfaces to keep in sync.
* **Concise schema syntax.** `Assert`, `scope`, `union`, and `intersection` give you a full string-based syntax (`string.email`, `number >= 0`, `'a' | 'b'`, optional keys with `?`).
* **Automatic route validation.** Schemas passed to `@Route.http` as `params`, `queries`, or `payload` are validated before the controller runs; invalid requests produce an error response automatically.
* **Built-in constraints.** Pre-built validators cover common shapes — email, URL, port, hostname, locale, currency, country code, hex color, names, IDs — so you do not re-write the same rules.
* **Custom validators.** Extend the abstract `Validation` class with `getConstraint` and `getErrorMessage` to package any rule (plus a friendly message) as a reusable class.

## How it works

A schema is a function. Calling it with data returns either the validated value or an `Assert.errors` object. The `Validation` class wraps that flow into a `{ isValid, message }` result, and the routing layer applies your schemas to incoming requests automatically.

| Stage       | What happens                                                                                                                                           |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Define      | `Assert({...})` builds a schema that is both a runtime validator and a static type.                                                                    |
| Validate    | Calling the schema returns the parsed value, or an instance of `Assert.errors` with a `.summary` describing the failure.                               |
| Route input | Schemas on `@Route.http` (`params`, `queries`, `payload`) run before the controller; a failure yields an error response and the controller is skipped. |
| Custom rule | A `Validation` subclass turns a schema and an optional message into a `validate(data)` method returning `{ isValid, message? }`.                       |

## Defining schemas

Use `Assert` to describe an object. Each value is a schema expression — a primitive, a constrained primitive, a numeric range, a literal union, or an array. Mark a key optional by suffixing it with `?`.

```typescript theme={null}
import { Assert } from "@ooneex/validation";

const UserSchema = Assert({
  name: "string > 0",          // non-empty string
  email: "string.email",       // valid email
  age: "number.integer >= 0",  // non-negative integer
  "phone?": "string",          // optional
  role: "'admin' | 'user'",    // literal union
  tags: "string[]",            // array of strings
});

const result = UserSchema({ name: "John", email: "john@example.com", age: 30, role: "user", tags: [] });

if (result instanceof Assert.errors) {
  console.error(result.summary); // human-readable failure description
} else {
  console.log(result); // fully typed, validated value
}
```

For related types, group them in a `scope` and `export()` the result; combine schemas with `union` and `intersection`.

```typescript theme={null}
import { scope } from "@ooneex/validation";

const $ = scope({
  profile: {
    firstName: "string > 0",
    lastName: "string > 0",
    bio: "string | null",
  },
  user: {
    id: "string.uuid",
    email: "string.email",
    profile: "profile",
  },
}).export();

const user = $.user({
  id: crypto.randomUUID(),
  email: "a@b.com",
  profile: { firstName: "Jane", lastName: "Doe", bio: null },
});
```

## Validation in routing

The most common place you use validation is on a route. Pass schemas to `@Route.http` and the framework validates the request before your controller's `index` runs — so inside the controller the data is already guaranteed to match.

```typescript theme={null}
import { Route } from "@ooneex/routing";
import { Assert } from "@ooneex/validation";
import type { ContextType, IController } from "@ooneex/controller";

const CreateUserPayload = Assert({
  name: "string > 0",
  email: "string.email",
  password: "string >= 8",
});

const UserIdParam = Assert({
  id: "string.uuid",
});

@Route.http({
  name: "api.users.create",
  path: "/api/users",
  method: "POST",
  payload: CreateUserPayload,
})
export class UserCreateController implements IController {
  public async index(context: ContextType) {
    // Already validated — invalid requests never reach here
    const { name, email } = context.payload;
    return context.response.json({ user: { name, email } });
  }
}
```

* `params` validates path parameters (e.g. `:id` in the path).
* `queries` validates the query string.
* `payload` validates the request body.

If any of them fail, the request is rejected with an error response and the controller is never invoked. See [Routing](/basics/routing) for the full route options, [Request](/basics/request) for how input reaches the context, and [Response](/basics/response) for the shape of the returned errors.

## Built-in constraints

Import these from `@ooneex/validation/constraints`. Each is a `Validation` subclass — construct it and call `validate(data)` to get a `{ isValid, message? }` result.

```typescript theme={null}
import { AssertEmail, AssertPort } from "@ooneex/validation/constraints";

const result = new AssertEmail().validate("user@example.com");
console.log(result.isValid); // true
```

| Constraint          | Validates                                                                                                          |
| ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `AssertEmail`       | A valid email address.                                                                                             |
| `AssertUrl`         | A URL (1–2083 chars, `http`/`https`, no `..` or trailing dots); also offers `validateStrict` requiring a protocol. |
| `AssertYoutubeUrl`  | A YouTube watch URL (`youtube.com/watch?v=…` or `youtu.be/…`).                                                     |
| `AssertHostname`    | A hostname or IPv4 address (also accepts an empty string).                                                         |
| `AssertPort`        | An integer port between 1 and 65535.                                                                               |
| `AssertHexaColor`   | A 3- or 6-digit hex color (e.g. `#fff`, `#A1B2C3`).                                                                |
| `AssertCountryCode` | A 2-letter uppercase ISO 3166-1 alpha-2 code.                                                                      |
| `AssertCurrency`    | A valid ISO 4217 currency code (3 uppercase letters).                                                              |
| `AssertLocale`      | A supported locale code from `@ooneex/translation`.                                                                |
| `AssertAppEnv`      | A valid application environment value.                                                                             |
| `AssertName`        | A name (1–50 chars, letters/numbers/spaces/`-'.`, trimmed).                                                        |
| `AssertFirstName`   | A first name (1–50 chars, letters, spaces, hyphens, apostrophes).                                                  |
| `AssertLastName`    | A last name (1–50 chars, letters, spaces, hyphens, apostrophes).                                                   |
| `AssertId`          | A 25-character lowercase hexadecimal ID.                                                                           |
| `AssertDescription` | Free text between 1 and 5000 characters.                                                                           |
| `AssertChatQuery`   | Chat text (1–2000 chars) with no HTML, scripts, or unsafe protocols.                                               |

## Custom validators

To package a rule of your own, extend the abstract `Validation` class. It requires two methods and gives you `validate` for free.

```typescript theme={null}
public abstract getConstraint(): AssertType;       // the schema to check against
public abstract getErrorMessage(): string | null;  // custom message, or null for the default summary
```

`validate(data, constraint?)` calls the schema; on failure it returns your `getErrorMessage()` (falling back to the schema's `summary`), otherwise `{ isValid: true }`.

```typescript theme={null}
import { Validation, Assert } from "@ooneex/validation";
import type { AssertType } from "@ooneex/validation";

class PositiveNumberValidator extends Validation {
  public getConstraint(): AssertType {
    return Assert("number > 0");
  }

  public getErrorMessage(): string | null {
    return "Value must be a positive number";
  }
}

const result = new PositiveNumberValidator().validate(42);
console.log(result.isValid); // true
```

This is the same pattern the built-in constraints use, and the same one you reach for when validating [Entity](/components/entity) fields with a shared, named rule.

## Inline assertions and utilities

`Assert` builds a schema you can call immediately to check a value — useful for one-off assertions without defining a named schema.

```typescript theme={null}
import { Assert } from "@ooneex/validation";

const out = Assert("string.email")("john@example.com");
const isInvalid = out instanceof Assert.errors;
```

`jsonSchemaToTypeString(schema)` converts a JSON Schema object into a readable TypeScript-style type string — handling primitives, arrays, objects with required/optional keys, and `anyOf`/`oneOf`/`allOf` unions and intersections. Pair it with a schema's `toJsonSchema()` for documentation or AI-output descriptions.

```typescript theme={null}
import { Assert, jsonSchemaToTypeString } from "@ooneex/validation";

const ProductSchema = Assert({ name: "string", price: "number" });
console.log(jsonSchemaToTypeString(ProductSchema.toJsonSchema())); // "{ name: string; price: number }"
```

## Types

* **`AssertType`** — alias for the schema `Type`; the return type of `getConstraint` and the type of any schema.
* **`IAssert`** — the interface implemented by every validator: `getConstraint`, `getErrorMessage`, and `validate`.
* **`ValidationResultType`** — the result of `validate`: `{ isValid: boolean; message?: string }`.

## Best practices

* **Validate at the edge.** Attach schemas to `@Route.http` (`params`, `queries`, `payload`) so bad input is rejected before any business logic runs.
* **Define schemas once.** Export a schema and reuse it for routing, custom validators, and JSON Schema generation — never re-describe the same shape.
* **Prefer built-in constraints.** Use `AssertEmail`, `AssertPort`, and friends instead of re-writing common rules; extend `Validation` only for genuinely new ones.
* **Give helpful messages.** Return a clear `getErrorMessage()` so failures explain what was expected rather than leaning on the raw `summary`.
* **Mark optionals explicitly.** Use the `?` key suffix for optional fields so the static type and runtime check agree.

See [Routing](/basics/routing), [Request](/basics/request), [Response](/basics/response), and [Entity](/components/entity) for the surfaces that consume these schemas.
