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

# Shapes and constraints

> Define API boundaries as types with built-in validation.

Whenever you deal with untrusted data, you need validation as far up as possible. Have the compiler prove these rules where values are known at compile time, and have it enforce them at runtime as well.

## Manual validation in Go

Go handlers often repeat input validation manually:

```go theme={"languages":{"custom":["/languages/forst.json"]}}
type PlaceOrderInput struct {
	StockKeepingUnit string
	Quantity         int
}

func placeOrder(in PlaceOrderInput) (string, error) {
	if len(in.StockKeepingUnit) < 1 || len(in.StockKeepingUnit) > 64 {
		return "", errors.New("invalid stock keeping unit")
	}
	if in.Quantity < 1 || in.Quantity > 99 {
		return "", errors.New("invalid quantity")
	}
	// business logic...
}
```

Go structs define field names and types, and interfaces specify required methods, but neither can express detailed validation rules like value ranges or formats.

## Constraints on types

In Forst, types can declare exactly what makes data valid. You attach constraints directly to types, and the compiler enforces them at the API boundary and, where possible, at compile time. This keeps validation clear and central—no more scattered checks.

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    type PlaceOrderInput = {
    	stockKeepingUnit: String.Min(1).Max(64),
    	quantity:         Int.Min(1).Max(99),
    }

    func placeOrder(in: PlaceOrderInput) {
    	// in is already validated; business logic starts here
    }
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    type PlaceOrderInput struct {
    	stockKeepingUnit string
    	quantity         int
    }

    func placeOrder(in PlaceOrderInput) {
    	if len(in.stockKeepingUnit) < 1 || len(in.stockKeepingUnit) > 64 { ... }
    	if in.quantity < 1 || in.quantity > 99 { ... }
    	// business logic starts here
    }
    ```
  </Tab>

  <Tab title="Generated TypeScript">
    ```typescript theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst generate → generated/types.d.ts
    export interface PlaceOrderInput {
      quantity: number;
      stockKeepingUnit: string;
    }
    ```
  </Tab>
</Tabs>

When `placeOrder` runs, the compiler has already proven the shape at compile time where values are known; use `ensure` (or explicit checks) for dynamic input. Invalid SKUs and quantities never reach your catalog logic.

<Note>
  Forst also lets you define **your own** rules for types with [type guards](/docs/language/type-guards).
</Note>

## Built-in constraints

Constraints appear in two places:

| Context                       | Syntax                  | Purpose                                   |
| ----------------------------- | ----------------------- | ----------------------------------------- |
| **On types** (shapes, params) | `String.Min(1).Max(64)` | Validate at the boundary when data enters |
| **In function bodies**        | `ensure x is Min(1)`    | Assert invariants at runtime              |

The same built-in names work in both. For `Result` narrowing (`Ok`, `Err`) and pointer checks (`Nil`, `Present`), see [Ensure and narrowing](/docs/language/ensure-and-narrowing).

### Chaining

Constraints **chain with dots** on the base type. All chained rules must pass:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    name:     String.Min(3).Max(10).NotEmpty()
    quantity: Int.Min(1).Max(99)
    tags:     Array(String).Min(1).Max(10)
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build — constraints on struct fields
    name     string   // len(name) checked at boundary
    quantity int      // numeric range checked at boundary
    tags     []string // len(tags) checked at boundary
    ```
  </Tab>
</Tabs>

### Reference

| Type                | Constraints                             | Meaning                         |
| ------------------- | --------------------------------------- | ------------------------------- |
| **`String`**        | `.Min(n)`, `.Max(n)`                    | String **length** bounds        |
|                     | `.HasPrefix("…")`                       | Must start with literal prefix  |
|                     | `.Contains("…")`                        | Must contain substring          |
|                     | `.NotEmpty()`                           | `len != 0`                      |
| **`Int`**           | `.Min(n)`, `.Max(n)`                    | Inclusive numeric bounds        |
|                     | `.LessThan(n)`                          | Strictly below `n`              |
|                     | `.GreaterThan(n)`                       | Strictly above `n`              |
| **`Float`**         | `.Min(n)`, `.Max(n)`, `.GreaterThan(n)` | Same numeric semantics as `Int` |
| **`Array(T)`**      | `.Min(n)`, `.Max(n)`                    | **Element count** bounds        |
|                     | `.NotEmpty()`                           | At least one element            |
| **`Bool`**          | `.True()`, `.False()`                   | Must be `true` or `false`       |
| **`*T` (pointers)** | `.Nil()`, `.Present()`                  | Optional vs required fields     |

