diff --git a/src/compileComponentSchemas.ts b/src/compileComponentSchemas.ts new file mode 100644 index 0000000..2d57f69 --- /dev/null +++ b/src/compileComponentSchemas.ts @@ -0,0 +1,42 @@ +import { builders } from 'ast-types'; +import { Compiler } from './compiler'; +import { compileValueSchema } from './compileValueSchema'; +import { OpenAPIValueSchema } from './types'; +import { annotateWithJSDocComment } from './comments'; + +const COMMENT = ` +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +`; + +/** + * Compile all component schemas to be expoerted as `components['Name']`. + */ +export function compileComponentSchemas( + compiler: Compiler, + schemas: { + [key: string]: OpenAPIValueSchema; + }, +) { + const properties = Object.entries(schemas).map(([name]) => { + return builders.property( + 'init', + builders.literal(name), + compileValueSchema(compiler, schemas[name]), + ); + }); + + return [ + annotateWithJSDocComment( + builders.exportNamedDeclaration( + builders.variableDeclaration('const', [ + builders.variableDeclarator( + builders.identifier('componentSchemas'), + builders.objectExpression(properties), + ), + ]), + ), + COMMENT, + ), + ]; +} diff --git a/src/compiler.ts b/src/compiler.ts index 9227fc2..d5c2ccd 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -5,6 +5,7 @@ import { OpenAPIRef, OpenAPISpec } from './types'; import { compileValueSchema } from './compileValueSchema'; import { hash } from './hash'; import { compileValidateRequest } from './compileValidateRequest'; +import { compileComponentSchemas } from './compileComponentSchemas'; /** * Compiler for OpenAPI specs. @@ -117,23 +118,13 @@ export class Compiler { }); } - /** - * Build the AST from the entire spec. - */ - public indexAllComponents() { - // Index all the schema components. - const schemas = this.input.components?.schemas ?? {}; - Object.values(schemas).forEach((schema) => { - compileValueSchema(this, schema); - }); - } - /** * Return the AST for the program. */ public ast() { return builders.program([ ...compileValidateRequest(this, this.input), + ...compileComponentSchemas(this, this.input.components?.schemas ?? {}), ...this.globalDeclarations, ]); } diff --git a/src/tests/__snapshots__/compileValueSchema.test.ts.snap b/src/tests/__snapshots__/compileValueSchema.test.ts.snap index 584bfdf..5d72bdb 100644 --- a/src/tests/__snapshots__/compileValueSchema.test.ts.snap +++ b/src/tests/__snapshots__/compileValueSchema.test.ts.snap @@ -10,6 +10,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -49,6 +54,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -91,6 +101,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -133,6 +148,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -175,6 +195,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -217,6 +242,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -256,6 +286,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -298,6 +333,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -334,6 +374,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -374,6 +419,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -414,6 +464,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -492,6 +547,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -552,6 +612,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -589,6 +654,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -649,6 +719,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -725,6 +800,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -768,6 +848,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -826,6 +911,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -883,6 +973,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -939,6 +1034,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -1002,6 +1102,11 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = {}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ diff --git a/src/tests/__snapshots__/compiler.test.ts.snap b/src/tests/__snapshots__/compiler.test.ts.snap index f0ec8e4..b30687f 100644 --- a/src/tests/__snapshots__/compiler.test.ts.snap +++ b/src/tests/__snapshots__/compiler.test.ts.snap @@ -10,6 +10,14 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = { + 'A': obj0, + 'B': obj1 +}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ @@ -86,6 +94,14 @@ Validate a request against the OpenAPI spec export function validateRequest(request, context) { return new RequestError(404, 'no operation match path'); } +/** +Map of all components defined in the spec to their validation functions. +{Object.(path: string[], value: T, context: any) => (T | ValidationError)>} +*/ +export const componentSchemas = { + 'A': obj0, + 'B': obj1 +}; export class RequestError extends Error { /** @param {number} code HTTP code for the error @param {string} message The error message*/ diff --git a/src/tests/compiler.test.ts b/src/tests/compiler.test.ts index e274597..3ec0b78 100644 --- a/src/tests/compiler.test.ts +++ b/src/tests/compiler.test.ts @@ -22,7 +22,6 @@ test('components ref', () => { }, }, }); - compiler.indexAllComponents(); expect(compiler.compile()).toMatchSnapshot(); }); @@ -50,6 +49,5 @@ test('recursive refs', () => { }, }, }); - compiler.indexAllComponents(); expect(compiler.compile()).toMatchSnapshot(); }); diff --git a/tests/gitbook.test.ts b/tests/gitbook.test.ts index 6b5cecf..76b6f4c 100644 --- a/tests/gitbook.test.ts +++ b/tests/gitbook.test.ts @@ -1,5 +1,5 @@ -import { expect, test } from 'bun:test'; -import { validateRequest, ValidationError } from './gitbook.validate'; +import { describe, expect, test } from 'bun:test'; +import { validateRequest, ValidationError, componentSchemas } from './gitbook.validate'; test('POST orgs/appleId/custom-fields', () => { const result = validateRequest({ @@ -287,3 +287,28 @@ test('/orgs/xxx/synced-blocks?ids[]=foo allow string parameter as array too', () }); expect(result.query).toEqual({ ids: ['foo'] }); }); + +describe('componentSchemas', () => { + test('should export a function to validate a component', () => { + const validate = componentSchemas['ApiInformation']; + expect(validate).toBeInstanceOf(Function); + expect( + validate([], { + version: '1.0.0', + build: '123', + }), + ).toEqual({ + version: '1.0.0', + build: '123', + }); + + const error = validate([], { + version: '1.0.0', + // Missing property + }); + expect(error instanceof ValidationError ? error.path : null).toEqual([]); + expect(error instanceof ValidationError ? error.message : null).toEqual( + 'expected "build" to be defined', + ); + }); +});