Skip to content

Commit

Permalink
feat(validate): validate the data with existing schema
Browse files Browse the repository at this point in the history
  • Loading branch information
moontai0724 committed Jun 9, 2024
1 parent 506ee2b commit 482e35c
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 1 deletion.
38 changes: 37 additions & 1 deletion src/openapi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
PathItemObject,
SchemaObject,
} from "@moontai0724/openapi-types";
import Ajv, { type Options as AjvOptions } from "ajv";
import Ajv, { type ErrorObject, type Options as AjvOptions } from "ajv";
import YAML from "json-to-pretty-yaml";

import {
Expand Down Expand Up @@ -103,6 +103,42 @@ export class OpenAPI {
return ajvInstance;
}

/**
* Validate the data against the defined schemas. Only validate the schema if
* the key present in the data.
*
* @param path API endpoint path, used as key in paths object.
* @param method HTTP method that this operation is for, will overwrite
* existing path item if it exists.
* @param data Data object to be validated. The key is the schema name. If the
* key is not present in the data, the schema will not be validated.
* @param options Options for Ajv instance.
* @returns Validation result of each schema.
*/
public validate(
path: `/${string}`,
method: HttpMethod,
data: Partial<Record<keyof OperationSchemas, unknown>>,
options: AjvOptions,
) {
const ajvInstance = this.getAvjInstance(path, method, options);
const validationResult: Record<
keyof OperationSchemas,
ErrorObject[] | null | undefined
> = Object.entries(data).reduce(
(acc, [key, value]) => {
ajvInstance.validate(key, value);

Object.assign(acc, { [key]: ajvInstance.errors });

return acc;
},
{} as Record<keyof OperationSchemas, ErrorObject[] | null>,
);

return validationResult;
}

/**
* Stringify the document to JSON format.
* @returns OpenAPI document string in JSON format.
Expand Down
170 changes: 170 additions & 0 deletions src/openapi/validate.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Type } from "@sinclair/typebox";
import type { Options as AjvOptions } from "ajv";
import { beforeAll, beforeEach, expect, it, vi } from "vitest";

import type { OperationSchemas } from "../transformers";

const transformPathItem = vi.fn();

vi.mock("../transformers", () => ({
transformPathItem,
}));

let OpenAPI: typeof import("..").OpenAPI;

beforeAll(async () => {
OpenAPI = await import("..").then((m) => m.OpenAPI);
});

const baseOpenAPIDocument = {
openapi: "3.1.0",
info: {
title: "Example API",
version: "1.0.0",
},
};

let openapi: InstanceType<typeof OpenAPI>;

beforeEach(() => {
openapi = new OpenAPI(baseOpenAPIDocument);
});

const path = "/";
const method = "patch";
const schemas: OperationSchemas = {
body: Type.Object({
body1: Type.String(),
}),
cookie: Type.Object({
cookie1: Type.String(),
}),
header: Type.Object({
header1: Type.String(),
}),
path: Type.Object({
path1: Type.String(),
}),
query: Type.Object({
query1: Type.String(),
}),
response: Type.Object({
response1: Type.String(),
}),
};

it("should throw error if no schema found", () => {
const ajvOptions: AjvOptions = {
strictSchema: true,
};

expect(() => openapi.validate(path, method, {}, ajvOptions)).toThrowError(
"No schema found for PATCH /",
);
});

it("should be able to validate the defined schemas", () => {
const ajvOptions: AjvOptions = {
strictSchema: true,
};

openapi.define(path, method, schemas);

const validationResult = openapi.validate(
path,
method,
{
body: {
body1: "body1",
},
cookie: {
cookie1: "cookie1",
},
header: {
header1: "header1",
},
path: {
path1: "path1",
},
query: {
query1: "query1",
},
response: {
response1: "response1",
},
},
ajvOptions,
);

expect(validationResult).toEqual({
body: null,
cookie: null,
header: null,
path: null,
query: null,
response: null,
});
});

it("should be able to ~1", () => {
const ajvOptions: AjvOptions = {
strictSchema: true,
};

openapi.define(path, method, schemas);

const validationResult = openapi.validate(
path,
method,
{
body: {
body1: "body1",
},
cookie: {
cookie1: "cookie1",
},
header: {},
path: null,
query: undefined,
},
ajvOptions,
);

expect(validationResult).toEqual({
body: null,
cookie: null,
header: [
{
instancePath: "",
keyword: "required",
message: "must have required property 'header1'",
params: {
missingProperty: "header1",
},
schemaPath: "#/required",
},
],
path: [
{
instancePath: "",
keyword: "type",
message: "must be object",
params: {
type: "object",
},
schemaPath: "#/type",
},
],
query: [
{
instancePath: "",
keyword: "type",
message: "must be object",
params: {
type: "object",
},
schemaPath: "#/type",
},
],
});
});

0 comments on commit 482e35c

Please sign in to comment.