Use constraints on struct fields, function parameters, and nested shapes. Validation follows the data structure, including deeply nested records.

Nested constraints on an order input, including tags on the payload:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    type PlaceOrderInput = {
    	stockKeepingUnit: String.Min(1).Max(64),
    	quantity:         Int.Min(1).Max(99),
    	tags:             Array(String).Max(10),
    }
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    type PlaceOrderInput struct {
    	stockKeepingUnit string
    	quantity         int
    	tags             []string
    }
    ```
  </Tab>
</Tabs>

**Ensure-only or reserved** (not typically chained on types):

* **`Ok()` / `Err()`**: `Result` discriminants; use in `ensure x is Ok()` ([Ensure and narrowing](/docs/language/ensure-and-narrowing))
* **`Valid()`**: reserved placeholder; prefer explicit guards
* **`Value(…)`**: literal refinement in assertion types (advanced)

## Structural shapes

Forst shapes use **field names** (camelCase by convention) and compose like TypeScript interfaces:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    type Address = {
    	line1:   String.Min(1),
    	city:    String.Min(1),
    	zipCode: String.Min(5).Max(10),
    }

    type Customer = {
    	name:    String.Min(1),
    	address: Address,
    }
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    type Address struct {
    	line1   string
    	city    string
    	zipCode string
    }

    type Customer struct {
    	name    string
    	address Address
    }
    ```
  </Tab>
</Tabs>

Invalid nested data fails at the same boundary. You do not re-walk the tree in application code.

## Caveats

### Use `ensure` for runtime checks today

Constraints on types document the rules. The compiler does **not** emit automatic parameter checks before your function body yet. Use **`ensure`** explicitly inside handlers for runtime validation.

### Compile time vs runtime

The compiler proves constraints only where values are known at compile time. Dynamic or untrusted input still needs **`ensure`** at the boundary.

### Wire serialization

By default, shape struct fields lower to **unexported** Go identifiers with **no** `json` tags. For HTTP or JSON marshaling, pass **`-export-struct-fields`** on `forst run` / `forst build`, or set **`compiler.exportStructFields`: true** in `ftconfig.json`.

### Built-in semantics still maturing

The typechecker does not fully validate every built-in constraint beyond presence wiring. **`Valid()`** is a reserved placeholder. Prefer explicit constraints or [type guards](/docs/language/type-guards).

Generated TypeScript types reflect structure only. Client-side validation is your job unless you call the Forst server. See [Generate client types § Caveats](/docs/interop/node/generate-types#caveats).

<Info>
  Constraint and emission behavior is still expanding. See the [roadmap](/docs/resources/roadmap).
</Info>

## What gets emitted

The compiler lowers **`ensure`** checks in the function body to plain Go: `String.Min(1)` becomes a `len(field)` comparison, `Int.Min(1)` a numeric comparison, and `HasPrefix` / `Contains` call `strings.*` with an auto-added import.

## Try it

From the Forst repository:

```bash theme={"languages":{"custom":["/languages/forst.json"]}}
task example:basic
task example:ensure
```

Examples live under [`examples/in/`](https://github.com/forst-lang/forst/tree/main/examples/in/).

## Related

<CardGroup cols={2}>
  <Card title="Ensure and narrowing" icon="filter" href="/docs/language/ensure-and-narrowing">
    Assert invariants inside functions with `ensure … is …`.
  </Card>

  <Card title="Type guards" icon="code-branch" href="/docs/language/type-guards">
    Reusable custom predicates on your domain types.
  </Card>
</CardGroup>
