Skip to content

Validation Guide

This guide explains how validation works in Vona within the Cabloy monorepo.

Why validation matters in Vona

Vona builds validation around Zod and integrates it directly into controller argument handling, DTOs, entities, and OpenAPI generation.

That means validation is not an isolated input-checking step. It is part of the contract layer shared across runtime behavior, types, and API documentation.

Automatic schema inference

One very important convenience is that if a parameter type is a basic type, DTO, or entity, Vona can automatically infer the corresponding Zod schema.

Representative cases:

  • numberz.number()
  • DTO → z.object({...})
  • Entity → z.object({...})

This matters because Vona tries to keep the contract surface concise while still producing strong runtime validation.

In controller AOP terms, this usually means built-in argument handling is enough for many request parameters without creating a custom pipe or argument pipe first.

Explicit schema rules

You can also pass explicit schema rules when automatic inference is not enough.

Representative pattern:

typescript
findOne(@Arg.query('id', z.number().min(6)) id: number) {}

Extending the inferred schema

Inferred schemas can also be extended through helper tools like:

  • v.optional
  • v.nullable
  • v.default
  • v.array
  • v.lazy

That is important because many real validation cases need augmentation rather than total replacement.

Scene-aware readonly input sanitization

DTO and field OpenAPI metadata can identify a Cabloy schema scene and its scene-specific field behavior. After a schema parses successfully, Vona removes each known property whose effective rest metadata resolves to readonly: true for that scene.

This is server-side input sanitization, not a browser-only disabled control and not a validation error. A caller cannot turn a read-only field into a write channel by bypassing a Zova form.

For form-view, form-create, and filter, Vona resolves field metadata in this order:

  1. base rest metadata
  2. shared rest.form metadata
  3. the exact scene metadata, such as rest['form-create']

For table and form, only the base metadata and exact-scene metadata apply. A nested DTO inherits the containing scene unless it declares its own rest.schemaScene.

The sanitization traverses nested DTO/object values and array elements, including framework lazy and chained schema composition. For example, a create request can prevent callers from supplying a line-item price while still accepting its quantity:

typescript
class DtoLineItem {
  @Api.field(v.openapi({ rest: { form: { readonly: true } } }))
  price: number;

  @Api.field()
  quantity: number;
}

export class DtoOrderCreate extends $Dto.create(() => ModelOrder) {
  @Api.field(v.array(DtoLineItem))
  items: DtoLineItem[];
}

// request input
{
  items: [{ price: 99, quantity: 2 }];
}

// validated value delivered to the handler
{
  items: [{ quantity: 2 }];
}

Use DTO projection to omit a field that the API must never accept or return. Use scene-aware readonly when a field belongs to the schema but must be removed from write input for one contract scene. This sanitization is not response serialization; response payload shaping remains a separate concern in the Serialization Guide. General object parsing and unknown-key behavior still belong to the supplied Zod schema; readonly sanitization removes known protected fields only after successful parsing.

A useful distinction is:

  • v.optional means the field may be omitted
  • v.nullable means the field may carry a real null

For query parameters, that distinction matters because plain optional values still collapse null to omission, while v.optional(), v.nullable() allows a real null to survive parsing when the contract needs SQL IS NULL semantics downstream.

@Arg.filter

The validation layer also supports more advanced query-style inputs through @Arg.filter, which ties into DTO/query helper structures.

This is a good example of validation being connected to higher-level query semantics, not only primitive field checks.

Tool groups

These helper tools fall into groups such as:

  • basic tools
  • string tools
  • OpenAPI tools
  • serializer tools
  • Zod tools

For response-side shaping with those serializer helpers, see Serialization Guide.

  • query filter tools
  • special tools like v.tableIdentity

This matters because the right answer is often “use the existing helper vocabulary” instead of hand-writing a one-off schema pattern.

Implementation checks for request-validation changes

When changing request contracts, ask:

  1. can Vona infer the schema automatically?
  2. does the contract need explicit extension through the v helpers?
  3. does the same validation surface also feed OpenAPI and DTO behavior?
  4. is the validation logic better expressed at the controller, DTO, or entity layer?
  5. for a scene-aware DTO, what is its effective schema scene and does each protected field resolve to read-only there?
  6. when protected fields can be nested, have object and array inputs been tested separately?

That produces more consistent backend contracts.

For the broader request-path model around pipes, guards, middleware, interceptors, and filters, see Controller AOP Guide. Captcha verification flows commonly intersect with this request-path layer through a local interceptor; see Captcha Guide.

Released under the MIT License.