ent-framework
  • Ent Framework
  • Getting Started
    • Code Structure
    • Connect to a Database
    • Create Ent Classes
    • VC: Viewer Context and Principal
    • Ent API: insert*()
    • Built-in Field Types
    • Ent API: load*() by ID
    • N+1 Selects Solution
    • Automatic Batching Examples
    • Ent API: select() by Expression
    • Ent API: loadBy*() Unique Key
    • Ent API: update*()
    • Ent API: deleteOriginal()
    • Ent API: count() by Expression
    • Ent API: exists() by Expression
    • Ent API: selectBy() Unique Key Prefix
    • Ent API: upsert*()
    • Privacy Rules
    • Validators
    • Triggers
    • Custom Field Types
  • Ent API: Configuration and Types
  • Scalability
    • Replication and Automatic Lag Tracking
    • Sharding and Microsharding
    • Sharding Terminology
    • Locating a Shard and ID Format
    • Sharding Low-Level API
    • Shard Affinity and Ent Colocation
    • Inverses and Cross Shard Foreign Keys
    • Shards Rebalancing and pg-microsharding Tool
    • Connection Pooling
  • Advanced
    • Database Migrations and pg-mig Tool
    • Ephemeral (Symbol) Fields
    • Atomic Updates and CAS
    • Custom Field Refactoring
    • VC Flavors
    • Query Cache and VC Caches
    • Loaders and Custom Batching
    • PostgreSQL Specific Features
    • Query Planner Hints
    • Cluster Maintenance Queries
    • Logging and Diagnostic Tools
    • Composite Primary Keys
    • Passwords Rotation
  • Architecture
    • Abstraction Layers
    • Ent Framework, Meta’s TAO, entgo
    • JIT in SQL Queries Batching
    • To JOIN or not to JOIN
Powered by GitBook
On this page
  • Field Validators
  • Whole-Row Validators
  • Using with Zod or Standard Schema
  • Running Validators Manually

Was this helpful?

Edit on GitHub
  1. Getting Started

Validators

Validators are predicates, similar to what you use in privacyInsert/Update Privacy Rules. They are called at the same time, and the error messages (if any) are accumulated to build and throw a compound EntValidationError instance.

Field Validators

Field validators are executed on every insert*() and upsert*() call.

Also, they are fired when an update*() call touches the fields that the validators are attached to. The untouched fields do not trigger re-validation.

export class EntComment extends BaseEnt(cluster, schema) {
  static override configure() {
    return new this.Configuration({
      privacyLoad: [...],
      privacyInsert: [...],
      validators: [
        new FieldIs(
          "message",
          (value, _row, _vc) => value.trim().length > 0,
          "Please provide comment text",
        ),
        new FieldIs(
          "topic_id",
          async (value, _row, vc) => {
            const topic = await EntTopic.loadX(vc, value);
            return Date.now() - topic.created_at.getTime() < 1000 * 3600 * 24;
          },
          "You can only leave comments on topics created today",
        ),
        ...
      ]
    });
  }
}

If you want to build your own custom validation predicate similar to FieldIs, make sure that it implements AbstractIs interface. Otherwise, you won't be able to use it in validators block.

Validators have so much in common with privacy rules that internally, the whole Ent Framework's privacy engine is called Validation.

The use case for validators is enforcing some early integrity checks on Ent fields before saving the Ent to the database. Putting this logic as close to the database layer as possible brings expra firmness to the architecture.

try {
  const comment = EntComment.insertReturning(vc, {
    topic_id: topic.id,
    creator_id: vc.principal,
    message: request.body.message,
  });
  ...
} catch (e: unknown) {
  if (e instanceof EntValidationError) {
    return res.json({
      errors: e.errors.map((e) => ({
        field: e.field, // null if relates to the whole row
        message: e.message,
      })),
    });
  } else {
    throw e;
  }
}

Whole-Row Validators

You can also define RowIs validators that operate with the entire row to be inserted or updated. As opposed to FiledIs, such validators are fired independently on which fields you are modifying.

export class EntComment extends BaseEnt(cluster, schema) {
  static override configure() {
    return new this.Configuration({
      privacyLoad: [...],
      privacyInsert: [...],
      validators: [
        new RowIs(
          async (row, vc) => checkForSpam(vc, row),
          "Comment spam checking failed",
        ),
        ...
      ]
    });
  }
}

Using with Zod or Standard Schema

import { z } from "zod";

validators: [
  // Use Zod's default generated message
  new FieldIs(
    "message",
    async (value) => z.string().min(10).safeParseAsync(value),
  ),
  // Custom error message.
  new FieldIs(
    "message",
    (value) => z.string()
      .min(10, "Text must be longer than 10 characters")
      .safeParse(value),
  ),
  // Validation of the entire row.
  new RowIs(
    (row) => z.object({
      title: z.string().min(1),
      message: z.string().min(10),
    }).safeParse(row),
  ),
  ...
]

Running Validators Manually

Every Ent class exposes a special "constant" VALIDATION static property that allows you to run fields validators manually if needed. Read more about this in Ent API: Configuration and Types.

PreviousPrivacy RulesNextTriggers

Last updated 15 days ago

Was this helpful?

You can also use or any validation library compatibe with :

Basically, when you omit the last message parameter of FieldIs or RowIs constructors, then it's expected that your validator callback returns an object compatible with Zod's or Standard Schema's result shape.

Zod
Standard Schema
safeParse()
validate()