rasengan-dev/agent-skills

rasengan-server-validation

Schema validation patterns for @rasenganjs/server using @rasenganjs/validators.

Ver código-fonte

Como usar

  1. Copie o comando e verifique o pacote antes de instalar no seu ambiente de agente.
  2. Consulte no original as ferramentas, credenciais e ambientes necessários.
  3. Descreva os dados de entrada e a entrega esperada; confira a saída com as fontes.
npx skills add https://github.com/rasengan-dev/agent-skills --skill rasengan-server-validation

Antes de começar

  • A instalação não inclui assinatura de modelo, franquia API ou acesso aos seus arquivos.
  • Estrelas e instalações indicam atividade, não um teste independente de qualidade ou segurança.
Documento original do Skill

Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.

@rasenganjs/server Validation Patterns

When to Activate

  • Adding body/params/query validation to a route
  • Deciding between per-route schemas and a controller-level schemas dictionary
  • Debugging why a controller-level schema isn't being applied to a handler
  • Customizing the validation error response shape or status code
  • Swapping the default Zod adapter for another schema library

Configuring the Adapter

@rasenganjs/server defaults to zodAdapter with a built-in 400 JSON error handler. Override either via app.configureValidation():

ts
// src/main.ts
import { bootstrap } from '@rasenganjs/server';
import { zodAdapter } from '@rasenganjs/validators';
import appModule from './app.module';

bootstrap(async (app) => {
  app.registerModule(appModule);

  app.configureValidation({
    adapter: zodAdapter,
  });
});

Rules:

  • configureValidation() merges with the defaults — pass only the field you want to change (adapter and/or onError)
  • @rasenganjs/validators is a required dependency of @rasenganjs/server, not optional
  • The default adapter requires Zod to be installed; a custom SchemaAdapter ({ parse(schema, data), infer(schema) }) can back any schema library (Valibot, ArkType, ...)

Per-Route Schemas

Pass a SchemaDefinition ({ body?, params?, query?, onError? }) as the last argument to any Router verb method:

ts
import { Controller, type RouteHandler, type Router } from '@rasenganjs/server';
import z from 'zod';

const UserParamSchema = z.object({
  id: z.coerce.number({ message: 'must be number' }),
});
const UserQuerySchema = z.object({
  page: z.coerce.number({ message: 'must be number' }).optional(),
  limit: z.coerce.number({ message: 'must be number' }).optional(),
});

export class UserController extends Controller {
  routes(router: Router) {
    router.get(
      '/:id',
      this.findOne,
      { params: UserParamSchema, query: UserQuerySchema }
    );
    router.post('/', this.create, { body: z.object({ name: z.string() }) });
  }

  findOne: RouteHandler = async (ctx) => {
    // ctx.params.id is a number here (coerced + validated)
    return ctx.res.json({ id: ctx.params.id });
  };

  create: RouteHandler = async (ctx) => {
    return ctx.res.json(ctx.body); // ctx.body typed as { name: string }
  };
}

To get ctx.body/ctx.params/ctx.query typed from the schema, annotate the handler's type with RouteHandler<typeof schema>:

ts
create: RouteHandler<{ body: typeof CreateUserSchema }> = async (ctx) => {
  ctx.body.name; // typed as string
};

Controller-Level Schemas

Declare a schemas dictionary keyed by method name; it's matched automatically against handlers registered without a per-route schema:

ts
import { Controller, type RouteHandler, type Router } from '@rasenganjs/server';
import { UserService } from './user.service';
import z from 'zod';

const UserParamSchema = z.object({
  id: z.coerce.number({ message: 'must be number' }),
});
const UserQuerySchema = z.object({
  page: z.coerce.number({ message: 'must be number' }).optional(),
  limit: z.coerce.number({ message: 'must be number' }).optional(),
});

export class UserController extends Controller {
  schemas = {
    findOne: { params: UserParamSchema, query: UserQuerySchema },
    findAll: { query: UserQuerySchema },
    create: { body: z.object({ name: z.string() }) },
  } as const;

  constructor(private userService: UserService) {
    super();
  }

  routes(router: Router) {
    router.get('/', this.findAll);
    router.get('/:id', this.findOne, this.schemas.findOne);
    router.post('/', this.create, this.schemas.create);
  }

  findAll: RouteHandler = async (ctx) => {
    const list = await this.userService.findAll();
    return ctx.res.json(list);
  };

  findOne: RouteHandler<typeof this.schemas.findOne> = async (ctx) => {
    const user = await this.userService.findById(ctx.params.id);
    if (!user) return ctx.res.status(404).json({ error: 'User not found' });
    return ctx.res.json(user);
  };

  create: RouteHandler<typeof this.schemas.create> = async (ctx) => {
    const user = await this.userService.create(ctx.body);
    return ctx.res.json(user);
  };
}

Rules:

  • Matching is done by handler.name at route-registration time — pass this.findOne (a class method reference) as the handler, not an inline arrow function, for name-based matching to work
  • Gotcha: arrow functions assigned to class fields (findOne: RouteHandler = async (ctx) => {...}) still have a .name (the field name, e.g. "findOne"), so the pattern above works — but a genuinely anonymous or reassigned/bound function (this.findOne.bind(this), an inline lambda passed directly to router.get) loses or changes its .name and won't match. For those, pass the schema explicitly as the route's last argument instead of relying on schemas
  • Precedence: a per-route schema argument always overrides a controller-level schemas entry of the same handler name — pass both only when you intend the per-route one to win
  • this.schemas.findOne as the third argument and the schemas dictionary matching are not mutually exclusive — passing it explicitly is redundant but harmless; omitting it relies purely on name matching

Custom Validation Error Handler

Global, via configureValidation:

ts
app.configureValidation({
  onError: (errors, ctx) =>
    Response.json({ code: 'VALIDATION_ERROR', errors }, { status: 422 }),
});

Per-route, via the schema's onError field (overrides the global handler for that route only):

ts
router.post('/users', handler, {
  body: CreateUserSchema,
  onError: (errors, ctx) =>
    Response.json({ code: 'INVALID_USER', errors }, { status: 422 }),
});

errors is ValidationError[], each { path: (string | number)[], message: string, code?: string }. The default handler returns Response.json({ errors }, { status: 400 }).

Rules:

  • Validation middleware runs after route-level middleware and before the handler, injected automatically at route-registration time whenever a schema is resolved (per-route or controller-level)
  • A route with no schema at all skips validation entirely — no perf cost

Relacionados por tarefa

do mesmo repositório

Mais Skills

Todos os Skills