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:

  • number → z.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.