Skip to content

[http-server-js] Add support for body validation #9889

@benjlevesque

Description

@benjlevesque

Clear and concise description of the problem

Context

Typespec's JS emitter is a great approach to API servers! The type safety it brings is 👌

What I really miss is a validation mechanism. Currently the body available in the endpoint handler is properly typed, but is not actually validated against the schema. It gives a false sense of safety.

I've tried implementing my workaround(see below) in a policy, so it's executed before each endpoint, but this would mean to reimplement the routing logic (maybe related to #6933?).

Feature request

I'd love for this to be natively available.
If it's not desired (because of dependecy to a library for instance), a great option would be to add an optional validate function on the options of the router.

Something like this :

const router = createDemoServiceRouter(widgetService, {
  validate: (ctx, body) => {
    // ctx would need to contain at least the path, ideally also the related openAPI schema ? 
  }
}

Workaround

Here is my current workaround : I load the openApiDocument, inject the schema in AJV, and validate the body in each endpoint where I need it.

import { Ajv2020, ValidationError, ValidateFunction } from 'ajv/dist/2020.js';
import type { JTDDataType } from 'ajv/dist/core.js';
import addFormats from 'ajv-formats';

import { BadRequestError } from '../errors.js';

export const createSchemaValidator = <SchemasTypes extends Record<string, object>>(
  schemas: SchemasTypes,
) => {
  const ajv = new Ajv2020();
  addFormats.default(ajv);

  for (const [name, schema] of Object.entries(schemas)) {
    ajv.addSchema(schema, name);
  }

  const validate = <T extends Extract<keyof SchemasTypes, string>>(
    type: T,
    ...[data, dataCtx]: Parameters<ValidateFunction>
  ): JTDDataType<SchemasTypes[T]> => {
    const schema = ajv.getSchema<JTDDataType<SchemasTypes[T]>>(type);
    if (!schema) {
      throw new Error(`Schema ${type} not found`);
    }
    if (!schema(data, dataCtx)) {
      const error = new ValidationError(schema.errors ?? []);
      if (error.errors.length === 1) {
        const err = error.errors[0];
        const formattedError = err.message;
        const formattedMessage = err.instancePath
          ? `Validation failed (${err.instancePath})`
          : 'Validation failed';
        throw new BadRequestError(`${formattedMessage}: ${formattedError}`);
      }
      throw new BadRequestError(`Validation failed`, error.errors);
    }
    return data;
  };

  return validate;
};

Usage :

import { openApiDocument } from "./http/openapi3";
const schemas = openApiDocument.components.schemas;
const validate = createSchemaValidator(schemas);

// ...
// in endpoint handler
const { id, weight } = validate("Widget", body);
// typesafe, and validated against the open API schema.

Checklist

  • Follow our Code of Conduct
  • Read the docs.
  • Check that there isn't already an issue that request the same feature to avoid creating a duplicate.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions