ensure that uses the guard stops the function before the
rest of the body runs.
Define a reusable check
ThisLoggedIn guard requires an AppContext that has both a session and a
user.
- Forst
- Generated Go
ctx). The type after it is
the type the guard attaches to (AppContext). The name after the parentheses
is the rule (LoggedIn).
Use that name anywhere you can write an is check:
ctx.sessionId and ctx.user are present.
A type guard records what a passing check proves about the value. A Bool
return cannot do that. When LoggedIn() passes, later code can use the
session and user as present.
Attach a guard to a named type
Attach a guard to a declared named type or struct shape. Define a named scalar type when you want guards on values such as passwords:Strong then belongs to Password, not to every String in the program.
When to use which form
Use a guard inside if and ensure
Use if when both outcomes belong in the function. Use ensure with a guard
inside a branch when that branch only makes sense if more checks pass.
This handler ships a paid order. An unpaid order waits. A paid order must be
unlocked and must have items:
- Forst
- Generated Go
Unlocked is order.locked is False(). HasItems is order.items is Min(1).
If order.paid is false, fulfill returns "awaiting payment" and never
runs those checks. If it is paid, a locked order returns OrderLocked and an
empty cart returns EmptyCart. After both ensures pass, order.items[0] is
safe. After the if, Forst does not treat the order as Unlocked or
HasItems.
Keep guard bodies as checks
A guard body only checks the value in parentheses and any extra parameters. The same input must always produce the same result. You can useif, else if, else, is, and ensure. A guard cannot change
values, use return, or read unrelated variables.
If an if in a guard body does not match and has no else, the guard fails.
A guard also cannot return a named error with else Unauthorized{}. Put the
named error on the ensure that uses the guard:
or inside a guard when several conditions can satisfy the rule. See
Accept several valid cases.
Add fields to a request shape
API procedures often share one context (who is calling) and differ on input (what they sent). You write that as one request object with actx field and
an input field.
AppContext and LoggedIn are the ones from the start of this page. Every
procedure below already has ctx: AppContext. createTask adds a task name.
getTask adds a task id. Callers pass ctx and input together.
- Forst
- Generated Go
type AppMutation = MutationArg.Context(AppContext) means every procedure
already has ctx. .Input({ input: { name: String } }) adds this procedure’s
payload.
The call passes a session, a user, and a name. Inside createTask,
ensure req.ctx is LoggedIn() reuses the LoggedIn guard. After that check,
req.input.name is the string the caller sent, and req.ctx.sessionId is
present.
getTask is the same pattern with a different input. You do not repeat the
context field on each procedure.
How Input and Context add fields
MutationArg is a request you can add fields to. In the example above, the
Input and Context guards require a field to exist and give it a type. The
names in parentheses (input, ctx) are those field types, not values at
runtime.
ensure req is { input } means the request must have an input field of the
type passed into the guard. AppMutation.Input({ input: { name: String } }) is
that guard applied to createTask.
Request shapes that have no Forst name become hashed Go types in the Generated
Go tab. Input and Context compile to helpers that return true; the type
checker already required those fields. A failed LoggedIn check returns
errors.New("ensure req.ctx is AppContext.LoggedIn(): want AppContext.LoggedIn()").
Caveats
Type guards are experimental. The examples above parse, type-check, and compile to Go. See the roadmap. A guard checks the value once at anensure or if. Later changes do not run
the guard again. A write to a field the check used drops the fact. See
When a later write drops the fact
for field writes, pointer fields, and concurrency.
Narrowing after a check on a simple name (ensure ctx is LoggedIn()) is the
reliable case. A check on a dotted path (ensure req.ctx is LoggedIn()) still
has gaps. Editor hover may show the guard while later lines still see the
original type.
Narrowing inside an if branch may end with that branch. Code after the full
chain can see the earlier type.
A shape guard such as Input verifies that a field exists with the required
shape. It never creates or fills that field.
Generated Go uses functions or inline conditions for guard checks. An ensure
that uses the guard follows the usual Go error return path.
See Check a value for general
narrowing limits.
Examples
See the type guard examples on GitHub:Related
Continue with built-in constraints or apply guards withensure.
Describe a shape
Built-in constraints on primitive fields.
Check a value
Using ensure … is … at call sites.