This commit is contained in:
Luke Hagar
2025-08-13 11:21:00 -05:00
parent 5b157f060a
commit fe7d055362
8048 changed files with 6385 additions and 1727801 deletions

66
.gitignore vendored Normal file
View File

@@ -0,0 +1,66 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Node modules
node_modules/
# Build outputs
dist/
build/
out/
.tmp/
tmp/
temp/
.cache/
# Coverage
coverage/
.nyc_output/
# TypeScript
*.tsbuildinfo
# Env files
.env
.env.*
!.env.example
# Package manager stores
.pnpm-store/
# Tool caches
.eslintcache
.parcel-cache/
.turbo/
.vite/
# Frameworks
.next/
.nuxt/
.svelte-kit/
# Editors/IDE
.vscode/
.idea/
*.swp
# OS
.DS_Store
Thumbs.db
# Local data (Docker volume mount)
data/

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,177 +0,0 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { startServers } from '../../src/server.js';
import fetch from 'node-fetch';
describe('Arbiter Integration Tests', () => {
// Use different ports to avoid conflicts with other tests
const targetPort = 4001;
const proxyPort = 4002;
const docsPort = 4003;
let targetServer;
let proxyServer;
let docsServer;
// Create a mock target API
const targetApi = new Hono();
// Setup test endpoints
targetApi.get('/users', (c) => {
return c.json([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' },
]);
});
targetApi.post('/users', async (c) => {
const body = await c.req.json();
c.status(201);
return c.json({ id: 3, ...body });
});
targetApi.get('/users/:id', (c) => {
const id = c.req.param('id');
return c.json({ id: parseInt(id), name: 'John Doe' });
});
targetApi.get('/secure', (c) => {
const apiKey = c.req.header('x-api-key');
if (apiKey !== 'test-key') {
c.status(401);
return c.json({ error: 'Unauthorized' });
}
return c.json({ message: 'Secret data' });
});
// Add endpoint for query parameter test
targetApi.get('/users/search', (c) => {
const limit = c.req.query('limit');
const sort = c.req.query('sort');
return c.json({
results: [{ id: 1, name: 'John Doe' }],
limit: limit ? parseInt(limit) : 10,
sort: sort || 'asc'
});
});
beforeAll(async () => {
// Start the target API server
targetServer = serve({
fetch: targetApi.fetch,
port: targetPort,
});
// Start Arbiter servers
const { proxyServer: proxy, docsServer: docs } = await startServers({
target: `http://localhost:${targetPort}`,
proxyPort: proxyPort,
docsPort: docsPort,
verbose: false
});
proxyServer = proxy;
docsServer = docs;
});
afterAll(() => {
targetServer?.close();
proxyServer?.close();
docsServer?.close();
});
it('should proxy basic GET request and record in HAR', async () => {
const response = await fetch(`http://localhost:${proxyPort}/users`);
expect(response.status).toBe(200);
const users = (await response.json());
expect(users).toHaveLength(2);
expect(users[0].name).toBe('John Doe');
// Check HAR recording
const harResponse = await fetch(`http://localhost:${docsPort}/har`);
const har = (await harResponse.json());
expect(har.log.entries).toHaveLength(1);
expect(har.log.entries[0].request.method).toBe('GET');
expect(har.log.entries[0].request.url).toBe(`http://localhost:${targetPort}/users`);
expect(har.log.entries[0].response.status).toBe(200);
});
it('should record POST request with body in HAR', async () => {
const response = await fetch(`http://localhost:${proxyPort}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Bob Wilson' }),
});
expect(response.status).toBe(201);
const newUser = (await response.json());
expect(newUser.name).toBe('Bob Wilson');
// Check HAR recording
const harResponse = await fetch(`http://localhost:${docsPort}/har`);
const har = (await harResponse.json());
const postEntry = har.log.entries.find((e) => e.request.method === 'POST');
expect(postEntry).toBeDefined();
expect(postEntry?.request.postData?.text).toBe(JSON.stringify({ name: 'Bob Wilson' }));
expect(postEntry?.response.status).toBe(201);
});
it('should generate OpenAPI spec with paths and schemas', async () => {
// Make some requests to generate OpenAPI spec
await fetch(`http://localhost:${proxyPort}/users`);
await fetch(`http://localhost:${proxyPort}/users/1`);
await fetch(`http://localhost:${proxyPort}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test User' }),
});
// Get OpenAPI spec
const specResponse = await fetch(`http://localhost:${docsPort}/openapi.json`);
const spec = (await specResponse.json());
// Validate paths
expect(spec.paths?.['/users']).toBeDefined();
expect(spec.paths?.['/users']?.get).toBeDefined();
expect(spec.paths?.['/users']?.post).toBeDefined();
expect(spec.paths?.['/users/{id}']?.get).toBeDefined();
// Check request body schema
expect(spec.paths?.['/users']?.post?.requestBody).toBeDefined();
const requestBody = spec.paths?.['/users']?.post?.requestBody;
expect(requestBody.content?.['application/json']).toBeDefined();
expect(requestBody.content?.['application/json'].schema).toBeDefined();
// Validate schema properties based on what we sent in the POST request
const schema = requestBody.content?.['application/json'].schema;
expect(schema).toBeDefined();
expect(schema.type).toBe('object');
expect(schema.properties?.name).toBeDefined();
expect((schema.properties?.name).type).toBe('string');
});
it('should handle query parameters', async () => {
await fetch(`http://localhost:${proxyPort}/users?limit=10&offset=0`);
const harResponse = await fetch(`http://localhost:${docsPort}/har`);
const har = (await harResponse.json());
const entry = har.log.entries.find((e) => e.request.url.includes('?limit=10'));
expect(entry).toBeDefined();
expect(entry?.request.queryString).toEqual([
{ name: 'limit', value: '10' },
{ name: 'offset', value: '0' },
]);
const specResponse = await fetch(`http://localhost:${docsPort}/openapi.json`);
const spec = (await specResponse.json());
const parameters = spec.paths?.['/users']?.get?.parameters;
expect(parameters).toBeDefined();
expect(parameters).toContainEqual({
name: 'limit',
in: 'query',
schema: { type: 'string' },
});
});
it('should handle security schemes', async () => {
await fetch(`http://localhost:${proxyPort}/secure`, {
headers: {
'x-api-key': 'test-key',
},
});
const specResponse = await fetch(`http://localhost:${docsPort}/openapi.json`);
const spec = (await specResponse.json());
// Check security scheme definition
expect(spec.components?.securitySchemes).toBeDefined();
const apiKeyAuth = spec.components?.securitySchemes
?.apiKey_;
expect(apiKeyAuth).toBeDefined();
expect(apiKeyAuth.type).toBe('apiKey');
expect(apiKeyAuth.in).toBe('header');
expect(apiKeyAuth.name).toBe('x-api-key');
// Check security requirement on endpoint
const securityRequirements = spec.paths?.['/secure']?.get?.security;
expect(securityRequirements).toBeDefined();
expect(securityRequirements).toContainEqual({
apiKey_: [],
});
});
});
//# sourceMappingURL=proxy.test.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,107 +0,0 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { startServers } from '../../src/server.js';
import fetch from 'node-fetch';
import { openApiStore } from '../../src/store/openApiStore.js';
// Create a mock version of startServers function that operates on our test ports
// This function is no longer needed since we're using the real startServers
// function createMockServer(targetUrl: string, port: number): Server {
// // ... existing code ...
// }
describe('Server Integration Tests', () => {
const TARGET_PORT = 3000;
const PROXY_PORT = 3005; // Changed to avoid conflicts with other tests
const DOCS_PORT = 3006; // Changed to avoid conflicts with other tests
const TARGET_URL = `http://localhost:${TARGET_PORT}`;
const PROXY_URL = `http://localhost:${PROXY_PORT}`;
const DOCS_URL = `http://localhost:${DOCS_PORT}`;
let targetServer;
let proxyServer;
let docsServer;
beforeAll(async () => {
// Create a mock target API server
const targetApp = new Hono();
// Basic GET endpoint
targetApp.get('/api/test', (c) => {
return c.json({ message: 'Test successful' });
});
// POST endpoint for users
targetApp.post('/api/users', async (c) => {
try {
const body = await c.req.json();
c.status(201);
return c.json({ id: 1, ...body });
}
catch (e) {
c.status(400);
return c.json({ error: 'Invalid JSON', message: e.message });
}
});
// Start the target server
targetServer = serve({ port: TARGET_PORT, fetch: targetApp.fetch });
// Clear the OpenAPI store
openApiStore.clear();
// Start the real proxy and docs servers
const servers = await startServers({
target: TARGET_URL,
proxyPort: PROXY_PORT,
docsPort: DOCS_PORT,
verbose: false
});
proxyServer = servers.proxyServer;
docsServer = servers.docsServer;
});
afterAll(async () => {
// Shutdown servers
targetServer?.close();
proxyServer?.close();
docsServer?.close();
});
it('should respond to GET requests and record them', async () => {
const response = await fetch(`${PROXY_URL}/api/test`);
expect(response.status).toBe(200);
const body = await response.json();
expect(body).toEqual({ message: 'Test successful' });
// Verify that the endpoint was recorded in OpenAPI spec
const specResponse = await fetch(`${DOCS_URL}/openapi.json`);
const spec = await specResponse.json();
expect(spec.paths?.['/api/test']?.get).toBeDefined();
// Verify that the endpoint was recorded in HAR format
const harResponse = await fetch(`${DOCS_URL}/har`);
const har = await harResponse.json();
expect(har.log.entries.length).toBeGreaterThan(0);
expect(har.log.entries).toContainEqual(expect.objectContaining({
request: expect.objectContaining({
method: 'GET',
url: expect.stringContaining('/api/test')
})
}));
});
it('should handle POST requests with JSON bodies', async () => {
const payload = { name: 'Test User', email: 'test@example.com' };
const response = await fetch(`${PROXY_URL}/api/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
expect(response.status).toBe(201);
const body = await response.json();
expect(body).toEqual({ id: 1, name: 'Test User', email: 'test@example.com' });
// Verify that the endpoint and request body were recorded
const specResponse = await fetch(`${DOCS_URL}/openapi.json`);
const spec = await specResponse.json();
expect(spec.paths?.['/api/users']?.post?.requestBody).toBeDefined();
// Check that the request schema was generated
if (spec.paths?.['/api/users']?.post?.requestBody) {
const requestBody = spec.paths['/api/users'].post.requestBody;
if (requestBody.content) {
expect(requestBody.content['application/json']).toBeDefined();
expect(requestBody.content['application/json'].schema).toBeDefined();
}
}
});
});
//# sourceMappingURL=server.test.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../../integration/__tests__/server.test.ts"],"names":[],"mappings":"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"}

View File

@@ -1 +0,0 @@
export {};

13
dist/server.test.js vendored
View File

@@ -1,13 +0,0 @@
import { describe, it, expect } from 'vitest';
// Remove the imports for missing modules
// import { fetch } from 'undici';
// import { startDocServer, startProxyServer } from '../utils/testHelpers.js';
// import { openApiStore } from '../../src/store/openApiStore.js';
// Use mock test to prevent the test failure due to missing modules
describe('Server Integration Tests', () => {
it('should be implemented with actual helper functions', () => {
// This is a placeholder test until we set up the proper environment
expect(true).toBe(true);
});
});
//# sourceMappingURL=server.test.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"server.test.js","sourceRoot":"","sources":["../server.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAuB,MAAM,QAAQ,CAAC;AACnE,yCAAyC;AACzC,kCAAkC;AAClC,8EAA8E;AAC9E,kEAAkE;AAElE,mEAAmE;AACnE,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,oEAAoE;QACpE,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,74 +0,0 @@
import { describe, it, expect } from 'vitest';
import { Command } from 'commander';
describe('CLI Options', () => {
it('should require target URL', () => {
const program = new Command();
program
.name('arbiter')
.description('API proxy with OpenAPI generation and HAR export capabilities')
.version('1.0.0')
.requiredOption('-t, --target <url>', 'target API URL to proxy to')
.option('-p, --port <number>', 'port to run the proxy server on', '8080')
.option('-d, --docs-port <number>', 'port to run the documentation server on', '9000')
.option('-k, --key <string>', 'API key to add to proxied requests')
.option('--docs-only', 'run only the documentation server')
.option('--proxy-only', 'run only the proxy server')
.option('-v, --verbose', 'enable verbose logging');
// Test without target URL
expect(() => program.parse(['node', 'arbiter'])).toThrow();
// Test with target URL
const options = program.parse(['node', 'arbiter', '-t', 'http://example.com']).opts();
expect(options.target).toBe('http://example.com');
expect(options.port).toBe('8080');
expect(options.docsPort).toBe('9000');
});
it('should handle custom ports', () => {
const program = new Command();
program
.name('arbiter')
.description('API proxy with OpenAPI generation and HAR export capabilities')
.version('1.0.0')
.requiredOption('-t, --target <url>', 'target API URL to proxy to')
.option('-p, --port <number>', 'port to run the proxy server on', '8080')
.option('-d, --docs-port <number>', 'port to run the documentation server on', '9000');
const options = program
.parse(['node', 'arbiter', '-t', 'http://example.com', '-p', '8081', '-d', '9001'])
.opts();
expect(options.port).toBe('8081');
expect(options.docsPort).toBe('9001');
});
it('should handle API key', () => {
const program = new Command();
program
.name('arbiter')
.description('API proxy with OpenAPI generation and HAR export capabilities')
.version('1.0.0')
.requiredOption('-t, --target <url>', 'target API URL to proxy to')
.option('-k, --key <string>', 'API key to add to proxied requests');
const options = program
.parse(['node', 'arbiter', '-t', 'http://example.com', '-k', 'test-api-key'])
.opts();
expect(options.key).toBe('test-api-key');
});
it('should handle server mode options', () => {
const program = new Command();
program
.name('arbiter')
.description('API proxy with OpenAPI generation and HAR export capabilities')
.version('1.0.0')
.requiredOption('-t, --target <url>', 'target API URL to proxy to')
.option('--docs-only', 'run only the documentation server')
.option('--proxy-only', 'run only the proxy server');
// Test docs-only mode
const docsOptions = program
.parse(['node', 'arbiter', '-t', 'http://example.com', '--docs-only'])
.opts();
expect(docsOptions.docsOnly).toBe(true);
// Test proxy-only mode
const proxyOptions = program
.parse(['node', 'arbiter', '-t', 'http://example.com', '--proxy-only'])
.opts();
expect(proxyOptions.proxyOnly).toBe(true);
});
});
//# sourceMappingURL=cli.test.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../../../src/__tests__/cli.test.ts"],"names":[],"mappings":"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"}

2
dist/src/cli.d.ts vendored
View File

@@ -1,2 +0,0 @@
#!/usr/bin/env node
export {};

30
dist/src/cli.js vendored
View File

@@ -1,30 +0,0 @@
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import { startServers } from './server.js';
const program = new Command();
// Use console.info for startup messages
console.info('Starting Arbiter...');
program
.name('arbiter')
.description('API proxy with OpenAPI generation and HAR export capabilities')
.version('1.0.0')
.requiredOption('-t, --target <url>', 'target API URL to proxy to')
.option('-p, --port <number>', 'port to run the proxy server on', '8080')
.option('-d, --docs-port <number>', 'port to run the documentation server on', '9000')
.option('--docs-only', 'run only the documentation server')
.option('--proxy-only', 'run only the proxy server')
.option('-v, --verbose', 'enable verbose logging')
.parse(process.argv);
const options = program.opts();
// Start the servers
startServers({
target: options.target,
proxyPort: parseInt(options.port, 10),
docsPort: parseInt(options.docsPort, 10),
verbose: options.verbose,
}).catch((error) => {
console.error(chalk.red('Failed to start servers:'), error.message);
process.exit(1);
});
//# sourceMappingURL=cli.js.map

1
dist/src/cli.js.map vendored
View File

@@ -1 +0,0 @@
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,wCAAwC;AACxC,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAEpC,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,+DAA+D,CAAC;KAC5E,OAAO,CAAC,OAAO,CAAC;KAChB,cAAc,CAAC,oBAAoB,EAAE,4BAA4B,CAAC;KAClE,MAAM,CAAC,qBAAqB,EAAE,iCAAiC,EAAE,MAAM,CAAC;KACxE,MAAM,CAAC,0BAA0B,EAAE,yCAAyC,EAAE,MAAM,CAAC;KACrF,MAAM,CAAC,aAAa,EAAE,mCAAmC,CAAC;KAC1D,MAAM,CAAC,cAAc,EAAE,2BAA2B,CAAC;KACnD,MAAM,CAAC,eAAe,EAAE,wBAAwB,CAAC;KACjD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;AAE/B,oBAAoB;AACpB,YAAY,CAAC;IACX,MAAM,EAAE,OAAO,CAAC,MAAgB;IAChC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAc,EAAE,EAAE,CAAC;IAC/C,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAkB,EAAE,EAAE,CAAC;IAClD,OAAO,EAAE,OAAO,CAAC,OAAkB;CACpC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,519 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { harRecorder } from '../harRecorder.js';
import { openApiStore } from '../../store/openApiStore.js';
describe('HAR Recorder Middleware', () => {
beforeEach(() => {
openApiStore.clear();
openApiStore.setTargetUrl('http://localhost:8080');
});
it('should record basic GET request and response details', async () => {
const store = new Map();
const ctx = {
req: {
method: 'GET',
url: 'http://localhost:8080/test',
path: '/test',
query: {},
header: () => ({
'content-type': 'application/json',
}),
raw: {
clone: () => ({
text: async () => '{"test":"data"}',
formData: async () => new Map([['key', 'value']]),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries).toHaveLength(1);
expect(har.log.entries[0].request.method).toBe('GET');
expect(har.log.entries[0].request.url).toBe('http://localhost:8080/test');
expect(har.log.entries[0].response.status).toBe(200);
expect(har.log.entries[0].response.content.text).toBe(JSON.stringify({ success: true }));
});
it('should handle POST requests with JSON body', async () => {
const requestBody = { name: 'Test User', email: 'test@example.com' };
const jsonBody = JSON.stringify(requestBody);
const store = new Map();
const ctx = {
req: {
method: 'POST',
url: 'http://localhost:8080/users',
path: '/users',
query: {},
header: () => ({
'content-type': 'application/json',
}),
raw: {
clone: () => ({
text: async () => jsonBody,
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ id: 1, ...requestBody }), {
status: 201,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries).toHaveLength(1);
expect(har.log.entries[0].request.method).toBe('POST');
expect(har.log.entries[0].request.url).toBe('http://localhost:8080/users');
// Check request body was properly captured
const entry = openApiStore.getOpenAPISpec().paths?.['/users']?.post;
expect(entry).toBeDefined();
expect(entry?.requestBody).toBeDefined();
// Check response body and status
expect(har.log.entries[0].response.status).toBe(201);
expect(har.log.entries[0].response.content.text).toEqual(expect.stringContaining('Test User'));
});
it('should handle PUT requests with JSON body', async () => {
const requestBody = { name: 'Updated User', email: 'updated@example.com' };
const jsonBody = JSON.stringify(requestBody);
const store = new Map();
const ctx = {
req: {
method: 'PUT',
url: 'http://localhost:8080/users/1',
path: '/users/1',
query: {},
header: () => ({
'content-type': 'application/json',
}),
raw: {
clone: () => ({
text: async () => jsonBody,
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ id: 1, ...requestBody }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries).toHaveLength(1);
expect(har.log.entries[0].request.method).toBe('PUT');
// Check request body was properly captured
const entry = openApiStore.getOpenAPISpec().paths?.['/users/{id}']?.put;
expect(entry).toBeDefined();
expect(entry?.requestBody).toBeDefined();
});
it('should handle PATCH requests with JSON body', async () => {
const requestBody = { name: 'Partially Updated User' };
const jsonBody = JSON.stringify(requestBody);
const store = new Map();
const ctx = {
req: {
method: 'PATCH',
url: 'http://localhost:8080/users/1',
path: '/users/1',
query: {},
header: () => ({
'content-type': 'application/json',
}),
raw: {
clone: () => ({
text: async () => jsonBody,
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ id: 1, name: 'Partially Updated User', email: 'existing@example.com' }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries).toHaveLength(1);
expect(har.log.entries[0].request.method).toBe('PATCH');
// Check request body was properly captured
const entry = openApiStore.getOpenAPISpec().paths?.['/users/{id}']?.patch;
expect(entry).toBeDefined();
expect(entry?.requestBody).toBeDefined();
});
it('should handle form data in requests', async () => {
const formData = new Map([
['username', 'testuser'],
['email', 'test@example.com']
]);
const store = new Map();
const ctx = {
req: {
method: 'POST',
url: 'http://localhost:8080/form',
path: '/form',
query: {},
header: () => ({
'content-type': 'application/x-www-form-urlencoded',
}),
raw: {
clone: () => ({
text: async () => 'username=testuser&email=test@example.com',
formData: async () => formData,
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries).toHaveLength(1);
// Check form data was captured
const entry = openApiStore.getOpenAPISpec().paths?.['/form']?.post;
expect(entry).toBeDefined();
expect(entry?.requestBody).toBeDefined();
});
it('should handle text content in requests', async () => {
const textContent = 'This is a plain text content';
const store = new Map();
const ctx = {
req: {
method: 'POST',
url: 'http://localhost:8080/text',
path: '/text',
query: {},
header: () => ({
'content-type': 'text/plain',
}),
raw: {
clone: () => ({
text: async () => textContent,
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response('Received text content', {
status: 200,
headers: new Headers({
'content-type': 'text/plain',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries).toHaveLength(1);
// Check text content was captured
const entry = openApiStore.getOpenAPISpec().paths?.['/text']?.post;
expect(entry).toBeDefined();
expect(entry?.requestBody).toBeDefined();
});
it('should handle query parameters', async () => {
const store = new Map();
const ctx = {
req: {
method: 'GET',
url: 'http://localhost:8080/test?foo=bar&baz=qux',
path: '/test',
query: { foo: 'bar', baz: 'qux' },
header: () => ({
'content-type': 'application/json',
}),
raw: {
clone: () => ({
text: async () => '',
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries[0].request.queryString).toEqual([
{ name: 'foo', value: 'bar' },
{ name: 'baz', value: 'qux' },
]);
// Check query parameters in OpenAPI spec
const parameters = openApiStore.getOpenAPISpec().paths?.['/test']?.get?.parameters;
expect(parameters).toBeDefined();
expect(parameters).toContainEqual(expect.objectContaining({
name: 'foo',
in: 'query'
}));
expect(parameters).toContainEqual(expect.objectContaining({
name: 'baz',
in: 'query'
}));
});
it('should handle request headers', async () => {
const store = new Map();
const customHeaders = {
'content-type': 'application/json',
'x-custom-header': 'test-value',
'authorization': 'Bearer test-token',
};
const ctx = {
req: {
method: 'GET',
url: 'http://localhost:8080/test',
path: '/test',
query: {},
header: () => customHeaders,
raw: {
clone: () => ({
text: async () => '',
formData: async () => new Map(),
}),
},
},
header: (name) => (name ? customHeaders[name] : customHeaders),
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Clear the store first
openApiStore.clear();
// Add security configuration explicitly before running middleware
openApiStore.recordEndpoint('/test', 'get', {
query: {},
headers: {
'authorization': 'Bearer test-token',
'x-custom-header': 'test-value'
},
contentType: 'application/json',
body: null,
security: [{ type: 'http', scheme: 'bearer' }]
}, {
status: 200,
headers: {},
contentType: 'application/json',
body: { success: true }
});
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries[0].request.headers).toContainEqual({
name: 'x-custom-header',
value: 'test-value',
});
// Check headers in OpenAPI spec
const parameters = openApiStore.getOpenAPISpec().paths?.['/test']?.get?.parameters;
expect(parameters).toBeDefined();
expect(parameters).toContainEqual(expect.objectContaining({
name: 'x-custom-header',
in: 'header'
}));
// Check security schemes for auth header
const spec = openApiStore.getOpenAPISpec();
expect(spec.components?.securitySchemes?.http_).toBeDefined();
});
it('should handle response headers', async () => {
const store = new Map();
const ctx = {
req: {
method: 'GET',
url: 'http://localhost:8080/test',
path: '/test',
query: {},
header: () => ({
'content-type': 'application/json',
}),
raw: {
clone: () => ({
text: async () => '',
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
'x-custom-response': 'test-value',
'cache-control': 'no-cache',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries[0].response.headers).toContainEqual({
name: 'x-custom-response',
value: 'test-value',
});
// Check response headers in OpenAPI spec
const responseObj = openApiStore.getOpenAPISpec().paths?.['/test']?.get?.responses?.[200];
expect(responseObj).toBeDefined();
// Cast to ResponseObject to access headers property
if (responseObj && 'headers' in responseObj && responseObj.headers) {
expect(Object.keys(responseObj.headers).length).toBeGreaterThan(0);
}
});
it('should handle error responses', async () => {
const store = new Map();
const ctx = {
req: {
method: 'GET',
url: 'http://localhost:8080/error',
path: '/error',
query: {},
header: () => ({
'content-type': 'application/json',
}),
raw: {
clone: () => ({
text: async () => '',
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ error: 'Something went wrong' }), {
status: 500,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
await middleware(ctx, next);
const har = openApiStore.generateHAR();
expect(har.log.entries[0].response.status).toBe(500);
// Check error response in OpenAPI spec
const errorResponse = openApiStore.getOpenAPISpec().paths?.['/error']?.get?.responses?.[500];
expect(errorResponse).toBeDefined();
});
it('should gracefully handle errors during middleware execution', async () => {
const store = new Map();
const ctx = {
req: {
method: 'GET',
url: 'http://localhost:8080/test',
path: '/test',
query: {},
header: () => { throw new Error('Test error'); }, // Deliberately throw an error
raw: {
clone: () => ({
text: async () => '',
formData: async () => new Map(),
}),
},
},
header: () => undefined,
get: (key) => store.get(key),
set: (key, value) => store.set(key, value),
res: undefined,
};
const next = async () => {
ctx.res = new Response(JSON.stringify({ success: true }), {
status: 200,
headers: new Headers({
'content-type': 'application/json',
}),
});
};
// Get the middleware function and call it
const middleware = harRecorder(openApiStore);
// Should not throw
await expect(middleware(ctx, next)).resolves.not.toThrow();
});
});
//# sourceMappingURL=harRecorder.test.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
import type { Context, Next } from 'hono';
import type { OpenAPIStore } from '../store/openApiStore.js';
export declare function apiDocGenerator(store: OpenAPIStore): (c: Context, next: Next) => Promise<void>;

View File

@@ -1,52 +0,0 @@
export function apiDocGenerator(store) {
return async (c, next) => {
const startTime = Date.now();
try {
await next();
}
catch (error) {
console.error('Error in apiDocGenerator middleware:', error);
throw error;
}
const endTime = Date.now();
const responseTime = endTime - startTime;
// Record the request/response in OpenAPI format
try {
const url = new URL(c.req.url);
const queryParams = {};
for (const [key, value] of url.searchParams.entries()) {
queryParams[key] = value;
}
// Get request headers
const requestHeaders = {};
for (const [key, value] of Object.entries(c.req.header())) {
if (typeof value === 'string') {
requestHeaders[key] = value;
}
}
// Get response headers
const responseHeaders = {};
if (c.res) {
for (const [key, value] of c.res.headers.entries()) {
responseHeaders[key] = value;
}
}
// Record the endpoint
store.recordEndpoint(c.req.path, c.req.method.toLowerCase(), {
query: queryParams,
headers: requestHeaders,
contentType: c.req.header('content-type') || 'application/json',
body: undefined, // We'll need to handle body parsing if needed
}, {
status: c.res?.status || 500,
headers: responseHeaders,
contentType: c.res?.headers.get('content-type') || 'application/json',
body: c.res ? await c.res.clone().text() : '',
});
}
catch (error) {
console.error('Error recording OpenAPI entry:', error);
}
};
}
//# sourceMappingURL=apiDocGenerator.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"apiDocGenerator.js","sourceRoot":"","sources":["../../../src/middleware/apiDocGenerator.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,eAAe,CAAC,KAAmB;IACjD,OAAO,KAAK,EAAE,CAAU,EAAE,IAAU,EAAiB,EAAE;QACrD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,IAAI,EAAE,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC,CAAC;YAC7D,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;QAEzC,gDAAgD;QAChD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,MAAM,WAAW,GAA2B,EAAE,CAAC;YAC/C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;gBACtD,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAC3B,CAAC;YAED,sBAAsB;YACtB,MAAM,cAAc,GAA2B,EAAE,CAAC;YAClD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;gBAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC9B,cAAc,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC9B,CAAC;YACH,CAAC;YAED,uBAAuB;YACvB,MAAM,eAAe,GAA2B,EAAE,CAAC;YACnD,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;gBACV,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;oBACnD,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC/B,CAAC;YACH,CAAC;YAED,sBAAsB;YACtB,KAAK,CAAC,cAAc,CAClB,CAAC,CAAC,GAAG,CAAC,IAAI,EACV,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,EAC1B;gBACE,KAAK,EAAE,WAAW;gBAClB,OAAO,EAAE,cAAc;gBACvB,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,kBAAkB;gBAC/D,IAAI,EAAE,SAAS,EAAE,8CAA8C;aAChE,EACD;gBACE,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG;gBAC5B,OAAO,EAAE,eAAe;gBACxB,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,kBAAkB;gBACrE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;aAC9C,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;QACzD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}

View File

@@ -1,3 +0,0 @@
import type { Context, Next } from 'hono';
import type { OpenAPIStore } from '../store/openApiStore.js';
export declare function harRecorder(store: OpenAPIStore): (c: Context, next: Next) => Promise<void>;

View File

@@ -1,113 +0,0 @@
export function harRecorder(store) {
return async (c, next) => {
const startTime = Date.now();
// Get a clone of the request body before processing if it's a POST/PUT/PATCH
let requestBody = undefined;
if (['POST', 'PUT', 'PATCH'].includes(c.req.method)) {
try {
// Clone the request body based on content type
const contentType = c.req.header('content-type') || '';
// Create a copy of the request to avoid consuming the body
const reqClone = c.req.raw.clone();
if (typeof contentType === 'string' && contentType.includes('application/json')) {
const text = await reqClone.text();
try {
requestBody = JSON.parse(text);
}
catch (e) {
requestBody = text; // Keep as text if JSON parsing fails
}
}
else if (typeof contentType === 'string' && contentType.includes('application/x-www-form-urlencoded')) {
const formData = await reqClone.formData();
requestBody = Object.fromEntries(formData);
}
else if (typeof contentType === 'string' && contentType.includes('text/')) {
requestBody = await reqClone.text();
}
else {
requestBody = await reqClone.text();
}
}
catch (e) {
console.error('Error cloning request body:', e);
}
}
try {
await next();
}
catch (error) {
console.error('Error in harRecorder middleware:', error);
throw error;
}
const endTime = Date.now();
const responseTime = endTime - startTime;
// Record the request/response in HAR format
try {
const url = new URL(c.req.url);
const queryParams = {};
for (const [key, value] of url.searchParams.entries()) {
queryParams[key] = value;
}
// Get request headers
const requestHeaders = {};
if (c.req.header) {
const headers = c.req.header();
if (headers && typeof headers === 'object') {
for (const [key, value] of Object.entries(headers)) {
if (typeof value === 'string') {
requestHeaders[key] = value;
}
}
}
}
// Get response headers
const responseHeaders = {};
if (c.res) {
for (const [key, value] of c.res.headers.entries()) {
responseHeaders[key] = value;
}
}
// For response body, try to get content from the response
let responseBody = {};
try {
if (c.res) {
// Clone the response to avoid consuming the body
const resClone = c.res.clone();
const contentType = c.res.headers.get('content-type') || '';
if (typeof contentType === 'string' && contentType.includes('application/json')) {
const text = await resClone.text();
try {
responseBody = JSON.parse(text);
}
catch (e) {
responseBody = text;
}
}
else if (typeof contentType === 'string' && contentType.includes('text/')) {
responseBody = await resClone.text();
}
}
}
catch (e) {
console.error('Error getting response body:', e);
}
// Record the endpoint
store.recordEndpoint(c.req.path, c.req.method.toLowerCase(), {
query: queryParams,
headers: requestHeaders,
contentType: c.req.header('content-type') || 'application/json',
body: requestBody, // Use the captured request body
}, {
status: c.res?.status || 500,
headers: responseHeaders,
contentType: c.res?.headers.get('content-type') || 'application/json',
body: responseBody // Now using captured response body
});
}
catch (error) {
console.error('Error recording HAR entry:', error);
}
};
}
//# sourceMappingURL=harRecorder.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"harRecorder.js","sourceRoot":"","sources":["../../../src/middleware/harRecorder.ts"],"names":[],"mappings":"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"}

67
dist/src/server.d.ts vendored
View File

@@ -1,67 +0,0 @@
import { createServer } from 'http';
declare class HARStore {
private har;
getHAR(): {
log: {
version: string;
creator: {
name: string;
version: string;
};
entries: Array<{
startedDateTime: string;
time: number;
request: {
method: string;
url: string;
httpVersion: string;
headers: Array<{
name: string;
value: string;
}>;
queryString: Array<{
name: string;
value: string;
}>;
postData?: any;
};
response: {
status: number;
statusText: string;
httpVersion: string;
headers: Array<{
name: string;
value: string;
}>;
content: {
size: number;
mimeType: string;
text: string;
};
};
_rawResponseBuffer?: Buffer;
}>;
};
};
addEntry(entry: typeof this.har.log.entries[0]): void;
clear(): void;
private processRawBuffers;
}
export declare const harStore: HARStore;
/**
* Server configuration options
*/
export interface ServerOptions {
target: string;
proxyPort: number;
docsPort: number;
verbose?: boolean;
}
/**
* Sets up and starts the proxy and docs servers
*/
export declare function startServers({ target, proxyPort, docsPort, verbose, }: ServerOptions): Promise<{
proxyServer: ReturnType<typeof createServer>;
docsServer: ReturnType<typeof createServer>;
}>;
export {};

470
dist/src/server.js vendored
View File

@@ -1,470 +0,0 @@
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { createServer } from 'http';
import cors from 'cors';
import zlib from 'zlib';
import { openApiStore } from './store/openApiStore.js';
import chalk from 'chalk';
import { ServerResponse } from 'http';
import bodyParser from 'body-parser';
// Create a simple HAR store
class HARStore {
har = {
log: {
version: '1.2',
creator: {
name: 'Arbiter',
version: '1.0.0',
},
entries: [],
},
};
getHAR() {
// Process any deferred entries before returning
this.processRawBuffers();
return this.har;
}
addEntry(entry) {
this.har.log.entries.push(entry);
}
clear() {
this.har.log.entries = [];
}
// Process any entries with raw response buffers
processRawBuffers() {
for (const entry of this.har.log.entries) {
if (entry._rawResponseBuffer && entry.response.content.text === '[Response content stored]') {
try {
const buffer = entry._rawResponseBuffer;
const contentType = entry.response.content.mimeType;
// Process buffer based on content-encoding header
const contentEncoding = entry.response.headers.find(h => h.name.toLowerCase() === 'content-encoding')?.value;
if (contentEncoding) {
if (contentEncoding.toLowerCase() === 'gzip') {
try {
const decompressed = zlib.gunzipSync(buffer);
const text = decompressed.toString('utf-8');
if (contentType.includes('json')) {
try {
entry.response.content.text = text;
}
catch (e) {
entry.response.content.text = text;
}
}
else {
entry.response.content.text = text;
}
}
catch (e) {
entry.response.content.text = '[Compressed content]';
}
}
else {
entry.response.content.text = `[${contentEncoding} compressed content]`;
}
}
else {
// For non-compressed responses
const text = buffer.toString('utf-8');
if (contentType.includes('json')) {
try {
const json = JSON.parse(text);
entry.response.content.text = JSON.stringify(json);
}
catch (e) {
entry.response.content.text = text;
}
}
else {
entry.response.content.text = text;
}
}
}
catch (e) {
entry.response.content.text = '[Error processing response content]';
}
// Remove the raw buffer to free memory
delete entry._rawResponseBuffer;
}
}
}
}
export const harStore = new HARStore();
/**
* Sets up and starts the proxy and docs servers
*/
export async function startServers({ target, proxyPort, docsPort, verbose = false, }) {
// Set the target URL in the OpenAPI store
openApiStore.setTargetUrl(target);
// Create proxy app with Express
const proxyApp = express();
proxyApp.use(cors());
// Add body parser for JSON and URL-encoded forms
proxyApp.use(bodyParser.json({ limit: '10mb' }));
proxyApp.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
proxyApp.use(bodyParser.text({ limit: '10mb' }));
proxyApp.use(bodyParser.raw({ type: 'application/octet-stream', limit: '10mb' }));
// Create a map to store request bodies
const requestBodies = new Map();
if (verbose) {
// Add request logging middleware
proxyApp.use((req, res, next) => {
console.log(`Proxying: ${req.method} ${req.url}`);
next();
});
}
// Create the proxy middleware with explicit type parameters for Express
const proxyMiddleware = createProxyMiddleware({
target,
changeOrigin: true,
secure: false,
ws: true,
pathRewrite: (path) => path,
selfHandleResponse: true,
plugins: [
(proxyServer, options) => {
// Handle proxy errors
proxyServer.on('error', (err, req, res) => {
console.error('Proxy error:', err);
if (res instanceof ServerResponse && !res.headersSent) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Proxy error', message: err.message }));
}
});
// Handle proxy response
proxyServer.on('proxyReq', (proxyReq, req, res) => {
// Store the request body for later use
if (['POST', 'PUT', 'PATCH'].includes(req.method || '') && req.body) {
const requestId = `${req.method}-${req.url}-${Date.now()}`;
requestBodies.set(requestId, req.body);
// Set a custom header to identify the request
proxyReq.setHeader('x-request-id', requestId);
// If the body has been consumed by the body-parser, we need to restream it to the proxy
if (req.body) {
const bodyData = JSON.stringify(req.body);
if (bodyData && bodyData !== '{}') {
// Update content-length
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
// Write the body to the proxied request
proxyReq.write(bodyData);
proxyReq.end();
}
}
}
});
proxyServer.on('proxyRes', (proxyRes, req, res) => {
const startTime = Date.now();
const chunks = [];
// Collect response chunks
proxyRes.on('data', (chunk) => {
chunks.push(Buffer.from(chunk));
});
// When the response is complete
proxyRes.on('end', () => {
const endTime = Date.now();
const responseTime = endTime - startTime;
// Combine response chunks
const buffer = Buffer.concat(chunks);
// Set status code
res.statusCode = proxyRes.statusCode || 200;
res.statusMessage = proxyRes.statusMessage || '';
// Copy ALL headers exactly as they are
Object.keys(proxyRes.headers).forEach(key => {
const headerValue = proxyRes.headers[key];
if (headerValue) {
res.setHeader(key, headerValue);
}
});
// Send the buffer as the response body without modifying it
res.end(buffer);
// Process HAR and OpenAPI data in the background (next event loop tick)
// to avoid delaying the response to the client
setImmediate(() => {
// Get request data
const method = req.method || 'GET';
const originalUrl = new URL(`http://${req.headers.host}${req.url}`);
const path = originalUrl.pathname;
// Skip web asset requests - don't process JS, CSS, HTML, etc. but keep images and icons
if (path.endsWith('.js') ||
path.endsWith('.css') ||
path.endsWith('.html') ||
path.endsWith('.htm') ||
path.endsWith('.woff') ||
path.endsWith('.woff2') ||
path.endsWith('.ttf') ||
path.endsWith('.eot') ||
path.endsWith('.map')) {
if (verbose) {
console.log(`Skipping web asset: ${method} ${path}`);
}
return;
}
// Skip if contentType is related to web assets, but keep images
const contentType = proxyRes.headers['content-type'] || '';
if (contentType.includes('javascript') ||
contentType.includes('css') ||
contentType.includes('html') ||
contentType.includes('font/')) {
if (verbose) {
console.log(`Skipping content type: ${method} ${path} (${contentType})`);
}
return;
}
// Extract query parameters
const queryParams = {};
const urlSearchParams = new URLSearchParams(originalUrl.search);
urlSearchParams.forEach((value, key) => {
queryParams[key] = value;
});
// Extract request headers
const requestHeaders = {};
for (const [key, value] of Object.entries(req.headers)) {
if (typeof value === 'string') {
requestHeaders[key] = value;
}
else if (Array.isArray(value) && value.length > 0) {
requestHeaders[key] = value[0];
}
}
// Extract response headers
const responseHeaders = {};
for (const [key, value] of Object.entries(proxyRes.headers)) {
if (typeof value === 'string') {
responseHeaders[key] = value;
}
else if (Array.isArray(value) && value.length > 0) {
responseHeaders[key] = value[0];
}
}
// Get request body from our map if available
let requestBody = undefined;
if (['POST', 'PUT', 'PATCH'].includes(method)) {
const requestId = req.headers['x-request-id'];
if (requestId && requestBodies.has(requestId)) {
requestBody = requestBodies.get(requestId);
// Clean up after use
requestBodies.delete(requestId);
}
else {
// Fallback to req.body
requestBody = req.body;
}
}
// Store minimal data for HAR entry - delay expensive processing
const requestUrl = `${target}${path}${originalUrl.search}`;
// Create lighter HAR entry with minimal processing
const harEntry = {
startedDateTime: new Date(startTime).toISOString(),
time: responseTime,
request: {
method: method,
url: requestUrl,
httpVersion: 'HTTP/1.1',
headers: Object.entries(requestHeaders)
.filter(([key]) => key.toLowerCase() !== 'content-length')
.map(([name, value]) => ({ name, value })),
queryString: Object.entries(queryParams).map(([name, value]) => ({ name, value })),
postData: requestBody ? {
mimeType: requestHeaders['content-type'] || 'application/json',
text: typeof requestBody === 'string' ? requestBody : JSON.stringify(requestBody)
} : undefined,
},
response: {
status: proxyRes.statusCode || 200,
statusText: proxyRes.statusCode === 200 ? 'OK' : 'Error',
httpVersion: 'HTTP/1.1',
headers: Object.entries(responseHeaders).map(([name, value]) => ({ name, value })),
content: {
size: buffer.length,
mimeType: responseHeaders['content-type'] || 'application/octet-stream',
// Store raw buffer and defer text conversion/parsing until needed
text: '[Response content stored]'
},
},
_rawResponseBuffer: buffer, // Store for later processing if needed
};
// Add the HAR entry to the store
harStore.addEntry(harEntry);
// Extract security schemes from headers - minimal work
const securitySchemes = [];
if (requestHeaders['x-api-key']) {
securitySchemes.push({
type: 'apiKey',
name: 'x-api-key',
in: 'header',
});
}
if (requestHeaders['authorization']?.startsWith('Bearer ')) {
securitySchemes.push({
type: 'http',
scheme: 'bearer',
});
}
if (requestHeaders['authorization']?.startsWith('Basic ')) {
securitySchemes.push({
type: 'http',
scheme: 'basic',
});
}
// Store minimal data in OpenAPI store - just record the endpoint and method
// This defers schema generation until actually requested
openApiStore.recordEndpoint(path, method.toLowerCase(), {
query: queryParams,
headers: requestHeaders,
contentType: requestHeaders['content-type'] || 'application/json',
body: requestBody, // Now we have the body properly captured
security: securitySchemes,
}, {
status: proxyRes.statusCode || 500,
headers: responseHeaders,
contentType: responseHeaders['content-type'] || 'application/json',
// Store raw data instead of parsed body, but still provide a body property to satisfy the type
body: '[Raw data stored]',
rawData: buffer,
});
if (verbose) {
console.log(`${method} ${path} -> ${proxyRes.statusCode}`);
}
}); // End of setImmediate
});
});
}
]
});
proxyApp.use('/', proxyMiddleware);
// Create docs app with Express
const docsApp = express();
docsApp.use(cors());
// Create documentation endpoints
docsApp.get('/har', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(harStore.getHAR()));
});
docsApp.get('/openapi.json', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(openApiStore.getOpenAPISpec()));
});
docsApp.get('/openapi.yaml', (req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.send(openApiStore.getOpenAPISpecAsYAML());
});
docsApp.get('/docs', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.send(`
<!doctype html>
<html>
<head>
<title>Scalar API Reference</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<script id="api-reference" data-url="/openapi.yaml"></script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
`);
});
// Home page with links
docsApp.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
ul { list-style-type: none; padding: 0; }
li { margin: 10px 0; }
a { color: #0366d6; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>API Documentation</h1>
<ul>
<li><a href="/docs">Swagger UI</a></li>
<li><a href="/openapi.json">OpenAPI JSON</a></li>
<li><a href="/openapi.yaml">OpenAPI YAML</a></li>
<li><a href="/har">HAR Export</a></li>
</ul>
</body>
</html>
`);
});
// Function to check if a port is available
async function isPortAvailable(port) {
return new Promise((resolve) => {
const server = createServer()
.once('error', () => {
resolve(false);
})
.once('listening', () => {
server.close();
resolve(true);
})
.listen(port);
});
}
// Function to find an available port
async function findAvailablePort(startPort) {
let port = startPort;
while (!(await isPortAvailable(port))) {
port++;
}
return port;
}
// Start servers
const availableProxyPort = await findAvailablePort(proxyPort);
const availableDocsPort = await findAvailablePort(docsPort);
if (availableProxyPort !== proxyPort) {
console.log(chalk.yellow(`Port ${proxyPort} is in use, using port ${availableProxyPort} instead`));
}
if (availableDocsPort !== docsPort) {
console.log(chalk.yellow(`Port ${docsPort} is in use, using port ${availableDocsPort} instead`));
}
// Create HTTP servers
const proxyServer = createServer(proxyApp);
const docsServer = createServer(docsApp);
// Start servers
return new Promise((resolve, reject) => {
try {
proxyServer.listen(availableProxyPort, () => {
docsServer.listen(availableDocsPort, () => {
console.log('\n' + chalk.green('Arbiter is running! 🚀'));
console.log('\n' + chalk.bold('Proxy Server:'));
console.log(chalk.cyan(` URL: http://localhost:${availableProxyPort}`));
console.log(chalk.gray(` Target: ${target}`));
console.log('\n' + chalk.bold('Documentation:'));
console.log(chalk.cyan(` API Reference: http://localhost:${availableDocsPort}/docs`));
console.log('\n' + chalk.bold('Exports:'));
console.log(chalk.cyan(` HAR Export: http://localhost:${availableDocsPort}/har`));
console.log(chalk.cyan(` OpenAPI JSON: http://localhost:${availableDocsPort}/openapi.json`));
console.log(chalk.cyan(` OpenAPI YAML: http://localhost:${availableDocsPort}/openapi.yaml`));
console.log('\n' + chalk.yellow('Press Ctrl+C to stop'));
resolve({ proxyServer, docsServer });
});
});
}
catch (error) {
reject(error);
}
});
// Handle graceful shutdown
const shutdown = (signal) => {
console.info(`Received ${signal}, shutting down...`);
proxyServer.close();
docsServer.close();
process.exit(0);
};
process.on('SIGTERM', () => {
shutdown('SIGTERM');
});
process.on('SIGINT', () => {
shutdown('SIGINT');
});
}
//# sourceMappingURL=server.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,769 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { openApiStore } from '../openApiStore.js';
import fs from 'fs';
import path from 'path';
describe('OpenAPI Store', () => {
beforeEach(() => {
// Reset the store before each test
openApiStore.clear();
openApiStore.setTargetUrl('http://localhost:8080');
});
it('should record a new endpoint', () => {
const path = '/test';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(path, method, request, response);
const spec = openApiStore.getOpenAPISpec();
const paths = spec.paths;
expect(paths).toBeDefined();
expect(paths[path]).toBeDefined();
expect(paths[path]?.[method]).toBeDefined();
const operation = paths[path]?.[method];
expect(operation).toBeDefined();
const responses = operation.responses;
expect(responses).toBeDefined();
expect(responses['200']).toBeDefined();
const responseObj = responses['200'];
expect(responseObj.content).toBeDefined();
const content = responseObj.content;
expect(content['application/json']).toBeDefined();
expect(content['application/json'].schema).toBeDefined();
});
it('should handle multiple endpoints', () => {
const endpoints = [
{
path: '/test1',
method: 'get',
response: { status: 200, body: { success: true }, contentType: 'application/json' },
},
{
path: '/test2',
method: 'post',
response: { status: 201, body: { id: 1 }, contentType: 'application/json' },
},
];
endpoints.forEach(({ path, method, response }) => {
const request = {
query: {},
body: null,
contentType: 'application/json',
};
openApiStore.recordEndpoint(path, method, request, response);
});
const spec = openApiStore.getOpenAPISpec();
const paths = spec.paths;
expect(paths).toBeDefined();
expect(Object.keys(paths)).toHaveLength(2);
const test1Path = paths['/test1'];
const test2Path = paths['/test2'];
expect(test1Path).toBeDefined();
expect(test2Path).toBeDefined();
expect(test1Path?.get).toBeDefined();
expect(test2Path?.post).toBeDefined();
});
it('should generate HAR format', () => {
// Record an endpoint first
const path = '/test';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
headers: {
'content-type': 'application/json',
},
};
openApiStore.recordEndpoint(path, method, request, response);
// Generate HAR format
const har = openApiStore.generateHAR();
expect(har.log.entries).toHaveLength(1);
expect(har.log.entries[0].request.method).toBe(method.toUpperCase());
expect(har.log.entries[0].request.url).toContain(path);
expect(har.log.entries[0].response.status).toBe(response.status);
expect(har.log.entries[0].response.content.text).toBe(JSON.stringify(response.body));
expect(har.log.entries[0].response.headers).toContainEqual({
name: 'content-type',
value: 'application/json',
});
});
it('should generate YAML spec', () => {
const endpointPath = '/test';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(endpointPath, method, request, response);
const yamlSpec = openApiStore.getOpenAPISpecAsYAML();
expect(yamlSpec).toBeDefined();
expect(yamlSpec).toContain('openapi: 3.1.0');
expect(yamlSpec).toContain('paths:');
expect(yamlSpec).toContain('/test:');
});
it('should save both JSON and YAML specs', () => {
const testDir = path.join(process.cwd(), 'test-output');
// Clean up test directory if it exists
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
const endpointPath = '/test';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(endpointPath, method, request, response);
openApiStore.saveOpenAPISpec(testDir);
// Check if files were created
expect(fs.existsSync(path.join(testDir, 'openapi.json'))).toBe(true);
expect(fs.existsSync(path.join(testDir, 'openapi.yaml'))).toBe(true);
// Clean up
fs.rmSync(testDir, { recursive: true, force: true });
});
describe('Security Schemes', () => {
it('should handle API Key authentication', () => {
const endpointPath = '/secure';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
headers: {
'X-API-Key': 'test-api-key',
},
security: [
{
type: 'apiKey',
name: 'X-API-Key',
in: 'header',
},
],
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(endpointPath, method, request, response);
const spec = openApiStore.getOpenAPISpec();
const paths = spec.paths;
const operation = paths[endpointPath]?.[method];
expect(operation.security).toBeDefined();
expect(operation.security?.[0]).toHaveProperty('apiKey_');
const securitySchemes = spec.components?.securitySchemes;
expect(securitySchemes).toBeDefined();
expect(securitySchemes?.['apiKey_']).toEqual({
type: 'apiKey',
name: 'X-API-Key',
in: 'header',
});
// Check HAR entry
const har = openApiStore.generateHAR();
const entry = har.log.entries[0];
expect(entry.request.headers).toContainEqual({
name: 'x-api-key',
value: 'test-api-key',
});
});
it('should handle OAuth2 authentication', () => {
const endpointPath = '/oauth';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
headers: {
Authorization: 'Bearer test-token',
},
security: [
{
type: 'oauth2',
flows: {
authorizationCode: {
authorizationUrl: 'https://example.com/oauth/authorize',
tokenUrl: 'https://example.com/oauth/token',
scopes: {
read: 'Read access',
write: 'Write access',
},
},
},
},
],
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(endpointPath, method, request, response);
const spec = openApiStore.getOpenAPISpec();
const paths = spec.paths;
const operation = paths[endpointPath]?.[method];
expect(operation.security).toBeDefined();
expect(operation.security?.[0]).toHaveProperty('oauth2_');
const securitySchemes = spec.components?.securitySchemes;
expect(securitySchemes).toBeDefined();
expect(securitySchemes?.['oauth2_']).toEqual({
type: 'oauth2',
flows: {
authorizationCode: {
authorizationUrl: 'https://example.com/oauth/authorize',
tokenUrl: 'https://example.com/oauth/token',
scopes: {
read: 'Read access',
write: 'Write access',
},
},
},
});
// Check HAR entry
const har = openApiStore.generateHAR();
const entry = har.log.entries[0];
expect(entry.request.headers).toContainEqual({
name: 'authorization',
value: 'Bearer test-token',
});
});
it('should handle HTTP Basic authentication', () => {
const endpointPath = '/basic';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
headers: {
Authorization: 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=',
},
security: [
{
type: 'http',
scheme: 'basic',
},
],
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(endpointPath, method, request, response);
const spec = openApiStore.getOpenAPISpec();
const paths = spec.paths;
const operation = paths[endpointPath]?.[method];
expect(operation.security).toBeDefined();
expect(operation.security?.[0]).toHaveProperty('http_');
const securitySchemes = spec.components?.securitySchemes;
expect(securitySchemes).toBeDefined();
expect(securitySchemes?.['http_']).toEqual({
type: 'http',
scheme: 'basic',
});
// Check HAR entry
const har = openApiStore.generateHAR();
const entry = har.log.entries[0];
expect(entry.request.headers).toContainEqual({
name: 'authorization',
value: 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=',
});
});
it('should handle OpenID Connect authentication', () => {
const endpointPath = '/oidc';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
headers: {
Authorization: 'Bearer test-oidc-token',
},
security: [
{
type: 'openIdConnect',
openIdConnectUrl: 'https://example.com/.well-known/openid-configuration',
},
],
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(endpointPath, method, request, response);
const spec = openApiStore.getOpenAPISpec();
const paths = spec.paths;
const operation = paths[endpointPath]?.[method];
expect(operation.security).toBeDefined();
expect(operation.security?.[0]).toHaveProperty('openIdConnect_');
const securitySchemes = spec.components?.securitySchemes;
expect(securitySchemes).toBeDefined();
expect(securitySchemes?.['openIdConnect_']).toEqual({
type: 'openIdConnect',
openIdConnectUrl: 'https://example.com/.well-known/openid-configuration',
});
// Check HAR entry
const har = openApiStore.generateHAR();
const entry = har.log.entries[0];
expect(entry.request.headers).toContainEqual({
name: 'authorization',
value: 'Bearer test-oidc-token',
});
});
it('should handle multiple security schemes', () => {
const endpointPath = '/multi-auth';
const method = 'get';
const request = {
query: {},
body: null,
contentType: 'application/json',
headers: {
'X-API-Key': 'test-api-key',
Authorization: 'Bearer test-token',
},
security: [
{
type: 'apiKey',
name: 'X-API-Key',
in: 'header',
},
{
type: 'http',
scheme: 'bearer',
},
],
};
const response = {
status: 200,
body: { success: true },
contentType: 'application/json',
};
openApiStore.recordEndpoint(endpointPath, method, request, response);
const spec = openApiStore.getOpenAPISpec();
const paths = spec.paths;
const operation = paths[endpointPath]?.[method];
expect(operation.security).toBeDefined();
expect(operation.security).toHaveLength(2);
expect(operation.security?.[0]).toHaveProperty('apiKey_');
expect(operation.security?.[1]).toHaveProperty('http_');
// Check HAR entry
const har = openApiStore.generateHAR();
const entry = har.log.entries[0];
expect(entry.request.headers).toContainEqual({
name: 'x-api-key',
value: 'test-api-key',
});
expect(entry.request.headers).toContainEqual({
name: 'authorization',
value: 'Bearer test-token',
});
});
});
describe('Schema merging', () => {
it('should merge object schemas correctly', () => {
const schemas = [
{
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
},
},
{
type: 'object',
properties: {
email: { type: 'string' },
age: { type: 'integer' },
},
},
];
const merged = openApiStore['deepMergeSchemas'](schemas);
expect(merged).toEqual({
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' },
age: {
type: 'object',
oneOf: [{ type: 'number' }, { type: 'integer' }],
},
},
});
});
it('should handle oneOf with unique schemas', () => {
const schemas = [
{ type: 'string' },
{ type: 'number' },
{ type: 'string' }, // Duplicate
];
const merged = openApiStore['deepMergeSchemas'](schemas);
expect(merged).toEqual({
type: 'object',
oneOf: [{ type: 'string' }, { type: 'number' }],
});
});
it('should handle anyOf with unique schemas', () => {
const schemas = [
{ type: 'string', format: 'email' },
{ type: 'string', format: 'uri' },
{ type: 'string', format: 'email' }, // Duplicate
];
const merged = openApiStore['deepMergeSchemas'](schemas);
expect(merged).toEqual({
type: 'object',
oneOf: [
{ type: 'string', format: 'email' },
{ type: 'string', format: 'uri' },
],
});
});
it('should handle allOf with unique schemas', () => {
const schemas = [
{ type: 'object', properties: { name: { type: 'string' } } },
{ type: 'object', properties: { age: { type: 'number' } } },
{ type: 'object', properties: { name: { type: 'string' } } }, // Duplicate
];
const merged = openApiStore['deepMergeSchemas'](schemas);
expect(merged).toEqual({
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
},
});
});
it('should handle mixed schema types', () => {
const schemas = [
{ type: 'string' },
{ type: 'object', properties: { name: { type: 'string' } } },
{ type: 'array', items: { type: 'string' } },
{ type: 'string' }, // Duplicate
];
const merged = openApiStore['deepMergeSchemas'](schemas);
expect(merged).toEqual({
type: 'object',
oneOf: [
{ type: 'string' },
{ type: 'object', properties: { name: { type: 'string' } } },
{ type: 'array', items: { type: 'string' } },
],
});
});
it('should handle nested object schemas', () => {
const schemas = [
{
type: 'object',
properties: {
user: {
type: 'object',
properties: { name: { type: 'string' } },
},
},
},
{
type: 'object',
properties: {
user: {
type: 'object',
properties: { age: { type: 'number' } },
},
},
},
];
const merged = openApiStore['deepMergeSchemas'](schemas);
expect(merged).toEqual({
type: 'object',
properties: {
user: {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
},
},
},
});
});
});
describe('Basic functionality', () => {
it('should initialize with correct default values', () => {
const spec = openApiStore.getOpenAPISpec();
expect(spec.openapi).toBe('3.1.0');
expect(spec.info.title).toBe('API Documentation');
expect(spec.info.version).toBe('1.0.0');
expect(spec.servers?.[0]?.url).toBe('http://localhost:8080');
expect(Object.keys(spec.paths || {})).toHaveLength(0);
});
it('should set target URL correctly', () => {
openApiStore.setTargetUrl('https://example.com/api');
const spec = openApiStore.getOpenAPISpec();
expect(spec.servers?.[0]?.url).toBe('https://example.com/api');
});
it('should clear stored data', () => {
// Add an endpoint
openApiStore.recordEndpoint('/test', 'get', { query: {}, headers: {}, contentType: 'application/json', body: null }, { status: 200, headers: {}, contentType: 'application/json', body: { success: true } });
// Verify it was added
const spec1 = openApiStore.getOpenAPISpec();
expect(Object.keys(spec1.paths || {})).toHaveLength(1);
// Clear and verify it's gone
openApiStore.clear();
const spec2 = openApiStore.getOpenAPISpec();
expect(Object.keys(spec2.paths || {})).toHaveLength(0);
});
});
describe('recordEndpoint', () => {
it('should record a GET endpoint with query parameters', () => {
openApiStore.recordEndpoint('/users', 'get', {
query: { limit: '10', offset: '0' },
headers: { 'accept': 'application/json' },
contentType: 'application/json',
body: null
}, {
status: 200,
headers: { 'content-type': 'application/json' },
contentType: 'application/json',
body: [{ id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Smith' }]
});
const spec = openApiStore.getOpenAPISpec();
// Check path exists
expect(spec.paths?.['/users']).toBeDefined();
expect(spec.paths?.['/users']?.get).toBeDefined();
// Check query parameters
const params = spec.paths?.['/users']?.get?.parameters;
expect(params).toBeDefined();
expect(params).toContainEqual(expect.objectContaining({
name: 'limit',
in: 'query'
}));
expect(params).toContainEqual(expect.objectContaining({
name: 'offset',
in: 'query'
}));
// Check response
expect(spec.paths?.['/users']?.get?.responses?.[200]).toBeDefined();
const content = spec.paths?.['/users']?.get?.responses?.[200]?.content;
expect(content?.['application/json']).toBeDefined();
});
it('should record a POST endpoint with request body', () => {
const requestBody = { name: 'Test User', email: 'test@example.com' };
openApiStore.recordEndpoint('/users', 'post', {
query: {},
headers: { 'content-type': 'application/json' },
contentType: 'application/json',
body: requestBody
}, {
status: 201,
headers: { 'content-type': 'application/json' },
contentType: 'application/json',
body: { id: 1, ...requestBody }
});
const spec = openApiStore.getOpenAPISpec();
// Check path exists
expect(spec.paths?.['/users']).toBeDefined();
expect(spec.paths?.['/users']?.post).toBeDefined();
// Check request body
expect(spec.paths?.['/users']?.post?.requestBody).toBeDefined();
const content = spec.paths?.['/users']?.post?.requestBody?.content;
expect(content?.['application/json']).toBeDefined();
// Check response
expect(spec.paths?.['/users']?.post?.responses?.[201]).toBeDefined();
});
it('should record path parameters correctly', () => {
openApiStore.recordEndpoint('/users/123', 'get', { query: {}, headers: {}, contentType: 'application/json', body: null }, { status: 200, headers: {}, contentType: 'application/json', body: { id: 123, name: 'John Doe' } });
// Now record another endpoint with a different ID to help OpenAPI identify the path parameter
openApiStore.recordEndpoint('/users/456', 'get', { query: {}, headers: {}, contentType: 'application/json', body: null }, { status: 200, headers: {}, contentType: 'application/json', body: { id: 456, name: 'Jane Smith' } });
const spec = openApiStore.getOpenAPISpec();
// Check that the path was correctly parameterized
expect(spec.paths?.['/users/{id}']).toBeDefined();
if (spec.paths?.['/users/{id}']) {
expect(spec.paths['/users/{id}'].get).toBeDefined();
// Check that the path parameter is defined
const params = spec.paths['/users/{id}'].get?.parameters;
expect(params).toBeDefined();
expect(params?.some(p => p.name === 'id' && p.in === 'path')).toBe(true);
}
});
it('should handle security schemes', () => {
// Record an endpoint with API Key
openApiStore.recordEndpoint('/secure', 'get', {
query: {},
headers: { 'x-api-key': 'test-key' },
contentType: 'application/json',
body: null,
security: [{ type: 'apiKey', name: 'x-api-key', in: 'header' }]
}, { status: 200, headers: {}, contentType: 'application/json', body: { message: 'Secret data' } });
// Record an endpoint with Bearer token
openApiStore.recordEndpoint('/auth/profile', 'get', {
query: {},
headers: { 'authorization': 'Bearer token123' },
contentType: 'application/json',
body: null,
security: [{ type: 'http', scheme: 'bearer' }]
}, { status: 200, headers: {}, contentType: 'application/json', body: { id: 1, username: 'admin' } });
const spec = openApiStore.getOpenAPISpec();
// Check security schemes are defined
expect(spec.components?.securitySchemes).toBeDefined();
// Check API Key security scheme
const apiKeyScheme = spec.components?.securitySchemes?.apiKey_;
expect(apiKeyScheme).toBeDefined();
expect(apiKeyScheme.type).toBe('apiKey');
expect(apiKeyScheme.in).toBe('header');
expect(apiKeyScheme.name).toBe('x-api-key');
// Check Bearer token security scheme
const bearerScheme = spec.components?.securitySchemes?.http_;
expect(bearerScheme).toBeDefined();
expect(bearerScheme.type).toBe('http');
expect(bearerScheme.scheme).toBe('bearer');
// Check security requirements on endpoints
expect(spec.paths?.['/secure']?.get?.security).toBeDefined();
expect(spec.paths?.['/auth/profile']?.get?.security).toBeDefined();
});
});
describe('Schema generation', () => {
it('should generate schema from simple object', () => {
const data = { id: 1, name: 'John Doe', active: true, age: 30 };
// @ts-ignore: Testing private method
const schema = openApiStore.generateJsonSchema(data);
expect(schema.type).toBe('object');
expect((schema.properties?.id).type).toBe('integer');
expect((schema.properties?.name).type).toBe('string');
expect((schema.properties?.active).type).toBe('boolean');
expect((schema.properties?.age).type).toBe('integer');
});
it('should generate schema from array', () => {
const data = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
];
// @ts-ignore: Testing private method
const schema = openApiStore.generateJsonSchema(data);
expect(schema.type).toBe('array');
// Using ts-ignore since we're accessing a property that might not exist on all schema types
// @ts-ignore
expect(schema.items).toBeDefined();
// @ts-ignore
expect(schema.items?.type).toBe('object');
// @ts-ignore
expect((schema.items?.properties?.id).type).toBe('integer');
// @ts-ignore
expect((schema.items?.properties?.name).type).toBe('string');
});
it('should generate schema from nested objects', () => {
const data = {
id: 1,
name: 'John Doe',
address: {
street: '123 Main St',
city: 'Anytown',
zipCode: '12345'
},
tags: ['developer', 'javascript']
};
// @ts-ignore: Testing private method
const schema = openApiStore.generateJsonSchema(data);
expect(schema.type).toBe('object');
expect((schema.properties?.address).type).toBe('object');
expect(((schema.properties?.address).properties?.street).type).toBe('string');
expect((schema.properties?.tags).type).toBe('array');
// @ts-ignore
expect((schema.properties?.tags).items?.type).toBe('string');
});
it('should handle null values', () => {
const data = { id: 1, name: 'John Doe', description: null };
// @ts-ignore: Testing private method
const schema = openApiStore.generateJsonSchema(data);
expect((schema.properties?.description).type).toBe('null');
});
it('should detect proper types for numeric values', () => {
const data = {
integer: 42,
float: 3.14,
scientific: 1e6,
zero: 0
};
// @ts-ignore: Testing private method
const schema = openApiStore.generateJsonSchema(data);
expect((schema.properties?.integer).type).toBe('integer');
expect((schema.properties?.float).type).toBe('number');
expect((schema.properties?.scientific).type).toBe('integer');
expect((schema.properties?.zero).type).toBe('integer');
});
});
describe('Structure analysis', () => {
it('should detect and generate schema for array-like structures', () => {
// @ts-ignore: Testing private method
const schema = openApiStore.generateSchemaFromStructure('[{"id":1,"name":"test"},{"id":2}]');
expect(schema.type).toBe('array');
// TypeScript doesn't recognize that an array schema will have items
// @ts-ignore
expect(schema.items).toBeDefined();
});
it('should detect and generate schema for object-like structures', () => {
// @ts-ignore: Testing private method
const schema = openApiStore.generateSchemaFromStructure('{"id":1,"name":"test","active":true}');
expect(schema.type).toBe('object');
expect(schema.properties).toBeDefined();
expect(schema.properties?.id).toBeDefined();
expect(schema.properties?.name).toBeDefined();
expect(schema.properties?.active).toBeDefined();
});
it('should handle unstructured content', () => {
// @ts-ignore: Testing private method
const schema = openApiStore.generateSchemaFromStructure('This is just plain text');
expect(schema.type).toBe('string');
});
});
describe('HAR handling', () => {
it('should generate HAR output', () => {
// Record an endpoint
openApiStore.recordEndpoint('/test', 'get', { query: {}, headers: {}, contentType: 'application/json', body: null }, { status: 200, headers: {}, contentType: 'application/json', body: { success: true } });
const har = openApiStore.generateHAR();
expect(har.log).toBeDefined();
expect(har.log.version).toBe('1.2');
expect(har.log.creator).toBeDefined();
expect(har.log.entries).toBeDefined();
expect(har.log.entries).toHaveLength(1);
const entry = har.log.entries[0];
expect(entry.request.method).toBe('GET');
expect(entry.request.url).toBe('http://localhost:8080/test');
expect(entry.response.status).toBe(200);
});
});
describe('YAML output', () => {
it('should convert OpenAPI spec to YAML', () => {
// Record an endpoint
openApiStore.recordEndpoint('/test', 'get', { query: {}, headers: {}, contentType: 'application/json', body: null }, { status: 200, headers: {}, contentType: 'application/json', body: { success: true } });
const yaml = openApiStore.getOpenAPISpecAsYAML();
expect(yaml).toContain('openapi: 3.1.0');
expect(yaml).toContain('paths:');
expect(yaml).toContain('/test:');
expect(yaml).toContain('get:');
});
});
});
//# sourceMappingURL=openApiStore.test.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,71 +0,0 @@
import type { OpenAPIV3_1 } from 'openapi-types';
export interface SecurityInfo {
type: 'apiKey' | 'oauth2' | 'http' | 'openIdConnect';
name?: string;
in?: 'header' | 'query' | 'cookie';
scheme?: string;
flows?: {
implicit?: {
authorizationUrl: string;
scopes: Record<string, string>;
};
authorizationCode?: {
authorizationUrl: string;
tokenUrl: string;
scopes: Record<string, string>;
};
clientCredentials?: {
tokenUrl: string;
scopes: Record<string, string>;
};
password?: {
tokenUrl: string;
scopes: Record<string, string>;
};
};
openIdConnectUrl?: string;
}
interface RequestInfo {
query: Record<string, string>;
body: any;
contentType: string;
headers?: Record<string, string>;
security?: SecurityInfo[];
}
interface ResponseInfo {
status: number;
body: any;
contentType: string;
headers?: Record<string, string>;
rawData?: Buffer;
}
export declare class OpenAPIStore {
private openAPIObject;
private endpoints;
private harEntries;
private targetUrl;
private examples;
private schemaCache;
private securitySchemes;
private rawDataCache;
constructor(targetUrl?: string);
setTargetUrl(url: string): void;
clear(): void;
private deepMergeSchemas;
private generateJsonSchema;
private recordHAREntry;
private buildQueryString;
private addSecurityScheme;
recordEndpoint(path: string, method: string, request: RequestInfo, response: ResponseInfo): void;
private processHAREntries;
private processRawData;
getOpenAPISpec(): OpenAPIV3_1.Document;
getOpenAPISpecAsYAML(): string;
saveOpenAPISpec(outputDir: string): void;
private getOperationForPathAndMethod;
generateHAR(): any;
private generateSchemaFromStructure;
private cleanJsonString;
}
export declare const openApiStore: OpenAPIStore;
export {};

View File

@@ -1,906 +0,0 @@
import fs from 'fs';
import path from 'path';
import { stringify } from 'yaml';
import zlib from 'zlib';
export class OpenAPIStore {
openAPIObject = null;
endpoints = new Map();
harEntries = [];
targetUrl;
examples = new Map();
schemaCache = new Map();
securitySchemes = new Map();
rawDataCache = new Map();
constructor(targetUrl = 'http://localhost:3000') {
this.targetUrl = targetUrl;
this.openAPIObject = {
openapi: '3.1.0',
info: {
title: 'API Documentation',
version: '1.0.0',
},
paths: {},
components: {
schemas: {},
securitySchemes: {},
},
};
}
setTargetUrl(url) {
this.targetUrl = url;
}
clear() {
this.endpoints.clear();
this.harEntries = [];
this.examples.clear();
this.schemaCache.clear();
this.securitySchemes.clear();
this.rawDataCache.clear();
}
deepMergeSchemas(schemas) {
if (schemas.length === 0)
return { type: 'object' };
if (schemas.length === 1)
return schemas[0];
// If all schemas are objects, merge their properties
if (schemas.every((s) => s.type === 'object')) {
const mergedProperties = {};
const mergedRequired = [];
schemas.forEach((schema) => {
if (schema.properties) {
Object.entries(schema.properties).forEach(([key, value]) => {
if (!mergedProperties[key]) {
mergedProperties[key] = value;
}
else {
// If property exists, merge its schemas
mergedProperties[key] = this.deepMergeSchemas([mergedProperties[key], value]);
}
});
}
});
return {
type: 'object',
properties: mergedProperties,
};
}
// If schemas are different types, use oneOf with unique schemas
const uniqueSchemas = schemas.filter((schema, index, self) => index === self.findIndex((s) => JSON.stringify(s) === JSON.stringify(schema)));
if (uniqueSchemas.length === 1) {
return uniqueSchemas[0];
}
return {
type: 'object',
oneOf: uniqueSchemas,
};
}
generateJsonSchema(obj) {
if (obj === null)
return { type: 'null' };
if (Array.isArray(obj)) {
if (obj.length === 0)
return { type: 'array', items: { type: 'object' } };
// Check if all items are objects with similar structure
const allObjects = obj.every(item => typeof item === 'object' && item !== null && !Array.isArray(item));
if (allObjects) {
// Generate a schema for the first object
const firstObjectSchema = this.generateJsonSchema(obj[0]);
// Use that as a template for all items
return {
type: 'array',
items: firstObjectSchema,
example: obj,
};
}
// Check if all items are primitives of the same type
if (obj.length > 0 &&
obj.every(item => typeof item === 'string' ||
typeof item === 'number' ||
typeof item === 'boolean')) {
// Handle arrays of primitives
const firstItemType = typeof obj[0];
if (obj.every(item => typeof item === firstItemType)) {
// For numbers, check if they're all integers
if (firstItemType === 'number') {
const isAllIntegers = obj.every(Number.isInteger);
return {
type: 'array',
items: {
type: isAllIntegers ? 'integer' : 'number'
},
example: obj
};
}
// For strings and booleans
return {
type: 'array',
items: {
type: firstItemType
},
example: obj
};
}
}
// Generate schemas for all items
const itemSchemas = obj.map((item) => this.generateJsonSchema(item));
// If all items have the same schema, use that
if (itemSchemas.every((s) => JSON.stringify(s) === JSON.stringify(itemSchemas[0]))) {
return {
type: 'array',
items: itemSchemas[0],
example: obj,
};
}
// If items have different schemas, use oneOf
return {
type: 'array',
items: {
type: 'object',
oneOf: itemSchemas,
},
example: obj,
};
}
if (typeof obj === 'object') {
const properties = {};
for (const [key, value] of Object.entries(obj)) {
properties[key] = this.generateJsonSchema(value);
}
return {
type: 'object',
properties,
example: obj,
};
}
// Special handling for numbers to distinguish between integer and number
if (typeof obj === 'number') {
// Check if the number is an integer
if (Number.isInteger(obj)) {
return {
type: 'integer',
example: obj,
};
}
return {
type: 'number',
example: obj,
};
}
// Map JavaScript types to OpenAPI types
const typeMap = {
string: 'string',
boolean: 'boolean',
bigint: 'integer',
symbol: 'string',
undefined: 'string',
function: 'string',
};
return {
type: typeMap[typeof obj] || 'string',
example: obj,
};
}
recordHAREntry(path, method, request, response) {
const now = new Date();
const url = new URL(path, this.targetUrl);
// Add query parameters from request.query
Object.entries(request.query || {}).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
const entry = {
startedDateTime: now.toISOString(),
time: 0,
request: {
method: method.toUpperCase(),
url: url.toString(),
httpVersion: 'HTTP/1.1',
headers: Object.entries(request.headers || {}).map(([name, value]) => ({
name: name.toLowerCase(), // Normalize header names
value: String(value), // Ensure value is a string
})),
queryString: Object.entries(request.query || {}).map(([name, value]) => ({
name,
value: String(value), // Ensure value is a string
})),
// Ensure postData is properly included for all requests with body
postData: request.body ? {
mimeType: request.contentType,
text: typeof request.body === 'string' ? request.body : JSON.stringify(request.body),
} : undefined,
},
response: {
status: response.status,
statusText: response.status === 200 ? 'OK' : 'Error',
httpVersion: 'HTTP/1.1',
headers: Object.entries(response.headers || {}).map(([name, value]) => ({
name: name.toLowerCase(), // Normalize header names
value: String(value), // Ensure value is a string
})),
content: {
// If rawData is available, just store size but defer content processing
size: response.rawData ? response.rawData.length :
response.body ? JSON.stringify(response.body).length : 0,
mimeType: response.contentType || 'application/json',
// Use a placeholder for rawData, or convert body as before
text: response.rawData ? '[Content stored but not processed for performance]' :
typeof response.body === 'string' ? response.body : JSON.stringify(response.body),
},
},
};
this.harEntries.push(entry);
}
buildQueryString(query) {
if (!query || Object.keys(query).length === 0) {
return '';
}
const params = new URLSearchParams();
Object.entries(query).forEach(([key, value]) => {
params.append(key, value);
});
return `?${params.toString()}`;
}
addSecurityScheme(security) {
// Use a consistent name based on the type with underscore suffix
const schemeName = security.type === 'apiKey' ? 'apiKey_' : `${security.type}_`;
let scheme;
switch (security.type) {
case 'apiKey':
scheme = {
type: 'apiKey',
name: security.name || 'x-api-key',
in: security.in || 'header',
};
break;
case 'oauth2':
scheme = {
type: 'oauth2',
flows: security.flows || {
implicit: {
authorizationUrl: 'https://example.com/oauth/authorize',
scopes: {
read: 'Read access',
write: 'Write access',
},
},
},
};
break;
case 'http':
scheme = {
type: 'http',
scheme: security.scheme || 'bearer',
};
break;
case 'openIdConnect':
scheme = {
type: 'openIdConnect',
openIdConnectUrl: security.openIdConnectUrl || 'https://example.com/.well-known/openid-configuration',
};
break;
default:
throw new Error(`Unsupported security type: ${security.type}`);
}
this.securitySchemes.set(schemeName, scheme);
return schemeName;
}
recordEndpoint(path, method, request, response) {
// Convert path parameters to OpenAPI format
const openApiPath = path.replace(/\/(\d+)/g, '/{id}').replace(/:(\w+)/g, '{$1}');
const key = `${method}:${openApiPath}`;
const endpoint = this.endpoints.get(key) || {
path: openApiPath,
method,
responses: {},
parameters: [],
requestBody: method.toLowerCase() === 'get'
? undefined
: {
required: false,
content: {},
},
};
// Add security schemes if present
if (request.security) {
endpoint.security = request.security.map((security) => {
const schemeName = this.addSecurityScheme(security);
return { [schemeName]: [] }; // Empty array for scopes
});
}
// Add path parameters
const pathParams = openApiPath.match(/\{(\w+)\}/g) || [];
pathParams.forEach((param) => {
const paramName = param.slice(1, -1);
if (!endpoint.parameters.some((p) => p.name === paramName)) {
endpoint.parameters.push({
name: paramName,
in: 'path',
required: true,
schema: {
type: 'string',
},
});
}
});
// Add query parameters
Object.entries(request.query).forEach(([key, value]) => {
if (!endpoint.parameters.some((p) => p.name === key)) {
endpoint.parameters.push({
name: key,
in: 'query',
schema: {
type: 'string',
},
});
}
});
// Add request headers as parameters
if (request.headers) {
Object.entries(request.headers).forEach(([name, value]) => {
if (!endpoint.parameters.some((p) => p.name === name)) {
endpoint.parameters.push({
name: name,
in: 'header',
required: false,
schema: {
type: 'string',
example: value,
},
});
}
});
}
// Add request body schema if present and not a GET request
if (request.body && method.toLowerCase() !== 'get') {
const contentType = request.contentType || 'application/json';
if (endpoint.requestBody && !endpoint.requestBody.content[contentType]) {
const schema = this.generateJsonSchema(request.body);
endpoint.requestBody.content[contentType] = {
schema,
};
}
}
// Add response schema
const responseContentType = response.contentType || 'application/json';
// Initialize response object if it doesn't exist
if (!endpoint.responses[response.status]) {
endpoint.responses[response.status] = {
description: `Response for ${method.toUpperCase()} ${path}`,
content: {},
};
}
// Ensure content object exists
const responseObj = endpoint.responses[response.status];
if (!responseObj.content) {
responseObj.content = {};
}
// Skip schema generation if we're using rawData for deferred processing
if (!response.rawData) {
// Generate schema for the current response
const currentSchema = this.generateJsonSchema(response.body);
// Get existing schemas for this endpoint and status code
const schemaKey = `${key}:${response.status}:${responseContentType}`;
const existingSchemas = this.schemaCache.get(schemaKey) || [];
// Add the current schema to the cache
existingSchemas.push(currentSchema);
this.schemaCache.set(schemaKey, existingSchemas);
// Merge all schemas for this endpoint and status code
const mergedSchema = this.deepMergeSchemas(existingSchemas);
// Update the content with the merged schema
responseObj.content[responseContentType] = {
schema: mergedSchema,
};
}
else {
// Just create a placeholder schema when using deferred processing
responseObj.content[responseContentType] = {
schema: {
type: 'object',
description: 'Schema generation deferred to improve performance'
},
};
// Store the raw data for later processing
let pathMap = this.rawDataCache.get(path);
if (!pathMap) {
pathMap = new Map();
this.rawDataCache.set(path, pathMap);
}
pathMap.set(method, {
rawData: response.rawData ? response.rawData.toString('base64') : '',
status: response.status,
headers: response.headers,
});
}
// Add response headers
if (response.headers && Object.keys(response.headers).length > 0) {
endpoint.responses[response.status].headers = Object.entries(response.headers).reduce((acc, [name, value]) => {
acc[name] = {
schema: {
type: 'string',
example: value,
},
description: `Response header ${name}`,
};
return acc;
}, {});
}
this.endpoints.set(key, endpoint);
// Record in HAR
this.recordHAREntry(path, method, request, response);
}
// Process any raw data in HAR entries before returning
processHAREntries() {
// For each HAR entry with placeholder text, process the raw data
for (let i = 0; i < this.harEntries.length; i++) {
const entry = this.harEntries[i];
// Check if this entry has deferred processing
if (entry.response.content.text === '[Content stored but not processed for performance]') {
try {
// Get the URL path and method
const url = new URL(entry.request.url);
const path = url.pathname;
const method = entry.request.method.toLowerCase();
// Try to get the raw data from our cache
const pathMap = this.rawDataCache.get(path);
if (!pathMap)
continue;
const responseData = pathMap.get(method);
if (!responseData || !responseData.rawData)
continue;
// Get content type and encoding info
const contentEncoding = entry.response.headers.find(h => h.name.toLowerCase() === 'content-encoding')?.value;
// Process based on content type and encoding
let text;
// Handle compressed content
if (contentEncoding && contentEncoding.includes('gzip')) {
const buffer = Buffer.from(responseData.rawData, 'base64');
const gunzipped = zlib.gunzipSync(buffer);
text = gunzipped.toString('utf-8');
}
else {
// Handle non-compressed content
const buffer = Buffer.from(responseData.rawData, 'base64');
text = buffer.toString('utf-8');
}
// Process based on content type
const contentType = entry.response.content.mimeType;
if (contentType.includes('json')) {
try {
// First attempt standard JSON parsing
const jsonData = JSON.parse(text);
entry.response.content.text = JSON.stringify(jsonData);
}
catch (e) {
// Try cleaning the JSON first
try {
// Clean the JSON string
const cleanedText = this.cleanJsonString(text);
const jsonData = JSON.parse(cleanedText);
entry.response.content.text = JSON.stringify(jsonData);
}
catch (e2) {
// If parsing still fails, fall back to the raw text
entry.response.content.text = text;
}
}
}
else {
// For non-JSON content, just use the text
entry.response.content.text = text;
}
}
catch (error) {
entry.response.content.text = '[Error processing content]';
}
}
}
}
// Process any raw data before generating OpenAPI specs
processRawData() {
if (!this.rawDataCache || this.rawDataCache.size === 0)
return;
// Process each path and method in the raw data cache
for (const [path, methodMap] of this.rawDataCache.entries()) {
for (const [method, responseData] of methodMap.entries()) {
const operation = this.getOperationForPathAndMethod(path, method);
if (!operation)
continue;
const { rawData, status, headers = {} } = responseData;
if (!rawData)
continue;
// Find the response object for this status code
const responseKey = status.toString();
if (!operation.responses) {
operation.responses = {};
}
if (!operation.responses[responseKey]) {
operation.responses[responseKey] = {
description: `Response for status code ${responseKey}`
};
}
const response = operation.responses[responseKey];
if (!response.content) {
response.content = {};
}
// Determine content type from headers
let contentType = 'application/json'; // Default
const contentTypeHeader = Object.keys(headers)
.find(key => key.toLowerCase() === 'content-type');
if (contentTypeHeader && headers[contentTypeHeader]) {
contentType = headers[contentTypeHeader].split(';')[0];
}
// Check if content is compressed
const contentEncodingHeader = Object.keys(headers)
.find(key => key.toLowerCase() === 'content-encoding');
const contentEncoding = contentEncodingHeader ? headers[contentEncodingHeader] : null;
// Process based on encoding and content type
try {
let text;
// Handle compressed content
if (contentEncoding && contentEncoding.includes('gzip')) {
const buffer = Buffer.from(rawData, 'base64');
const gunzipped = zlib.gunzipSync(buffer);
text = gunzipped.toString('utf-8');
}
else {
// Handle non-compressed content
// Base64 decode if needed
const buffer = Buffer.from(rawData, 'base64');
text = buffer.toString('utf-8');
}
// Process based on content type
if (contentType.includes('json')) {
try {
// First attempt standard JSON parsing
const jsonData = JSON.parse(text);
const schema = this.generateJsonSchema(jsonData);
response.content[contentType] = {
schema
};
}
catch (e) {
// Try cleaning the JSON first
try {
// Clean the JSON string
const cleanedText = this.cleanJsonString(text);
const jsonData = JSON.parse(cleanedText);
const schema = this.generateJsonSchema(jsonData);
response.content[contentType] = {
schema
};
}
catch (e2) {
// If parsing still fails, try to infer the schema from structure
if (text.trim().startsWith('{') || text.trim().startsWith('[')) {
// Looks like JSON-like structure, infer schema
const schema = this.generateSchemaFromStructure(text);
response.content[contentType] = {
schema
};
}
else {
// Not JSON-like, treat as string
response.content[contentType] = {
schema: {
type: 'string',
description: 'Non-parseable content'
}
};
}
}
}
}
else if (contentType.includes('xml')) {
// Handle XML content
response.content[contentType] = {
schema: {
type: 'string',
format: 'xml',
description: 'XML content'
}
};
}
else if (contentType.includes('image/')) {
// Handle image content
response.content[contentType] = {
schema: {
type: 'string',
format: 'binary',
description: 'Image content'
}
};
}
else {
// Handle other content types
response.content[contentType] = {
schema: {
type: 'string',
description: text.length > 100 ?
`${text.substring(0, 100)}...` :
text
}
};
}
}
catch (error) {
// Handle errors during processing
console.error(`Error processing raw data for ${path} ${method}:`, error);
response.content['text/plain'] = {
schema: {
type: 'string',
description: 'Error processing content'
}
};
}
}
}
// Clear processed data
this.rawDataCache.clear();
}
getOpenAPISpec() {
// Process any deferred raw data before generating the spec
this.processRawData();
const paths = Array.from(this.endpoints.entries()).reduce((acc, [key, info]) => {
const [method, path] = key.split(':');
if (!acc[path]) {
acc[path] = {};
}
const operation = {
summary: `${method.toUpperCase()} ${path}`,
responses: info.responses,
};
// Only include parameters if there are any
if (info.parameters.length > 0) {
// Filter out duplicate parameters and format them correctly
const uniqueParams = info.parameters.reduce((params, param) => {
const existing = params.find((p) => p.name === param.name && p.in === param.in);
if (!existing) {
const formattedParam = {
name: param.name,
in: param.in,
schema: {
type: 'string',
},
};
// Only add required field for path parameters
if (param.in === 'path') {
formattedParam.required = true;
}
// Only add example for header parameters
if (param.in === 'header' && param.schema && 'example' in param.schema) {
formattedParam.schema.example =
param.schema.example;
}
params.push(formattedParam);
}
return params;
}, []);
operation.parameters = uniqueParams;
}
// Only include requestBody if it exists
if (info.requestBody) {
operation.requestBody = info.requestBody;
}
// Only add security if it exists
if (info.security) {
operation.security = info.security;
}
// @ts-ignore - TypeScript index expression issue
acc[path][method.toLowerCase()] = operation;
return acc;
}, {});
const spec = {
openapi: '3.1.0',
info: {
title: 'API Documentation',
version: '1.0.0',
description: 'Automatically generated API documentation from proxy traffic',
},
servers: [
{
url: this.targetUrl,
},
],
paths,
components: {
securitySchemes: Object.fromEntries(this.securitySchemes),
schemas: {},
},
};
return spec;
}
getOpenAPISpecAsYAML() {
const spec = this.getOpenAPISpec();
return stringify(spec, {
indent: 2,
simpleKeys: true,
aliasDuplicateObjects: false,
strict: true,
});
}
saveOpenAPISpec(outputDir) {
const spec = this.getOpenAPISpec();
const yamlSpec = this.getOpenAPISpecAsYAML();
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Save JSON spec
fs.writeFileSync(path.join(outputDir, 'openapi.json'), JSON.stringify(spec, null, 2));
// Save YAML spec
fs.writeFileSync(path.join(outputDir, 'openapi.yaml'), yamlSpec);
}
// Get operation for a path and method
getOperationForPathAndMethod(path, method) {
// Convert path parameters to OpenAPI format if needed
const openApiPath = path.replace(/\/(\d+)/g, '/{id}').replace(/:(\w+)/g, '{$1}');
const key = `${method}:${openApiPath}`;
return this.endpoints.get(key);
}
generateHAR() {
// Process any raw data before generating HAR
this.processHAREntries();
return {
log: {
version: '1.2',
creator: {
name: 'Arbiter',
version: '1.0.0',
},
entries: this.harEntries,
},
};
}
// Generate a schema by analyzing the structure of a text that might be JSON-like
generateSchemaFromStructure(text) {
// First, try to determine if this is an array or object
const trimmedText = text.trim();
if (trimmedText.startsWith('[') && trimmedText.endsWith(']')) {
// Looks like an array
return {
type: 'array',
description: 'Array-like structure detected',
items: {
type: 'object',
description: 'Array items (structure inferred)'
}
};
}
if (trimmedText.startsWith('{') && trimmedText.endsWith('}')) {
// Looks like an object - try to extract some field names
try {
// Extract property names using a regex that looks for different "key": patterns
// This matcher is more flexible and can handle single quotes, double quotes, and unquoted keys
const propMatches = trimmedText.match(/["']?([a-zA-Z0-9_$]+)["']?\s*:/g) || [];
if (propMatches.length > 0) {
const properties = {};
// Extract property names and create a basic schema
propMatches.forEach(match => {
// Clean up the property name by removing quotes and colon
const propName = match.replace(/["']/g, '').replace(':', '').trim();
if (propName && !properties[propName]) {
// Try to guess the type based on what follows the property
const propPattern = new RegExp(`["']?${propName}["']?\\s*:\\s*(.{1,50})`, 'g');
const valueMatch = propPattern.exec(trimmedText);
if (valueMatch && valueMatch[1]) {
const valueStart = valueMatch[1].trim();
if (valueStart.startsWith('{')) {
properties[propName] = {
type: 'object',
description: 'Nested object detected'
};
}
else if (valueStart.startsWith('[')) {
properties[propName] = {
type: 'array',
description: 'Array value detected',
items: {
type: 'object',
description: 'Array items (structure inferred)'
}
};
}
else if (valueStart.startsWith('"') || valueStart.startsWith("'")) {
properties[propName] = {
type: 'string',
};
}
else if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?/.test(valueStart)) {
properties[propName] = {
type: valueStart.includes('.') ? 'number' : 'integer',
};
}
else if (valueStart.startsWith('true') || valueStart.startsWith('false')) {
properties[propName] = {
type: 'boolean',
};
}
else if (valueStart.startsWith('null')) {
properties[propName] = {
type: 'null',
};
}
else {
properties[propName] = {
type: 'string',
description: 'Property detected by structure analysis'
};
}
}
else {
properties[propName] = {
type: 'string',
description: 'Property detected by structure analysis'
};
}
}
});
return {
type: 'object',
properties,
description: 'Object structure detected with properties'
};
}
}
catch (e) {
// If property extraction fails, fall back to a generic object schema
}
// Generic object
return {
type: 'object',
description: 'Object-like structure detected'
};
}
// Not clearly structured as JSON
return {
type: 'string',
description: 'Unstructured content'
};
}
// Helper to clean up potential JSON issues
cleanJsonString(text) {
try {
// Remove JavaScript-style comments
let cleaned = text
.replace(/\/\/.*$/gm, '') // Remove single line comments
.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
// Handle trailing commas in objects and arrays
cleaned = cleaned
.replace(/,\s*}/g, '}')
.replace(/,\s*\]/g, ']');
// Fix unquoted property names (only basic cases)
cleaned = cleaned.replace(/([{,]\s*)([a-zA-Z0-9_$]+)(\s*:)/g, '$1"$2"$3');
// Fix single quotes used for strings (convert to double quotes)
// This is complex - we need to avoid replacing quotes inside quotes
let inString = false;
let inSingleQuotedString = false;
let result = '';
for (let i = 0; i < cleaned.length; i++) {
const char = cleaned[i];
const prevChar = i > 0 ? cleaned[i - 1] : '';
// Handle escape sequences
if (prevChar === '\\') {
result += char;
continue;
}
if (char === '"' && !inSingleQuotedString) {
inString = !inString;
result += char;
}
else if (char === "'" && !inString) {
inSingleQuotedString = !inSingleQuotedString;
result += '"'; // Replace single quote with double quote
}
else {
result += char;
}
}
return result;
}
catch (e) {
// If cleaning fails, return the original text
return text;
}
}
}
export const openApiStore = new OpenAPIStore();
//# sourceMappingURL=openApiStore.js.map

File diff suppressed because one or more lines are too long

5
dist/src/types.d.ts vendored
View File

@@ -1,5 +0,0 @@
import type { Hono } from 'hono';
export interface ServerConfig {
fetch: Hono['fetch'];
port: number;
}

2
dist/src/types.js vendored
View File

@@ -1,2 +0,0 @@
export {};
//# sourceMappingURL=types.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}

View File

@@ -1,2 +0,0 @@
declare const _default: import("vite").UserConfig;
export default _default;

23
dist/vitest.config.js vendored
View File

@@ -1,23 +0,0 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: [
'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
'integration/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'
],
environment: 'node',
globals: true,
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'dist/',
'**/*.d.ts',
'**/*.test.ts',
'vitest.config.ts',
],
},
},
});
//# sourceMappingURL=vitest.config.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"vitest.config.js","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE;QACJ,OAAO,EAAE;YACP,sDAAsD;YACtD,8DAA8D;SAC/D;QACD,WAAW,EAAE,MAAM;QACnB,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE;YACR,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAClC,OAAO,EAAE;gBACP,eAAe;gBACf,OAAO;gBACP,WAAW;gBACX,cAAc;gBACd,kBAAkB;aACnB;SACF;KACF;CACF,CAAC,CAAC"}

View File

@@ -1,11 +0,0 @@
"use strict";
module.exports = function(it) {
const { pluginName } = it;
return `
ESLint couldn't find the plugin "${pluginName}". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name.
If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
`.trimStart();
};

View File

@@ -1,20 +0,0 @@
var Ajv = require('ajv');
var ajv = new Ajv({allErrors: true});
var schema = {
"properties": {
"foo": { "type": "string" },
"bar": { "type": "number", "maximum": 3 }
}
};
var validate = ajv.compile(schema);
test({"foo": "abc", "bar": 2});
test({"foo": 2, "bar": 4});
function test(data) {
var valid = validate(data);
if (valid) console.log('Valid!');
else console.log('Invalid: ' + ajv.errorsText(validate.errors));
}

View File

@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-2017 Evgeny Poberezkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,397 +0,0 @@
declare var ajv: {
(options?: ajv.Options): ajv.Ajv;
new(options?: ajv.Options): ajv.Ajv;
ValidationError: typeof AjvErrors.ValidationError;
MissingRefError: typeof AjvErrors.MissingRefError;
$dataMetaSchema: object;
}
declare namespace AjvErrors {
class ValidationError extends Error {
constructor(errors: Array<ajv.ErrorObject>);
message: string;
errors: Array<ajv.ErrorObject>;
ajv: true;
validation: true;
}
class MissingRefError extends Error {
constructor(baseId: string, ref: string, message?: string);
static message: (baseId: string, ref: string) => string;
message: string;
missingRef: string;
missingSchema: string;
}
}
declare namespace ajv {
type ValidationError = AjvErrors.ValidationError;
type MissingRefError = AjvErrors.MissingRefError;
interface Ajv {
/**
* Validate data using schema
* Schema will be compiled and cached (using serialized JSON as key, [fast-json-stable-stringify](https://github.com/epoberezkin/fast-json-stable-stringify) is used to serialize by default).
* @param {string|object|Boolean} schemaKeyRef key, ref or schema object
* @param {Any} data to be validated
* @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`).
*/
validate(schemaKeyRef: object | string | boolean, data: any): boolean | PromiseLike<any>;
/**
* Create validating function for passed schema.
* @param {object|Boolean} schema schema object
* @return {Function} validating function
*/
compile(schema: object | boolean): ValidateFunction;
/**
* Creates validating function for passed schema with asynchronous loading of missing schemas.
* `loadSchema` option should be a function that accepts schema uri and node-style callback.
* @this Ajv
* @param {object|Boolean} schema schema object
* @param {Boolean} meta optional true to compile meta-schema; this parameter can be skipped
* @param {Function} callback optional node-style callback, it is always called with 2 parameters: error (or null) and validating function.
* @return {PromiseLike<ValidateFunction>} validating function
*/
compileAsync(schema: object | boolean, meta?: Boolean, callback?: (err: Error, validate: ValidateFunction) => any): PromiseLike<ValidateFunction>;
/**
* Adds schema to the instance.
* @param {object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored.
* @param {string} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`.
* @return {Ajv} this for method chaining
*/
addSchema(schema: Array<object> | object, key?: string): Ajv;
/**
* Add schema that will be used to validate other schemas
* options in META_IGNORE_OPTIONS are alway set to false
* @param {object} schema schema object
* @param {string} key optional schema key
* @return {Ajv} this for method chaining
*/
addMetaSchema(schema: object, key?: string): Ajv;
/**
* Validate schema
* @param {object|Boolean} schema schema to validate
* @return {Boolean} true if schema is valid
*/
validateSchema(schema: object | boolean): boolean;
/**
* Get compiled schema from the instance by `key` or `ref`.
* @param {string} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id).
* @return {Function} schema validating function (with property `schema`). Returns undefined if keyRef can't be resolved to an existing schema.
*/
getSchema(keyRef: string): ValidateFunction | undefined;
/**
* Remove cached schema(s).
* If no parameter is passed all schemas but meta-schemas are removed.
* If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
* Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
* @param {string|object|RegExp|Boolean} schemaKeyRef key, ref, pattern to match key/ref or schema object
* @return {Ajv} this for method chaining
*/
removeSchema(schemaKeyRef?: object | string | RegExp | boolean): Ajv;
/**
* Add custom format
* @param {string} name format name
* @param {string|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid)
* @return {Ajv} this for method chaining
*/
addFormat(name: string, format: FormatValidator | FormatDefinition): Ajv;
/**
* Define custom keyword
* @this Ajv
* @param {string} keyword custom keyword, should be a valid identifier, should be different from all standard, custom and macro keywords.
* @param {object} definition keyword definition object with properties `type` (type(s) which the keyword applies to), `validate` or `compile`.
* @return {Ajv} this for method chaining
*/
addKeyword(keyword: string, definition: KeywordDefinition): Ajv;
/**
* Get keyword definition
* @this Ajv
* @param {string} keyword pre-defined or custom keyword.
* @return {object|Boolean} custom keyword definition, `true` if it is a predefined keyword, `false` otherwise.
*/
getKeyword(keyword: string): object | boolean;
/**
* Remove keyword
* @this Ajv
* @param {string} keyword pre-defined or custom keyword.
* @return {Ajv} this for method chaining
*/
removeKeyword(keyword: string): Ajv;
/**
* Validate keyword
* @this Ajv
* @param {object} definition keyword definition object
* @param {boolean} throwError true to throw exception if definition is invalid
* @return {boolean} validation result
*/
validateKeyword(definition: KeywordDefinition, throwError: boolean): boolean;
/**
* Convert array of error message objects to string
* @param {Array<object>} errors optional array of validation errors, if not passed errors from the instance are used.
* @param {object} options optional options with properties `separator` and `dataVar`.
* @return {string} human readable string with all errors descriptions
*/
errorsText(errors?: Array<ErrorObject> | null, options?: ErrorsTextOptions): string;
errors?: Array<ErrorObject> | null;
_opts: Options;
}
interface CustomLogger {
log(...args: any[]): any;
warn(...args: any[]): any;
error(...args: any[]): any;
}
interface ValidateFunction {
(
data: any,
dataPath?: string,
parentData?: object | Array<any>,
parentDataProperty?: string | number,
rootData?: object | Array<any>
): boolean | PromiseLike<any>;
schema?: object | boolean;
errors?: null | Array<ErrorObject>;
refs?: object;
refVal?: Array<any>;
root?: ValidateFunction | object;
$async?: true;
source?: object;
}
interface Options {
$data?: boolean;
allErrors?: boolean;
verbose?: boolean;
jsonPointers?: boolean;
uniqueItems?: boolean;
unicode?: boolean;
format?: false | string;
formats?: object;
keywords?: object;
unknownFormats?: true | string[] | 'ignore';
schemas?: Array<object> | object;
schemaId?: '$id' | 'id' | 'auto';
missingRefs?: true | 'ignore' | 'fail';
extendRefs?: true | 'ignore' | 'fail';
loadSchema?: (uri: string, cb?: (err: Error, schema: object) => void) => PromiseLike<object | boolean>;
removeAdditional?: boolean | 'all' | 'failing';
useDefaults?: boolean | 'empty' | 'shared';
coerceTypes?: boolean | 'array';
strictDefaults?: boolean | 'log';
strictKeywords?: boolean | 'log';
strictNumbers?: boolean;
async?: boolean | string;
transpile?: string | ((code: string) => string);
meta?: boolean | object;
validateSchema?: boolean | 'log';
addUsedSchema?: boolean;
inlineRefs?: boolean | number;
passContext?: boolean;
loopRequired?: number;
ownProperties?: boolean;
multipleOfPrecision?: boolean | number;
errorDataPath?: string,
messages?: boolean;
sourceCode?: boolean;
processCode?: (code: string, schema: object) => string;
cache?: object;
logger?: CustomLogger | false;
nullable?: boolean;
serialize?: ((schema: object | boolean) => any) | false;
}
type FormatValidator = string | RegExp | ((data: string) => boolean | PromiseLike<any>);
type NumberFormatValidator = ((data: number) => boolean | PromiseLike<any>);
interface NumberFormatDefinition {
type: "number",
validate: NumberFormatValidator;
compare?: (data1: number, data2: number) => number;
async?: boolean;
}
interface StringFormatDefinition {
type?: "string",
validate: FormatValidator;
compare?: (data1: string, data2: string) => number;
async?: boolean;
}
type FormatDefinition = NumberFormatDefinition | StringFormatDefinition;
interface KeywordDefinition {
type?: string | Array<string>;
async?: boolean;
$data?: boolean;
errors?: boolean | string;
metaSchema?: object;
// schema: false makes validate not to expect schema (ValidateFunction)
schema?: boolean;
statements?: boolean;
dependencies?: Array<string>;
modifying?: boolean;
valid?: boolean;
// one and only one of the following properties should be present
validate?: SchemaValidateFunction | ValidateFunction;
compile?: (schema: any, parentSchema: object, it: CompilationContext) => ValidateFunction;
macro?: (schema: any, parentSchema: object, it: CompilationContext) => object | boolean;
inline?: (it: CompilationContext, keyword: string, schema: any, parentSchema: object) => string;
}
interface CompilationContext {
level: number;
dataLevel: number;
dataPathArr: string[];
schema: any;
schemaPath: string;
baseId: string;
async: boolean;
opts: Options;
formats: {
[index: string]: FormatDefinition | undefined;
};
keywords: {
[index: string]: KeywordDefinition | undefined;
};
compositeRule: boolean;
validate: (schema: object) => boolean;
util: {
copy(obj: any, target?: any): any;
toHash(source: string[]): { [index: string]: true | undefined };
equal(obj: any, target: any): boolean;
getProperty(str: string): string;
schemaHasRules(schema: object, rules: any): string;
escapeQuotes(str: string): string;
toQuotedString(str: string): string;
getData(jsonPointer: string, dataLevel: number, paths: string[]): string;
escapeJsonPointer(str: string): string;
unescapeJsonPointer(str: string): string;
escapeFragment(str: string): string;
unescapeFragment(str: string): string;
};
self: Ajv;
}
interface SchemaValidateFunction {
(
schema: any,
data: any,
parentSchema?: object,
dataPath?: string,
parentData?: object | Array<any>,
parentDataProperty?: string | number,
rootData?: object | Array<any>
): boolean | PromiseLike<any>;
errors?: Array<ErrorObject>;
}
interface ErrorsTextOptions {
separator?: string;
dataVar?: string;
}
interface ErrorObject {
keyword: string;
dataPath: string;
schemaPath: string;
params: ErrorParameters;
// Added to validation errors of propertyNames keyword schema
propertyName?: string;
// Excluded if messages set to false.
message?: string;
// These are added with the `verbose` option.
schema?: any;
parentSchema?: object;
data?: any;
}
type ErrorParameters = RefParams | LimitParams | AdditionalPropertiesParams |
DependenciesParams | FormatParams | ComparisonParams |
MultipleOfParams | PatternParams | RequiredParams |
TypeParams | UniqueItemsParams | CustomParams |
PatternRequiredParams | PropertyNamesParams |
IfParams | SwitchParams | NoParams | EnumParams;
interface RefParams {
ref: string;
}
interface LimitParams {
limit: number;
}
interface AdditionalPropertiesParams {
additionalProperty: string;
}
interface DependenciesParams {
property: string;
missingProperty: string;
depsCount: number;
deps: string;
}
interface FormatParams {
format: string
}
interface ComparisonParams {
comparison: string;
limit: number | string;
exclusive: boolean;
}
interface MultipleOfParams {
multipleOf: number;
}
interface PatternParams {
pattern: string;
}
interface RequiredParams {
missingProperty: string;
}
interface TypeParams {
type: string;
}
interface UniqueItemsParams {
i: number;
j: number;
}
interface CustomParams {
keyword: string;
}
interface PatternRequiredParams {
missingPattern: string;
}
interface PropertyNamesParams {
propertyName: string;
}
interface IfParams {
failingKeyword: string;
}
interface SwitchParams {
caseIndex: number;
}
interface NoParams { }
interface EnumParams {
allowedValues: Array<any>;
}
}
export = ajv;

View File

@@ -1,506 +0,0 @@
'use strict';
var compileSchema = require('./compile')
, resolve = require('./compile/resolve')
, Cache = require('./cache')
, SchemaObject = require('./compile/schema_obj')
, stableStringify = require('fast-json-stable-stringify')
, formats = require('./compile/formats')
, rules = require('./compile/rules')
, $dataMetaSchema = require('./data')
, util = require('./compile/util');
module.exports = Ajv;
Ajv.prototype.validate = validate;
Ajv.prototype.compile = compile;
Ajv.prototype.addSchema = addSchema;
Ajv.prototype.addMetaSchema = addMetaSchema;
Ajv.prototype.validateSchema = validateSchema;
Ajv.prototype.getSchema = getSchema;
Ajv.prototype.removeSchema = removeSchema;
Ajv.prototype.addFormat = addFormat;
Ajv.prototype.errorsText = errorsText;
Ajv.prototype._addSchema = _addSchema;
Ajv.prototype._compile = _compile;
Ajv.prototype.compileAsync = require('./compile/async');
var customKeyword = require('./keyword');
Ajv.prototype.addKeyword = customKeyword.add;
Ajv.prototype.getKeyword = customKeyword.get;
Ajv.prototype.removeKeyword = customKeyword.remove;
Ajv.prototype.validateKeyword = customKeyword.validate;
var errorClasses = require('./compile/error_classes');
Ajv.ValidationError = errorClasses.Validation;
Ajv.MissingRefError = errorClasses.MissingRef;
Ajv.$dataMetaSchema = $dataMetaSchema;
var META_SCHEMA_ID = 'http://json-schema.org/draft-07/schema';
var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes', 'strictDefaults' ];
var META_SUPPORT_DATA = ['/properties'];
/**
* Creates validator instance.
* Usage: `Ajv(opts)`
* @param {Object} opts optional options
* @return {Object} ajv instance
*/
function Ajv(opts) {
if (!(this instanceof Ajv)) return new Ajv(opts);
opts = this._opts = util.copy(opts) || {};
setLogger(this);
this._schemas = {};
this._refs = {};
this._fragments = {};
this._formats = formats(opts.format);
this._cache = opts.cache || new Cache;
this._loadingSchemas = {};
this._compilations = [];
this.RULES = rules();
this._getId = chooseGetId(opts);
opts.loopRequired = opts.loopRequired || Infinity;
if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true;
if (opts.serialize === undefined) opts.serialize = stableStringify;
this._metaOpts = getMetaSchemaOptions(this);
if (opts.formats) addInitialFormats(this);
if (opts.keywords) addInitialKeywords(this);
addDefaultMetaSchema(this);
if (typeof opts.meta == 'object') this.addMetaSchema(opts.meta);
if (opts.nullable) this.addKeyword('nullable', {metaSchema: {type: 'boolean'}});
addInitialSchemas(this);
}
/**
* Validate data using schema
* Schema will be compiled and cached (using serialized JSON as key. [fast-json-stable-stringify](https://github.com/epoberezkin/fast-json-stable-stringify) is used to serialize.
* @this Ajv
* @param {String|Object} schemaKeyRef key, ref or schema object
* @param {Any} data to be validated
* @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`).
*/
function validate(schemaKeyRef, data) {
var v;
if (typeof schemaKeyRef == 'string') {
v = this.getSchema(schemaKeyRef);
if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"');
} else {
var schemaObj = this._addSchema(schemaKeyRef);
v = schemaObj.validate || this._compile(schemaObj);
}
var valid = v(data);
if (v.$async !== true) this.errors = v.errors;
return valid;
}
/**
* Create validating function for passed schema.
* @this Ajv
* @param {Object} schema schema object
* @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords.
* @return {Function} validating function
*/
function compile(schema, _meta) {
var schemaObj = this._addSchema(schema, undefined, _meta);
return schemaObj.validate || this._compile(schemaObj);
}
/**
* Adds schema to the instance.
* @this Ajv
* @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored.
* @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`.
* @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead.
* @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
* @return {Ajv} this for method chaining
*/
function addSchema(schema, key, _skipValidation, _meta) {
if (Array.isArray(schema)){
for (var i=0; i<schema.length; i++) this.addSchema(schema[i], undefined, _skipValidation, _meta);
return this;
}
var id = this._getId(schema);
if (id !== undefined && typeof id != 'string')
throw new Error('schema id must be string');
key = resolve.normalizeId(key || id);
checkUnique(this, key);
this._schemas[key] = this._addSchema(schema, _skipValidation, _meta, true);
return this;
}
/**
* Add schema that will be used to validate other schemas
* options in META_IGNORE_OPTIONS are alway set to false
* @this Ajv
* @param {Object} schema schema object
* @param {String} key optional schema key
* @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema
* @return {Ajv} this for method chaining
*/
function addMetaSchema(schema, key, skipValidation) {
this.addSchema(schema, key, skipValidation, true);
return this;
}
/**
* Validate schema
* @this Ajv
* @param {Object} schema schema to validate
* @param {Boolean} throwOrLogError pass true to throw (or log) an error if invalid
* @return {Boolean} true if schema is valid
*/
function validateSchema(schema, throwOrLogError) {
var $schema = schema.$schema;
if ($schema !== undefined && typeof $schema != 'string')
throw new Error('$schema must be a string');
$schema = $schema || this._opts.defaultMeta || defaultMeta(this);
if (!$schema) {
this.logger.warn('meta-schema not available');
this.errors = null;
return true;
}
var valid = this.validate($schema, schema);
if (!valid && throwOrLogError) {
var message = 'schema is invalid: ' + this.errorsText();
if (this._opts.validateSchema == 'log') this.logger.error(message);
else throw new Error(message);
}
return valid;
}
function defaultMeta(self) {
var meta = self._opts.meta;
self._opts.defaultMeta = typeof meta == 'object'
? self._getId(meta) || meta
: self.getSchema(META_SCHEMA_ID)
? META_SCHEMA_ID
: undefined;
return self._opts.defaultMeta;
}
/**
* Get compiled schema from the instance by `key` or `ref`.
* @this Ajv
* @param {String} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id).
* @return {Function} schema validating function (with property `schema`).
*/
function getSchema(keyRef) {
var schemaObj = _getSchemaObj(this, keyRef);
switch (typeof schemaObj) {
case 'object': return schemaObj.validate || this._compile(schemaObj);
case 'string': return this.getSchema(schemaObj);
case 'undefined': return _getSchemaFragment(this, keyRef);
}
}
function _getSchemaFragment(self, ref) {
var res = resolve.schema.call(self, { schema: {} }, ref);
if (res) {
var schema = res.schema
, root = res.root
, baseId = res.baseId;
var v = compileSchema.call(self, schema, root, undefined, baseId);
self._fragments[ref] = new SchemaObject({
ref: ref,
fragment: true,
schema: schema,
root: root,
baseId: baseId,
validate: v
});
return v;
}
}
function _getSchemaObj(self, keyRef) {
keyRef = resolve.normalizeId(keyRef);
return self._schemas[keyRef] || self._refs[keyRef] || self._fragments[keyRef];
}
/**
* Remove cached schema(s).
* If no parameter is passed all schemas but meta-schemas are removed.
* If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
* Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
* @this Ajv
* @param {String|Object|RegExp} schemaKeyRef key, ref, pattern to match key/ref or schema object
* @return {Ajv} this for method chaining
*/
function removeSchema(schemaKeyRef) {
if (schemaKeyRef instanceof RegExp) {
_removeAllSchemas(this, this._schemas, schemaKeyRef);
_removeAllSchemas(this, this._refs, schemaKeyRef);
return this;
}
switch (typeof schemaKeyRef) {
case 'undefined':
_removeAllSchemas(this, this._schemas);
_removeAllSchemas(this, this._refs);
this._cache.clear();
return this;
case 'string':
var schemaObj = _getSchemaObj(this, schemaKeyRef);
if (schemaObj) this._cache.del(schemaObj.cacheKey);
delete this._schemas[schemaKeyRef];
delete this._refs[schemaKeyRef];
return this;
case 'object':
var serialize = this._opts.serialize;
var cacheKey = serialize ? serialize(schemaKeyRef) : schemaKeyRef;
this._cache.del(cacheKey);
var id = this._getId(schemaKeyRef);
if (id) {
id = resolve.normalizeId(id);
delete this._schemas[id];
delete this._refs[id];
}
}
return this;
}
function _removeAllSchemas(self, schemas, regex) {
for (var keyRef in schemas) {
var schemaObj = schemas[keyRef];
if (!schemaObj.meta && (!regex || regex.test(keyRef))) {
self._cache.del(schemaObj.cacheKey);
delete schemas[keyRef];
}
}
}
/* @this Ajv */
function _addSchema(schema, skipValidation, meta, shouldAddSchema) {
if (typeof schema != 'object' && typeof schema != 'boolean')
throw new Error('schema should be object or boolean');
var serialize = this._opts.serialize;
var cacheKey = serialize ? serialize(schema) : schema;
var cached = this._cache.get(cacheKey);
if (cached) return cached;
shouldAddSchema = shouldAddSchema || this._opts.addUsedSchema !== false;
var id = resolve.normalizeId(this._getId(schema));
if (id && shouldAddSchema) checkUnique(this, id);
var willValidate = this._opts.validateSchema !== false && !skipValidation;
var recursiveMeta;
if (willValidate && !(recursiveMeta = id && id == resolve.normalizeId(schema.$schema)))
this.validateSchema(schema, true);
var localRefs = resolve.ids.call(this, schema);
var schemaObj = new SchemaObject({
id: id,
schema: schema,
localRefs: localRefs,
cacheKey: cacheKey,
meta: meta
});
if (id[0] != '#' && shouldAddSchema) this._refs[id] = schemaObj;
this._cache.put(cacheKey, schemaObj);
if (willValidate && recursiveMeta) this.validateSchema(schema, true);
return schemaObj;
}
/* @this Ajv */
function _compile(schemaObj, root) {
if (schemaObj.compiling) {
schemaObj.validate = callValidate;
callValidate.schema = schemaObj.schema;
callValidate.errors = null;
callValidate.root = root ? root : callValidate;
if (schemaObj.schema.$async === true)
callValidate.$async = true;
return callValidate;
}
schemaObj.compiling = true;
var currentOpts;
if (schemaObj.meta) {
currentOpts = this._opts;
this._opts = this._metaOpts;
}
var v;
try { v = compileSchema.call(this, schemaObj.schema, root, schemaObj.localRefs); }
catch(e) {
delete schemaObj.validate;
throw e;
}
finally {
schemaObj.compiling = false;
if (schemaObj.meta) this._opts = currentOpts;
}
schemaObj.validate = v;
schemaObj.refs = v.refs;
schemaObj.refVal = v.refVal;
schemaObj.root = v.root;
return v;
/* @this {*} - custom context, see passContext option */
function callValidate() {
/* jshint validthis: true */
var _validate = schemaObj.validate;
var result = _validate.apply(this, arguments);
callValidate.errors = _validate.errors;
return result;
}
}
function chooseGetId(opts) {
switch (opts.schemaId) {
case 'auto': return _get$IdOrId;
case 'id': return _getId;
default: return _get$Id;
}
}
/* @this Ajv */
function _getId(schema) {
if (schema.$id) this.logger.warn('schema $id ignored', schema.$id);
return schema.id;
}
/* @this Ajv */
function _get$Id(schema) {
if (schema.id) this.logger.warn('schema id ignored', schema.id);
return schema.$id;
}
function _get$IdOrId(schema) {
if (schema.$id && schema.id && schema.$id != schema.id)
throw new Error('schema $id is different from id');
return schema.$id || schema.id;
}
/**
* Convert array of error message objects to string
* @this Ajv
* @param {Array<Object>} errors optional array of validation errors, if not passed errors from the instance are used.
* @param {Object} options optional options with properties `separator` and `dataVar`.
* @return {String} human readable string with all errors descriptions
*/
function errorsText(errors, options) {
errors = errors || this.errors;
if (!errors) return 'No errors';
options = options || {};
var separator = options.separator === undefined ? ', ' : options.separator;
var dataVar = options.dataVar === undefined ? 'data' : options.dataVar;
var text = '';
for (var i=0; i<errors.length; i++) {
var e = errors[i];
if (e) text += dataVar + e.dataPath + ' ' + e.message + separator;
}
return text.slice(0, -separator.length);
}
/**
* Add custom format
* @this Ajv
* @param {String} name format name
* @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid)
* @return {Ajv} this for method chaining
*/
function addFormat(name, format) {
if (typeof format == 'string') format = new RegExp(format);
this._formats[name] = format;
return this;
}
function addDefaultMetaSchema(self) {
var $dataSchema;
if (self._opts.$data) {
$dataSchema = require('./refs/data.json');
self.addMetaSchema($dataSchema, $dataSchema.$id, true);
}
if (self._opts.meta === false) return;
var metaSchema = require('./refs/json-schema-draft-07.json');
if (self._opts.$data) metaSchema = $dataMetaSchema(metaSchema, META_SUPPORT_DATA);
self.addMetaSchema(metaSchema, META_SCHEMA_ID, true);
self._refs['http://json-schema.org/schema'] = META_SCHEMA_ID;
}
function addInitialSchemas(self) {
var optsSchemas = self._opts.schemas;
if (!optsSchemas) return;
if (Array.isArray(optsSchemas)) self.addSchema(optsSchemas);
else for (var key in optsSchemas) self.addSchema(optsSchemas[key], key);
}
function addInitialFormats(self) {
for (var name in self._opts.formats) {
var format = self._opts.formats[name];
self.addFormat(name, format);
}
}
function addInitialKeywords(self) {
for (var name in self._opts.keywords) {
var keyword = self._opts.keywords[name];
self.addKeyword(name, keyword);
}
}
function checkUnique(self, id) {
if (self._schemas[id] || self._refs[id])
throw new Error('schema with key or id "' + id + '" already exists');
}
function getMetaSchemaOptions(self) {
var metaOpts = util.copy(self._opts);
for (var i=0; i<META_IGNORE_OPTIONS.length; i++)
delete metaOpts[META_IGNORE_OPTIONS[i]];
return metaOpts;
}
function setLogger(self) {
var logger = self._opts.logger;
if (logger === false) {
self.logger = {log: noop, warn: noop, error: noop};
} else {
if (logger === undefined) logger = console;
if (!(typeof logger == 'object' && logger.log && logger.warn && logger.error))
throw new Error('logger must implement log, warn and error methods');
self.logger = logger;
}
}
function noop() {}

View File

@@ -1,26 +0,0 @@
'use strict';
var Cache = module.exports = function Cache() {
this._cache = {};
};
Cache.prototype.put = function Cache_put(key, value) {
this._cache[key] = value;
};
Cache.prototype.get = function Cache_get(key) {
return this._cache[key];
};
Cache.prototype.del = function Cache_del(key) {
delete this._cache[key];
};
Cache.prototype.clear = function Cache_clear() {
this._cache = {};
};

View File

@@ -1,90 +0,0 @@
'use strict';
var MissingRefError = require('./error_classes').MissingRef;
module.exports = compileAsync;
/**
* Creates validating function for passed schema with asynchronous loading of missing schemas.
* `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema.
* @this Ajv
* @param {Object} schema schema object
* @param {Boolean} meta optional true to compile meta-schema; this parameter can be skipped
* @param {Function} callback an optional node-style callback, it is called with 2 parameters: error (or null) and validating function.
* @return {Promise} promise that resolves with a validating function.
*/
function compileAsync(schema, meta, callback) {
/* eslint no-shadow: 0 */
/* global Promise */
/* jshint validthis: true */
var self = this;
if (typeof this._opts.loadSchema != 'function')
throw new Error('options.loadSchema should be a function');
if (typeof meta == 'function') {
callback = meta;
meta = undefined;
}
var p = loadMetaSchemaOf(schema).then(function () {
var schemaObj = self._addSchema(schema, undefined, meta);
return schemaObj.validate || _compileAsync(schemaObj);
});
if (callback) {
p.then(
function(v) { callback(null, v); },
callback
);
}
return p;
function loadMetaSchemaOf(sch) {
var $schema = sch.$schema;
return $schema && !self.getSchema($schema)
? compileAsync.call(self, { $ref: $schema }, true)
: Promise.resolve();
}
function _compileAsync(schemaObj) {
try { return self._compile(schemaObj); }
catch(e) {
if (e instanceof MissingRefError) return loadMissingSchema(e);
throw e;
}
function loadMissingSchema(e) {
var ref = e.missingSchema;
if (added(ref)) throw new Error('Schema ' + ref + ' is loaded but ' + e.missingRef + ' cannot be resolved');
var schemaPromise = self._loadingSchemas[ref];
if (!schemaPromise) {
schemaPromise = self._loadingSchemas[ref] = self._opts.loadSchema(ref);
schemaPromise.then(removePromise, removePromise);
}
return schemaPromise.then(function (sch) {
if (!added(ref)) {
return loadMetaSchemaOf(sch).then(function () {
if (!added(ref)) self.addSchema(sch, ref, undefined, meta);
});
}
}).then(function() {
return _compileAsync(schemaObj);
});
function removePromise() {
delete self._loadingSchemas[ref];
}
function added(ref) {
return self._refs[ref] || self._schemas[ref];
}
}
}
}

View File

@@ -1,5 +0,0 @@
'use strict';
// do NOT remove this file - it would break pre-compiled schemas
// https://github.com/ajv-validator/ajv/issues/889
module.exports = require('fast-deep-equal');

View File

@@ -1,34 +0,0 @@
'use strict';
var resolve = require('./resolve');
module.exports = {
Validation: errorSubclass(ValidationError),
MissingRef: errorSubclass(MissingRefError)
};
function ValidationError(errors) {
this.message = 'validation failed';
this.errors = errors;
this.ajv = this.validation = true;
}
MissingRefError.message = function (baseId, ref) {
return 'can\'t resolve reference ' + ref + ' from id ' + baseId;
};
function MissingRefError(baseId, ref, message) {
this.message = message || MissingRefError.message(baseId, ref);
this.missingRef = resolve.url(baseId, ref);
this.missingSchema = resolve.normalizeId(resolve.fullPath(this.missingRef));
}
function errorSubclass(Subclass) {
Subclass.prototype = Object.create(Error.prototype);
Subclass.prototype.constructor = Subclass;
return Subclass;
}

View File

@@ -1,142 +0,0 @@
'use strict';
var util = require('./util');
var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
var DAYS = [0,31,28,31,30,31,30,31,31,30,31,30,31];
var TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i;
var HOSTNAME = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i;
var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
var URIREF = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
// uri-template: https://tools.ietf.org/html/rfc6570
var URITEMPLATE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i;
// For the source: https://gist.github.com/dperini/729294
// For test cases: https://mathiasbynens.be/demo/url-regex
// @todo Delete current URL in favour of the commented out URL rule when this issue is fixed https://github.com/eslint/eslint/issues/7983.
// var URL = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu;
var URL = /^(?:(?:http[s\u017F]?|ftp):\/\/)(?:(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+(?::(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?@)?(?:(?!10(?:\.[0-9]{1,3}){3})(?!127(?:\.[0-9]{1,3}){3})(?!169\.254(?:\.[0-9]{1,3}){2})(?!192\.168(?:\.[0-9]{1,3}){2})(?!172\.(?:1[6-9]|2[0-9]|3[01])(?:\.[0-9]{1,3}){2})(?:[1-9][0-9]?|1[0-9][0-9]|2[01][0-9]|22[0-3])(?:\.(?:1?[0-9]{1,2}|2[0-4][0-9]|25[0-5])){2}(?:\.(?:[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-4]))|(?:(?:(?:[0-9a-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+-)*(?:[0-9a-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)(?:\.(?:(?:[0-9a-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+-)*(?:[0-9a-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)*(?:\.(?:(?:[a-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){2,})))(?::[0-9]{2,5})?(?:\/(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?$/i;
var UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
var JSON_POINTER = /^(?:\/(?:[^~/]|~0|~1)*)*$/;
var JSON_POINTER_URI_FRAGMENT = /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i;
var RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/;
module.exports = formats;
function formats(mode) {
mode = mode == 'full' ? 'full' : 'fast';
return util.copy(formats[mode]);
}
formats.fast = {
// date: http://tools.ietf.org/html/rfc3339#section-5.6
date: /^\d\d\d\d-[0-1]\d-[0-3]\d$/,
// date-time: http://tools.ietf.org/html/rfc3339#section-5.6
time: /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i,
'date-time': /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i,
// uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js
uri: /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/)?[^\s]*$/i,
'uri-reference': /^(?:(?:[a-z][a-z0-9+\-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i,
'uri-template': URITEMPLATE,
url: URL,
// email (sources from jsen validator):
// http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363
// http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'willful violation')
email: /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i,
hostname: HOSTNAME,
// optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
// optimized http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i,
regex: regex,
// uuid: http://tools.ietf.org/html/rfc4122
uuid: UUID,
// JSON-pointer: https://tools.ietf.org/html/rfc6901
// uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A
'json-pointer': JSON_POINTER,
'json-pointer-uri-fragment': JSON_POINTER_URI_FRAGMENT,
// relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00
'relative-json-pointer': RELATIVE_JSON_POINTER
};
formats.full = {
date: date,
time: time,
'date-time': date_time,
uri: uri,
'uri-reference': URIREF,
'uri-template': URITEMPLATE,
url: URL,
email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
hostname: HOSTNAME,
ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i,
regex: regex,
uuid: UUID,
'json-pointer': JSON_POINTER,
'json-pointer-uri-fragment': JSON_POINTER_URI_FRAGMENT,
'relative-json-pointer': RELATIVE_JSON_POINTER
};
function isLeapYear(year) {
// https://tools.ietf.org/html/rfc3339#appendix-C
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}
function date(str) {
// full-date from http://tools.ietf.org/html/rfc3339#section-5.6
var matches = str.match(DATE);
if (!matches) return false;
var year = +matches[1];
var month = +matches[2];
var day = +matches[3];
return month >= 1 && month <= 12 && day >= 1 &&
day <= (month == 2 && isLeapYear(year) ? 29 : DAYS[month]);
}
function time(str, full) {
var matches = str.match(TIME);
if (!matches) return false;
var hour = matches[1];
var minute = matches[2];
var second = matches[3];
var timeZone = matches[5];
return ((hour <= 23 && minute <= 59 && second <= 59) ||
(hour == 23 && minute == 59 && second == 60)) &&
(!full || timeZone);
}
var DATE_TIME_SEPARATOR = /t|\s/i;
function date_time(str) {
// http://tools.ietf.org/html/rfc3339#section-5.6
var dateTime = str.split(DATE_TIME_SEPARATOR);
return dateTime.length == 2 && date(dateTime[0]) && time(dateTime[1], true);
}
var NOT_URI_FRAGMENT = /\/|:/;
function uri(str) {
// http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "."
return NOT_URI_FRAGMENT.test(str) && URI.test(str);
}
var Z_ANCHOR = /[^\\]\\Z/;
function regex(str) {
if (Z_ANCHOR.test(str)) return false;
try {
new RegExp(str);
return true;
} catch(e) {
return false;
}
}

View File

@@ -1,387 +0,0 @@
'use strict';
var resolve = require('./resolve')
, util = require('./util')
, errorClasses = require('./error_classes')
, stableStringify = require('fast-json-stable-stringify');
var validateGenerator = require('../dotjs/validate');
/**
* Functions below are used inside compiled validations function
*/
var ucs2length = util.ucs2length;
var equal = require('fast-deep-equal');
// this error is thrown by async schemas to return validation errors via exception
var ValidationError = errorClasses.Validation;
module.exports = compile;
/**
* Compiles schema to validation function
* @this Ajv
* @param {Object} schema schema object
* @param {Object} root object with information about the root schema for this schema
* @param {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution
* @param {String} baseId base ID for IDs in the schema
* @return {Function} validation function
*/
function compile(schema, root, localRefs, baseId) {
/* jshint validthis: true, evil: true */
/* eslint no-shadow: 0 */
var self = this
, opts = this._opts
, refVal = [ undefined ]
, refs = {}
, patterns = []
, patternsHash = {}
, defaults = []
, defaultsHash = {}
, customRules = [];
root = root || { schema: schema, refVal: refVal, refs: refs };
var c = checkCompiling.call(this, schema, root, baseId);
var compilation = this._compilations[c.index];
if (c.compiling) return (compilation.callValidate = callValidate);
var formats = this._formats;
var RULES = this.RULES;
try {
var v = localCompile(schema, root, localRefs, baseId);
compilation.validate = v;
var cv = compilation.callValidate;
if (cv) {
cv.schema = v.schema;
cv.errors = null;
cv.refs = v.refs;
cv.refVal = v.refVal;
cv.root = v.root;
cv.$async = v.$async;
if (opts.sourceCode) cv.source = v.source;
}
return v;
} finally {
endCompiling.call(this, schema, root, baseId);
}
/* @this {*} - custom context, see passContext option */
function callValidate() {
/* jshint validthis: true */
var validate = compilation.validate;
var result = validate.apply(this, arguments);
callValidate.errors = validate.errors;
return result;
}
function localCompile(_schema, _root, localRefs, baseId) {
var isRoot = !_root || (_root && _root.schema == _schema);
if (_root.schema != root.schema)
return compile.call(self, _schema, _root, localRefs, baseId);
var $async = _schema.$async === true;
var sourceCode = validateGenerator({
isTop: true,
schema: _schema,
isRoot: isRoot,
baseId: baseId,
root: _root,
schemaPath: '',
errSchemaPath: '#',
errorPath: '""',
MissingRefError: errorClasses.MissingRef,
RULES: RULES,
validate: validateGenerator,
util: util,
resolve: resolve,
resolveRef: resolveRef,
usePattern: usePattern,
useDefault: useDefault,
useCustomRule: useCustomRule,
opts: opts,
formats: formats,
logger: self.logger,
self: self
});
sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode)
+ vars(defaults, defaultCode) + vars(customRules, customRuleCode)
+ sourceCode;
if (opts.processCode) sourceCode = opts.processCode(sourceCode, _schema);
// console.log('\n\n\n *** \n', JSON.stringify(sourceCode));
var validate;
try {
var makeValidate = new Function(
'self',
'RULES',
'formats',
'root',
'refVal',
'defaults',
'customRules',
'equal',
'ucs2length',
'ValidationError',
sourceCode
);
validate = makeValidate(
self,
RULES,
formats,
root,
refVal,
defaults,
customRules,
equal,
ucs2length,
ValidationError
);
refVal[0] = validate;
} catch(e) {
self.logger.error('Error compiling schema, function code:', sourceCode);
throw e;
}
validate.schema = _schema;
validate.errors = null;
validate.refs = refs;
validate.refVal = refVal;
validate.root = isRoot ? validate : _root;
if ($async) validate.$async = true;
if (opts.sourceCode === true) {
validate.source = {
code: sourceCode,
patterns: patterns,
defaults: defaults
};
}
return validate;
}
function resolveRef(baseId, ref, isRoot) {
ref = resolve.url(baseId, ref);
var refIndex = refs[ref];
var _refVal, refCode;
if (refIndex !== undefined) {
_refVal = refVal[refIndex];
refCode = 'refVal[' + refIndex + ']';
return resolvedRef(_refVal, refCode);
}
if (!isRoot && root.refs) {
var rootRefId = root.refs[ref];
if (rootRefId !== undefined) {
_refVal = root.refVal[rootRefId];
refCode = addLocalRef(ref, _refVal);
return resolvedRef(_refVal, refCode);
}
}
refCode = addLocalRef(ref);
var v = resolve.call(self, localCompile, root, ref);
if (v === undefined) {
var localSchema = localRefs && localRefs[ref];
if (localSchema) {
v = resolve.inlineRef(localSchema, opts.inlineRefs)
? localSchema
: compile.call(self, localSchema, root, localRefs, baseId);
}
}
if (v === undefined) {
removeLocalRef(ref);
} else {
replaceLocalRef(ref, v);
return resolvedRef(v, refCode);
}
}
function addLocalRef(ref, v) {
var refId = refVal.length;
refVal[refId] = v;
refs[ref] = refId;
return 'refVal' + refId;
}
function removeLocalRef(ref) {
delete refs[ref];
}
function replaceLocalRef(ref, v) {
var refId = refs[ref];
refVal[refId] = v;
}
function resolvedRef(refVal, code) {
return typeof refVal == 'object' || typeof refVal == 'boolean'
? { code: code, schema: refVal, inline: true }
: { code: code, $async: refVal && !!refVal.$async };
}
function usePattern(regexStr) {
var index = patternsHash[regexStr];
if (index === undefined) {
index = patternsHash[regexStr] = patterns.length;
patterns[index] = regexStr;
}
return 'pattern' + index;
}
function useDefault(value) {
switch (typeof value) {
case 'boolean':
case 'number':
return '' + value;
case 'string':
return util.toQuotedString(value);
case 'object':
if (value === null) return 'null';
var valueStr = stableStringify(value);
var index = defaultsHash[valueStr];
if (index === undefined) {
index = defaultsHash[valueStr] = defaults.length;
defaults[index] = value;
}
return 'default' + index;
}
}
function useCustomRule(rule, schema, parentSchema, it) {
if (self._opts.validateSchema !== false) {
var deps = rule.definition.dependencies;
if (deps && !deps.every(function(keyword) {
return Object.prototype.hasOwnProperty.call(parentSchema, keyword);
}))
throw new Error('parent schema must have all required keywords: ' + deps.join(','));
var validateSchema = rule.definition.validateSchema;
if (validateSchema) {
var valid = validateSchema(schema);
if (!valid) {
var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors);
if (self._opts.validateSchema == 'log') self.logger.error(message);
else throw new Error(message);
}
}
}
var compile = rule.definition.compile
, inline = rule.definition.inline
, macro = rule.definition.macro;
var validate;
if (compile) {
validate = compile.call(self, schema, parentSchema, it);
} else if (macro) {
validate = macro.call(self, schema, parentSchema, it);
if (opts.validateSchema !== false) self.validateSchema(validate, true);
} else if (inline) {
validate = inline.call(self, it, rule.keyword, schema, parentSchema);
} else {
validate = rule.definition.validate;
if (!validate) return;
}
if (validate === undefined)
throw new Error('custom keyword "' + rule.keyword + '"failed to compile');
var index = customRules.length;
customRules[index] = validate;
return {
code: 'customRule' + index,
validate: validate
};
}
}
/**
* Checks if the schema is currently compiled
* @this Ajv
* @param {Object} schema schema to compile
* @param {Object} root root object
* @param {String} baseId base schema ID
* @return {Object} object with properties "index" (compilation index) and "compiling" (boolean)
*/
function checkCompiling(schema, root, baseId) {
/* jshint validthis: true */
var index = compIndex.call(this, schema, root, baseId);
if (index >= 0) return { index: index, compiling: true };
index = this._compilations.length;
this._compilations[index] = {
schema: schema,
root: root,
baseId: baseId
};
return { index: index, compiling: false };
}
/**
* Removes the schema from the currently compiled list
* @this Ajv
* @param {Object} schema schema to compile
* @param {Object} root root object
* @param {String} baseId base schema ID
*/
function endCompiling(schema, root, baseId) {
/* jshint validthis: true */
var i = compIndex.call(this, schema, root, baseId);
if (i >= 0) this._compilations.splice(i, 1);
}
/**
* Index of schema compilation in the currently compiled list
* @this Ajv
* @param {Object} schema schema to compile
* @param {Object} root root object
* @param {String} baseId base schema ID
* @return {Integer} compilation index
*/
function compIndex(schema, root, baseId) {
/* jshint validthis: true */
for (var i=0; i<this._compilations.length; i++) {
var c = this._compilations[i];
if (c.schema == schema && c.root == root && c.baseId == baseId) return i;
}
return -1;
}
function patternCode(i, patterns) {
return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');';
}
function defaultCode(i) {
return 'var default' + i + ' = defaults[' + i + '];';
}
function refValCode(i, refVal) {
return refVal[i] === undefined ? '' : 'var refVal' + i + ' = refVal[' + i + '];';
}
function customRuleCode(i) {
return 'var customRule' + i + ' = customRules[' + i + '];';
}
function vars(arr, statement) {
if (!arr.length) return '';
var code = '';
for (var i=0; i<arr.length; i++)
code += statement(i, arr);
return code;
}

View File

@@ -1,270 +0,0 @@
'use strict';
var URI = require('uri-js')
, equal = require('fast-deep-equal')
, util = require('./util')
, SchemaObject = require('./schema_obj')
, traverse = require('json-schema-traverse');
module.exports = resolve;
resolve.normalizeId = normalizeId;
resolve.fullPath = getFullPath;
resolve.url = resolveUrl;
resolve.ids = resolveIds;
resolve.inlineRef = inlineRef;
resolve.schema = resolveSchema;
/**
* [resolve and compile the references ($ref)]
* @this Ajv
* @param {Function} compile reference to schema compilation funciton (localCompile)
* @param {Object} root object with information about the root schema for the current schema
* @param {String} ref reference to resolve
* @return {Object|Function} schema object (if the schema can be inlined) or validation function
*/
function resolve(compile, root, ref) {
/* jshint validthis: true */
var refVal = this._refs[ref];
if (typeof refVal == 'string') {
if (this._refs[refVal]) refVal = this._refs[refVal];
else return resolve.call(this, compile, root, refVal);
}
refVal = refVal || this._schemas[ref];
if (refVal instanceof SchemaObject) {
return inlineRef(refVal.schema, this._opts.inlineRefs)
? refVal.schema
: refVal.validate || this._compile(refVal);
}
var res = resolveSchema.call(this, root, ref);
var schema, v, baseId;
if (res) {
schema = res.schema;
root = res.root;
baseId = res.baseId;
}
if (schema instanceof SchemaObject) {
v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId);
} else if (schema !== undefined) {
v = inlineRef(schema, this._opts.inlineRefs)
? schema
: compile.call(this, schema, root, undefined, baseId);
}
return v;
}
/**
* Resolve schema, its root and baseId
* @this Ajv
* @param {Object} root root object with properties schema, refVal, refs
* @param {String} ref reference to resolve
* @return {Object} object with properties schema, root, baseId
*/
function resolveSchema(root, ref) {
/* jshint validthis: true */
var p = URI.parse(ref)
, refPath = _getFullPath(p)
, baseId = getFullPath(this._getId(root.schema));
if (Object.keys(root.schema).length === 0 || refPath !== baseId) {
var id = normalizeId(refPath);
var refVal = this._refs[id];
if (typeof refVal == 'string') {
return resolveRecursive.call(this, root, refVal, p);
} else if (refVal instanceof SchemaObject) {
if (!refVal.validate) this._compile(refVal);
root = refVal;
} else {
refVal = this._schemas[id];
if (refVal instanceof SchemaObject) {
if (!refVal.validate) this._compile(refVal);
if (id == normalizeId(ref))
return { schema: refVal, root: root, baseId: baseId };
root = refVal;
} else {
return;
}
}
if (!root.schema) return;
baseId = getFullPath(this._getId(root.schema));
}
return getJsonPointer.call(this, p, baseId, root.schema, root);
}
/* @this Ajv */
function resolveRecursive(root, ref, parsedRef) {
/* jshint validthis: true */
var res = resolveSchema.call(this, root, ref);
if (res) {
var schema = res.schema;
var baseId = res.baseId;
root = res.root;
var id = this._getId(schema);
if (id) baseId = resolveUrl(baseId, id);
return getJsonPointer.call(this, parsedRef, baseId, schema, root);
}
}
var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']);
/* @this Ajv */
function getJsonPointer(parsedRef, baseId, schema, root) {
/* jshint validthis: true */
parsedRef.fragment = parsedRef.fragment || '';
if (parsedRef.fragment.slice(0,1) != '/') return;
var parts = parsedRef.fragment.split('/');
for (var i = 1; i < parts.length; i++) {
var part = parts[i];
if (part) {
part = util.unescapeFragment(part);
schema = schema[part];
if (schema === undefined) break;
var id;
if (!PREVENT_SCOPE_CHANGE[part]) {
id = this._getId(schema);
if (id) baseId = resolveUrl(baseId, id);
if (schema.$ref) {
var $ref = resolveUrl(baseId, schema.$ref);
var res = resolveSchema.call(this, root, $ref);
if (res) {
schema = res.schema;
root = res.root;
baseId = res.baseId;
}
}
}
}
}
if (schema !== undefined && schema !== root.schema)
return { schema: schema, root: root, baseId: baseId };
}
var SIMPLE_INLINED = util.toHash([
'type', 'format', 'pattern',
'maxLength', 'minLength',
'maxProperties', 'minProperties',
'maxItems', 'minItems',
'maximum', 'minimum',
'uniqueItems', 'multipleOf',
'required', 'enum'
]);
function inlineRef(schema, limit) {
if (limit === false) return false;
if (limit === undefined || limit === true) return checkNoRef(schema);
else if (limit) return countKeys(schema) <= limit;
}
function checkNoRef(schema) {
var item;
if (Array.isArray(schema)) {
for (var i=0; i<schema.length; i++) {
item = schema[i];
if (typeof item == 'object' && !checkNoRef(item)) return false;
}
} else {
for (var key in schema) {
if (key == '$ref') return false;
item = schema[key];
if (typeof item == 'object' && !checkNoRef(item)) return false;
}
}
return true;
}
function countKeys(schema) {
var count = 0, item;
if (Array.isArray(schema)) {
for (var i=0; i<schema.length; i++) {
item = schema[i];
if (typeof item == 'object') count += countKeys(item);
if (count == Infinity) return Infinity;
}
} else {
for (var key in schema) {
if (key == '$ref') return Infinity;
if (SIMPLE_INLINED[key]) {
count++;
} else {
item = schema[key];
if (typeof item == 'object') count += countKeys(item) + 1;
if (count == Infinity) return Infinity;
}
}
}
return count;
}
function getFullPath(id, normalize) {
if (normalize !== false) id = normalizeId(id);
var p = URI.parse(id);
return _getFullPath(p);
}
function _getFullPath(p) {
return URI.serialize(p).split('#')[0] + '#';
}
var TRAILING_SLASH_HASH = /#\/?$/;
function normalizeId(id) {
return id ? id.replace(TRAILING_SLASH_HASH, '') : '';
}
function resolveUrl(baseId, id) {
id = normalizeId(id);
return URI.resolve(baseId, id);
}
/* @this Ajv */
function resolveIds(schema) {
var schemaId = normalizeId(this._getId(schema));
var baseIds = {'': schemaId};
var fullPaths = {'': getFullPath(schemaId, false)};
var localRefs = {};
var self = this;
traverse(schema, {allKeys: true}, function(sch, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {
if (jsonPtr === '') return;
var id = self._getId(sch);
var baseId = baseIds[parentJsonPtr];
var fullPath = fullPaths[parentJsonPtr] + '/' + parentKeyword;
if (keyIndex !== undefined)
fullPath += '/' + (typeof keyIndex == 'number' ? keyIndex : util.escapeFragment(keyIndex));
if (typeof id == 'string') {
id = baseId = normalizeId(baseId ? URI.resolve(baseId, id) : id);
var refVal = self._refs[id];
if (typeof refVal == 'string') refVal = self._refs[refVal];
if (refVal && refVal.schema) {
if (!equal(sch, refVal.schema))
throw new Error('id "' + id + '" resolves to more than one schema');
} else if (id != normalizeId(fullPath)) {
if (id[0] == '#') {
if (localRefs[id] && !equal(sch, localRefs[id]))
throw new Error('id "' + id + '" resolves to more than one schema');
localRefs[id] = sch;
} else {
self._refs[id] = fullPath;
}
}
}
baseIds[jsonPtr] = baseId;
fullPaths[jsonPtr] = fullPath;
});
return localRefs;
}

View File

@@ -1,66 +0,0 @@
'use strict';
var ruleModules = require('../dotjs')
, toHash = require('./util').toHash;
module.exports = function rules() {
var RULES = [
{ type: 'number',
rules: [ { 'maximum': ['exclusiveMaximum'] },
{ 'minimum': ['exclusiveMinimum'] }, 'multipleOf', 'format'] },
{ type: 'string',
rules: [ 'maxLength', 'minLength', 'pattern', 'format' ] },
{ type: 'array',
rules: [ 'maxItems', 'minItems', 'items', 'contains', 'uniqueItems' ] },
{ type: 'object',
rules: [ 'maxProperties', 'minProperties', 'required', 'dependencies', 'propertyNames',
{ 'properties': ['additionalProperties', 'patternProperties'] } ] },
{ rules: [ '$ref', 'const', 'enum', 'not', 'anyOf', 'oneOf', 'allOf', 'if' ] }
];
var ALL = [ 'type', '$comment' ];
var KEYWORDS = [
'$schema', '$id', 'id', '$data', '$async', 'title',
'description', 'default', 'definitions',
'examples', 'readOnly', 'writeOnly',
'contentMediaType', 'contentEncoding',
'additionalItems', 'then', 'else'
];
var TYPES = [ 'number', 'integer', 'string', 'array', 'object', 'boolean', 'null' ];
RULES.all = toHash(ALL);
RULES.types = toHash(TYPES);
RULES.forEach(function (group) {
group.rules = group.rules.map(function (keyword) {
var implKeywords;
if (typeof keyword == 'object') {
var key = Object.keys(keyword)[0];
implKeywords = keyword[key];
keyword = key;
implKeywords.forEach(function (k) {
ALL.push(k);
RULES.all[k] = true;
});
}
ALL.push(keyword);
var rule = RULES.all[keyword] = {
keyword: keyword,
code: ruleModules[keyword],
implements: implKeywords
};
return rule;
});
RULES.all.$comment = {
keyword: '$comment',
code: ruleModules.$comment
};
if (group.type) RULES.types[group.type] = group;
});
RULES.keywords = toHash(ALL.concat(KEYWORDS));
RULES.custom = {};
return RULES;
};

View File

@@ -1,9 +0,0 @@
'use strict';
var util = require('./util');
module.exports = SchemaObject;
function SchemaObject(obj) {
util.copy(obj, this);
}

View File

@@ -1,20 +0,0 @@
'use strict';
// https://mathiasbynens.be/notes/javascript-encoding
// https://github.com/bestiejs/punycode.js - punycode.ucs2.decode
module.exports = function ucs2length(str) {
var length = 0
, len = str.length
, pos = 0
, value;
while (pos < len) {
length++;
value = str.charCodeAt(pos++);
if (value >= 0xD800 && value <= 0xDBFF && pos < len) {
// high surrogate, and there is a next character
value = str.charCodeAt(pos);
if ((value & 0xFC00) == 0xDC00) pos++; // low surrogate
}
}
return length;
};

View File

@@ -1,239 +0,0 @@
'use strict';
module.exports = {
copy: copy,
checkDataType: checkDataType,
checkDataTypes: checkDataTypes,
coerceToTypes: coerceToTypes,
toHash: toHash,
getProperty: getProperty,
escapeQuotes: escapeQuotes,
equal: require('fast-deep-equal'),
ucs2length: require('./ucs2length'),
varOccurences: varOccurences,
varReplace: varReplace,
schemaHasRules: schemaHasRules,
schemaHasRulesExcept: schemaHasRulesExcept,
schemaUnknownRules: schemaUnknownRules,
toQuotedString: toQuotedString,
getPathExpr: getPathExpr,
getPath: getPath,
getData: getData,
unescapeFragment: unescapeFragment,
unescapeJsonPointer: unescapeJsonPointer,
escapeFragment: escapeFragment,
escapeJsonPointer: escapeJsonPointer
};
function copy(o, to) {
to = to || {};
for (var key in o) to[key] = o[key];
return to;
}
function checkDataType(dataType, data, strictNumbers, negate) {
var EQUAL = negate ? ' !== ' : ' === '
, AND = negate ? ' || ' : ' && '
, OK = negate ? '!' : ''
, NOT = negate ? '' : '!';
switch (dataType) {
case 'null': return data + EQUAL + 'null';
case 'array': return OK + 'Array.isArray(' + data + ')';
case 'object': return '(' + OK + data + AND +
'typeof ' + data + EQUAL + '"object"' + AND +
NOT + 'Array.isArray(' + data + '))';
case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND +
NOT + '(' + data + ' % 1)' +
AND + data + EQUAL + data +
(strictNumbers ? (AND + OK + 'isFinite(' + data + ')') : '') + ')';
case 'number': return '(typeof ' + data + EQUAL + '"' + dataType + '"' +
(strictNumbers ? (AND + OK + 'isFinite(' + data + ')') : '') + ')';
default: return 'typeof ' + data + EQUAL + '"' + dataType + '"';
}
}
function checkDataTypes(dataTypes, data, strictNumbers) {
switch (dataTypes.length) {
case 1: return checkDataType(dataTypes[0], data, strictNumbers, true);
default:
var code = '';
var types = toHash(dataTypes);
if (types.array && types.object) {
code = types.null ? '(': '(!' + data + ' || ';
code += 'typeof ' + data + ' !== "object")';
delete types.null;
delete types.array;
delete types.object;
}
if (types.number) delete types.integer;
for (var t in types)
code += (code ? ' && ' : '' ) + checkDataType(t, data, strictNumbers, true);
return code;
}
}
var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]);
function coerceToTypes(optionCoerceTypes, dataTypes) {
if (Array.isArray(dataTypes)) {
var types = [];
for (var i=0; i<dataTypes.length; i++) {
var t = dataTypes[i];
if (COERCE_TO_TYPES[t]) types[types.length] = t;
else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t;
}
if (types.length) return types;
} else if (COERCE_TO_TYPES[dataTypes]) {
return [dataTypes];
} else if (optionCoerceTypes === 'array' && dataTypes === 'array') {
return ['array'];
}
}
function toHash(arr) {
var hash = {};
for (var i=0; i<arr.length; i++) hash[arr[i]] = true;
return hash;
}
var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
var SINGLE_QUOTE = /'|\\/g;
function getProperty(key) {
return typeof key == 'number'
? '[' + key + ']'
: IDENTIFIER.test(key)
? '.' + key
: "['" + escapeQuotes(key) + "']";
}
function escapeQuotes(str) {
return str.replace(SINGLE_QUOTE, '\\$&')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\f/g, '\\f')
.replace(/\t/g, '\\t');
}
function varOccurences(str, dataVar) {
dataVar += '[^0-9]';
var matches = str.match(new RegExp(dataVar, 'g'));
return matches ? matches.length : 0;
}
function varReplace(str, dataVar, expr) {
dataVar += '([^0-9])';
expr = expr.replace(/\$/g, '$$$$');
return str.replace(new RegExp(dataVar, 'g'), expr + '$1');
}
function schemaHasRules(schema, rules) {
if (typeof schema == 'boolean') return !schema;
for (var key in schema) if (rules[key]) return true;
}
function schemaHasRulesExcept(schema, rules, exceptKeyword) {
if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not';
for (var key in schema) if (key != exceptKeyword && rules[key]) return true;
}
function schemaUnknownRules(schema, rules) {
if (typeof schema == 'boolean') return;
for (var key in schema) if (!rules[key]) return key;
}
function toQuotedString(str) {
return '\'' + escapeQuotes(str) + '\'';
}
function getPathExpr(currentPath, expr, jsonPointers, isNumber) {
var path = jsonPointers // false by default
? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')')
: (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\'');
return joinPaths(currentPath, path);
}
function getPath(currentPath, prop, jsonPointers) {
var path = jsonPointers // false by default
? toQuotedString('/' + escapeJsonPointer(prop))
: toQuotedString(getProperty(prop));
return joinPaths(currentPath, path);
}
var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
function getData($data, lvl, paths) {
var up, jsonPointer, data, matches;
if ($data === '') return 'rootData';
if ($data[0] == '/') {
if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data);
jsonPointer = $data;
data = 'rootData';
} else {
matches = $data.match(RELATIVE_JSON_POINTER);
if (!matches) throw new Error('Invalid JSON-pointer: ' + $data);
up = +matches[1];
jsonPointer = matches[2];
if (jsonPointer == '#') {
if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl);
return paths[lvl - up];
}
if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl);
data = 'data' + ((lvl - up) || '');
if (!jsonPointer) return data;
}
var expr = data;
var segments = jsonPointer.split('/');
for (var i=0; i<segments.length; i++) {
var segment = segments[i];
if (segment) {
data += getProperty(unescapeJsonPointer(segment));
expr += ' && ' + data;
}
}
return expr;
}
function joinPaths (a, b) {
if (a == '""') return b;
return (a + ' + ' + b).replace(/([^\\])' \+ '/g, '$1');
}
function unescapeFragment(str) {
return unescapeJsonPointer(decodeURIComponent(str));
}
function escapeFragment(str) {
return encodeURIComponent(escapeJsonPointer(str));
}
function escapeJsonPointer(str) {
return str.replace(/~/g, '~0').replace(/\//g, '~1');
}
function unescapeJsonPointer(str) {
return str.replace(/~1/g, '/').replace(/~0/g, '~');
}

View File

@@ -1,49 +0,0 @@
'use strict';
var KEYWORDS = [
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'additionalProperties',
'enum',
'format',
'const'
];
module.exports = function (metaSchema, keywordsJsonPointers) {
for (var i=0; i<keywordsJsonPointers.length; i++) {
metaSchema = JSON.parse(JSON.stringify(metaSchema));
var segments = keywordsJsonPointers[i].split('/');
var keywords = metaSchema;
var j;
for (j=1; j<segments.length; j++)
keywords = keywords[segments[j]];
for (j=0; j<KEYWORDS.length; j++) {
var key = KEYWORDS[j];
var schema = keywords[key];
if (schema) {
keywords[key] = {
anyOf: [
schema,
{ $ref: 'https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#' }
]
};
}
}
}
return metaSchema;
};

View File

@@ -1,37 +0,0 @@
'use strict';
var metaSchema = require('./refs/json-schema-draft-07.json');
module.exports = {
$id: 'https://github.com/ajv-validator/ajv/blob/master/lib/definition_schema.js',
definitions: {
simpleTypes: metaSchema.definitions.simpleTypes
},
type: 'object',
dependencies: {
schema: ['validate'],
$data: ['validate'],
statements: ['inline'],
valid: {not: {required: ['macro']}}
},
properties: {
type: metaSchema.properties.type,
schema: {type: 'boolean'},
statements: {type: 'boolean'},
dependencies: {
type: 'array',
items: {type: 'string'}
},
metaSchema: {type: 'object'},
modifying: {type: 'boolean'},
valid: {type: 'boolean'},
$data: {type: 'boolean'},
async: {type: 'boolean'},
errors: {
anyOf: [
{type: 'boolean'},
{const: 'full'}
]
}
}
};

View File

@@ -1,113 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{## def.setExclusiveLimit:
$exclusive = true;
$errorKeyword = $exclusiveKeyword;
$errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword;
#}}
{{
var $isMax = $keyword == 'maximum'
, $exclusiveKeyword = $isMax ? 'exclusiveMaximum' : 'exclusiveMinimum'
, $schemaExcl = it.schema[$exclusiveKeyword]
, $isDataExcl = it.opts.$data && $schemaExcl && $schemaExcl.$data
, $op = $isMax ? '<' : '>'
, $notOp = $isMax ? '>' : '<'
, $errorKeyword = undefined;
if (!($isData || typeof $schema == 'number' || $schema === undefined)) {
throw new Error($keyword + ' must be number');
}
if (!($isDataExcl || $schemaExcl === undefined
|| typeof $schemaExcl == 'number'
|| typeof $schemaExcl == 'boolean')) {
throw new Error($exclusiveKeyword + ' must be number or boolean');
}
}}
{{? $isDataExcl }}
{{
var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr)
, $exclusive = 'exclusive' + $lvl
, $exclType = 'exclType' + $lvl
, $exclIsNumber = 'exclIsNumber' + $lvl
, $opExpr = 'op' + $lvl
, $opStr = '\' + ' + $opExpr + ' + \'';
}}
var schemaExcl{{=$lvl}} = {{=$schemaValueExcl}};
{{ $schemaValueExcl = 'schemaExcl' + $lvl; }}
var {{=$exclusive}};
var {{=$exclType}} = typeof {{=$schemaValueExcl}};
if ({{=$exclType}} != 'boolean' && {{=$exclType}} != 'undefined' && {{=$exclType}} != 'number') {
{{ var $errorKeyword = $exclusiveKeyword; }}
{{# def.error:'_exclusiveLimit' }}
} else if ({{# def.$dataNotType:'number' }}
{{=$exclType}} == 'number'
? (
({{=$exclusive}} = {{=$schemaValue}} === undefined || {{=$schemaValueExcl}} {{=$op}}= {{=$schemaValue}})
? {{=$data}} {{=$notOp}}= {{=$schemaValueExcl}}
: {{=$data}} {{=$notOp}} {{=$schemaValue}}
)
: (
({{=$exclusive}} = {{=$schemaValueExcl}} === true)
? {{=$data}} {{=$notOp}}= {{=$schemaValue}}
: {{=$data}} {{=$notOp}} {{=$schemaValue}}
)
|| {{=$data}} !== {{=$data}}) {
var op{{=$lvl}} = {{=$exclusive}} ? '{{=$op}}' : '{{=$op}}=';
{{
if ($schema === undefined) {
$errorKeyword = $exclusiveKeyword;
$errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword;
$schemaValue = $schemaValueExcl;
$isData = $isDataExcl;
}
}}
{{??}}
{{
var $exclIsNumber = typeof $schemaExcl == 'number'
, $opStr = $op; /*used in error*/
}}
{{? $exclIsNumber && $isData }}
{{ var $opExpr = '\'' + $opStr + '\''; /*used in error*/ }}
if ({{# def.$dataNotType:'number' }}
( {{=$schemaValue}} === undefined
|| {{=$schemaExcl}} {{=$op}}= {{=$schemaValue}}
? {{=$data}} {{=$notOp}}= {{=$schemaExcl}}
: {{=$data}} {{=$notOp}} {{=$schemaValue}} )
|| {{=$data}} !== {{=$data}}) {
{{??}}
{{
if ($exclIsNumber && $schema === undefined) {
{{# def.setExclusiveLimit }}
$schemaValue = $schemaExcl;
$notOp += '=';
} else {
if ($exclIsNumber)
$schemaValue = Math[$isMax ? 'min' : 'max']($schemaExcl, $schema);
if ($schemaExcl === ($exclIsNumber ? $schemaValue : true)) {
{{# def.setExclusiveLimit }}
$notOp += '=';
} else {
$exclusive = false;
$opStr += '=';
}
}
var $opExpr = '\'' + $opStr + '\''; /*used in error*/
}}
if ({{# def.$dataNotType:'number' }}
{{=$data}} {{=$notOp}} {{=$schemaValue}}
|| {{=$data}} !== {{=$data}}) {
{{?}}
{{?}}
{{ $errorKeyword = $errorKeyword || $keyword; }}
{{# def.error:'_limit' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -1,12 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{# def.numberKeyword }}
{{ var $op = $keyword == 'maxItems' ? '>' : '<'; }}
if ({{# def.$dataNotType:'number' }} {{=$data}}.length {{=$op}} {{=$schemaValue}}) {
{{ var $errorKeyword = $keyword; }}
{{# def.error:'_limitItems' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -1,12 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{# def.numberKeyword }}
{{ var $op = $keyword == 'maxLength' ? '>' : '<'; }}
if ({{# def.$dataNotType:'number' }} {{# def.strLength }} {{=$op}} {{=$schemaValue}}) {
{{ var $errorKeyword = $keyword; }}
{{# def.error:'_limitLength' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -1,12 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{# def.numberKeyword }}
{{ var $op = $keyword == 'maxProperties' ? '>' : '<'; }}
if ({{# def.$dataNotType:'number' }} Object.keys({{=$data}}).length {{=$op}} {{=$schemaValue}}) {
{{ var $errorKeyword = $keyword; }}
{{# def.error:'_limitProperties' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -1,32 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{
var $currentBaseId = $it.baseId
, $allSchemasEmpty = true;
}}
{{~ $schema:$sch:$i }}
{{? {{# def.nonEmptySchema:$sch }} }}
{{
$allSchemasEmpty = false;
$it.schema = $sch;
$it.schemaPath = $schemaPath + '[' + $i + ']';
$it.errSchemaPath = $errSchemaPath + '/' + $i;
}}
{{# def.insertSubschemaCode }}
{{# def.ifResultValid }}
{{?}}
{{~}}
{{? $breakOnError }}
{{? $allSchemasEmpty }}
if (true) {
{{??}}
{{= $closingBraces.slice(0,-1) }}
{{?}}
{{?}}

View File

@@ -1,46 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{
var $noEmptySchema = $schema.every(function($sch) {
return {{# def.nonEmptySchema:$sch }};
});
}}
{{? $noEmptySchema }}
{{ var $currentBaseId = $it.baseId; }}
var {{=$errs}} = errors;
var {{=$valid}} = false;
{{# def.setCompositeRule }}
{{~ $schema:$sch:$i }}
{{
$it.schema = $sch;
$it.schemaPath = $schemaPath + '[' + $i + ']';
$it.errSchemaPath = $errSchemaPath + '/' + $i;
}}
{{# def.insertSubschemaCode }}
{{=$valid}} = {{=$valid}} || {{=$nextValid}};
if (!{{=$valid}}) {
{{ $closingBraces += '}'; }}
{{~}}
{{# def.resetCompositeRule }}
{{= $closingBraces }}
if (!{{=$valid}}) {
{{# def.extraError:'anyOf' }}
} else {
{{# def.resetErrors }}
{{? it.opts.allErrors }} } {{?}}
{{??}}
{{? $breakOnError }}
if (true) {
{{?}}
{{?}}

View File

@@ -1,51 +0,0 @@
{{## def.coerceType:
{{
var $dataType = 'dataType' + $lvl
, $coerced = 'coerced' + $lvl;
}}
var {{=$dataType}} = typeof {{=$data}};
var {{=$coerced}} = undefined;
{{? it.opts.coerceTypes == 'array' }}
if ({{=$dataType}} == 'object' && Array.isArray({{=$data}}) && {{=$data}}.length == 1) {
{{=$data}} = {{=$data}}[0];
{{=$dataType}} = typeof {{=$data}};
if ({{=it.util.checkDataType(it.schema.type, $data, it.opts.strictNumbers)}}) {{=$coerced}} = {{=$data}};
}
{{?}}
if ({{=$coerced}} !== undefined) ;
{{~ $coerceToTypes:$type:$i }}
{{? $type == 'string' }}
else if ({{=$dataType}} == 'number' || {{=$dataType}} == 'boolean')
{{=$coerced}} = '' + {{=$data}};
else if ({{=$data}} === null) {{=$coerced}} = '';
{{?? $type == 'number' || $type == 'integer' }}
else if ({{=$dataType}} == 'boolean' || {{=$data}} === null
|| ({{=$dataType}} == 'string' && {{=$data}} && {{=$data}} == +{{=$data}}
{{? $type == 'integer' }} && !({{=$data}} % 1){{?}}))
{{=$coerced}} = +{{=$data}};
{{?? $type == 'boolean' }}
else if ({{=$data}} === 'false' || {{=$data}} === 0 || {{=$data}} === null)
{{=$coerced}} = false;
else if ({{=$data}} === 'true' || {{=$data}} === 1)
{{=$coerced}} = true;
{{?? $type == 'null' }}
else if ({{=$data}} === '' || {{=$data}} === 0 || {{=$data}} === false)
{{=$coerced}} = null;
{{?? it.opts.coerceTypes == 'array' && $type == 'array' }}
else if ({{=$dataType}} == 'string' || {{=$dataType}} == 'number' || {{=$dataType}} == 'boolean' || {{=$data}} == null)
{{=$coerced}} = [{{=$data}}];
{{?}}
{{~}}
else {
{{# def.error:'type' }}
}
if ({{=$coerced}} !== undefined) {
{{# def.setParentData }}
{{=$data}} = {{=$coerced}};
{{? !$dataLvl }}if ({{=$parentData}} !== undefined){{?}}
{{=$parentData}}[{{=$parentDataProperty}}] = {{=$coerced}};
}
#}}

View File

@@ -1,9 +0,0 @@
{{# def.definitions }}
{{# def.setupKeyword }}
{{ var $comment = it.util.toQuotedString($schema); }}
{{? it.opts.$comment === true }}
console.log({{=$comment}});
{{?? typeof it.opts.$comment == 'function' }}
self._opts.$comment({{=$comment}}, {{=it.util.toQuotedString($errSchemaPath)}}, validate.root.schema);
{{?}}

View File

@@ -1,11 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{? !$isData }}
var schema{{=$lvl}} = validate.schema{{=$schemaPath}};
{{?}}
var {{=$valid}} = equal({{=$data}}, schema{{=$lvl}});
{{# def.checkError:'const' }}
{{? $breakOnError }} else { {{?}}

View File

@@ -1,55 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{
var $idx = 'i' + $lvl
, $dataNxt = $it.dataLevel = it.dataLevel + 1
, $nextData = 'data' + $dataNxt
, $currentBaseId = it.baseId
, $nonEmptySchema = {{# def.nonEmptySchema:$schema }};
}}
var {{=$errs}} = errors;
var {{=$valid}};
{{? $nonEmptySchema }}
{{# def.setCompositeRule }}
{{
$it.schema = $schema;
$it.schemaPath = $schemaPath;
$it.errSchemaPath = $errSchemaPath;
}}
var {{=$nextValid}} = false;
for (var {{=$idx}} = 0; {{=$idx}} < {{=$data}}.length; {{=$idx}}++) {
{{
$it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true);
var $passData = $data + '[' + $idx + ']';
$it.dataPathArr[$dataNxt] = $idx;
}}
{{# def.generateSubschemaCode }}
{{# def.optimizeValidate }}
if ({{=$nextValid}}) break;
}
{{# def.resetCompositeRule }}
{{= $closingBraces }}
if (!{{=$nextValid}}) {
{{??}}
if ({{=$data}}.length == 0) {
{{?}}
{{# def.error:'contains' }}
} else {
{{? $nonEmptySchema }}
{{# def.resetErrors }}
{{?}}
{{? it.opts.allErrors }} } {{?}}

View File

@@ -1,191 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{
var $rule = this
, $definition = 'definition' + $lvl
, $rDef = $rule.definition
, $closingBraces = '';
var $validate = $rDef.validate;
var $compile, $inline, $macro, $ruleValidate, $validateCode;
}}
{{? $isData && $rDef.$data }}
{{
$validateCode = 'keywordValidate' + $lvl;
var $validateSchema = $rDef.validateSchema;
}}
var {{=$definition}} = RULES.custom['{{=$keyword}}'].definition;
var {{=$validateCode}} = {{=$definition}}.validate;
{{??}}
{{
$ruleValidate = it.useCustomRule($rule, $schema, it.schema, it);
if (!$ruleValidate) return;
$schemaValue = 'validate.schema' + $schemaPath;
$validateCode = $ruleValidate.code;
$compile = $rDef.compile;
$inline = $rDef.inline;
$macro = $rDef.macro;
}}
{{?}}
{{
var $ruleErrs = $validateCode + '.errors'
, $i = 'i' + $lvl
, $ruleErr = 'ruleErr' + $lvl
, $asyncKeyword = $rDef.async;
if ($asyncKeyword && !it.async)
throw new Error('async keyword in sync schema');
}}
{{? !($inline || $macro) }}{{=$ruleErrs}} = null;{{?}}
var {{=$errs}} = errors;
var {{=$valid}};
{{## def.callRuleValidate:
{{=$validateCode}}.call(
{{? it.opts.passContext }}this{{??}}self{{?}}
{{? $compile || $rDef.schema === false }}
, {{=$data}}
{{??}}
, {{=$schemaValue}}
, {{=$data}}
, validate.schema{{=it.schemaPath}}
{{?}}
, {{# def.dataPath }}
{{# def.passParentData }}
, rootData
)
#}}
{{## def.extendErrors:_inline:
for (var {{=$i}}={{=$errs}}; {{=$i}}<errors; {{=$i}}++) {
var {{=$ruleErr}} = vErrors[{{=$i}}];
if ({{=$ruleErr}}.dataPath === undefined)
{{=$ruleErr}}.dataPath = (dataPath || '') + {{= it.errorPath }};
{{# _inline ? 'if (\{\{=$ruleErr\}\}.schemaPath === undefined) {' : '' }}
{{=$ruleErr}}.schemaPath = "{{=$errSchemaPath}}";
{{# _inline ? '}' : '' }}
{{? it.opts.verbose }}
{{=$ruleErr}}.schema = {{=$schemaValue}};
{{=$ruleErr}}.data = {{=$data}};
{{?}}
}
#}}
{{? $isData && $rDef.$data }}
{{ $closingBraces += '}'; }}
if ({{=$schemaValue}} === undefined) {
{{=$valid}} = true;
} else {
{{? $validateSchema }}
{{ $closingBraces += '}'; }}
{{=$valid}} = {{=$definition}}.validateSchema({{=$schemaValue}});
if ({{=$valid}}) {
{{?}}
{{?}}
{{? $inline }}
{{? $rDef.statements }}
{{= $ruleValidate.validate }}
{{??}}
{{=$valid}} = {{= $ruleValidate.validate }};
{{?}}
{{?? $macro }}
{{# def.setupNextLevel }}
{{
$it.schema = $ruleValidate.validate;
$it.schemaPath = '';
}}
{{# def.setCompositeRule }}
{{ var $code = it.validate($it).replace(/validate\.schema/g, $validateCode); }}
{{# def.resetCompositeRule }}
{{= $code }}
{{??}}
{{# def.beginDefOut}}
{{# def.callRuleValidate }}
{{# def.storeDefOut:def_callRuleValidate }}
{{? $rDef.errors === false }}
{{=$valid}} = {{? $asyncKeyword }}await {{?}}{{= def_callRuleValidate }};
{{??}}
{{? $asyncKeyword }}
{{ $ruleErrs = 'customErrors' + $lvl; }}
var {{=$ruleErrs}} = null;
try {
{{=$valid}} = await {{= def_callRuleValidate }};
} catch (e) {
{{=$valid}} = false;
if (e instanceof ValidationError) {{=$ruleErrs}} = e.errors;
else throw e;
}
{{??}}
{{=$ruleErrs}} = null;
{{=$valid}} = {{= def_callRuleValidate }};
{{?}}
{{?}}
{{?}}
{{? $rDef.modifying }}
if ({{=$parentData}}) {{=$data}} = {{=$parentData}}[{{=$parentDataProperty}}];
{{?}}
{{= $closingBraces }}
{{## def.notValidationResult:
{{? $rDef.valid === undefined }}
!{{? $macro }}{{=$nextValid}}{{??}}{{=$valid}}{{?}}
{{??}}
{{= !$rDef.valid }}
{{?}}
#}}
{{? $rDef.valid }}
{{? $breakOnError }} if (true) { {{?}}
{{??}}
if ({{# def.notValidationResult }}) {
{{ $errorKeyword = $rule.keyword; }}
{{# def.beginDefOut}}
{{# def.error:'custom' }}
{{# def.storeDefOut:def_customError }}
{{? $inline }}
{{? $rDef.errors }}
{{? $rDef.errors != 'full' }}
{{# def.extendErrors:true }}
{{?}}
{{??}}
{{? $rDef.errors === false}}
{{= def_customError }}
{{??}}
if ({{=$errs}} == errors) {
{{= def_customError }}
} else {
{{# def.extendErrors:true }}
}
{{?}}
{{?}}
{{?? $macro }}
{{# def.extraError:'custom' }}
{{??}}
{{? $rDef.errors === false}}
{{= def_customError }}
{{??}}
if (Array.isArray({{=$ruleErrs}})) {
if (vErrors === null) vErrors = {{=$ruleErrs}};
else vErrors = vErrors.concat({{=$ruleErrs}});
errors = vErrors.length;
{{# def.extendErrors:false }}
} else {
{{= def_customError }}
}
{{?}}
{{?}}
} {{? $breakOnError }} else { {{?}}
{{?}}

View File

@@ -1,47 +0,0 @@
{{## def.assignDefault:
{{? it.compositeRule }}
{{
if (it.opts.strictDefaults) {
var $defaultMsg = 'default is ignored for: ' + $passData;
if (it.opts.strictDefaults === 'log') it.logger.warn($defaultMsg);
else throw new Error($defaultMsg);
}
}}
{{??}}
if ({{=$passData}} === undefined
{{? it.opts.useDefaults == 'empty' }}
|| {{=$passData}} === null
|| {{=$passData}} === ''
{{?}}
)
{{=$passData}} = {{? it.opts.useDefaults == 'shared' }}
{{= it.useDefault($sch.default) }}
{{??}}
{{= JSON.stringify($sch.default) }}
{{?}};
{{?}}
#}}
{{## def.defaultProperties:
{{
var $schema = it.schema.properties
, $schemaKeys = Object.keys($schema); }}
{{~ $schemaKeys:$propertyKey }}
{{ var $sch = $schema[$propertyKey]; }}
{{? $sch.default !== undefined }}
{{ var $passData = $data + it.util.getProperty($propertyKey); }}
{{# def.assignDefault }}
{{?}}
{{~}}
#}}
{{## def.defaultItems:
{{~ it.schema.items:$sch:$i }}
{{? $sch.default !== undefined }}
{{ var $passData = $data + '[' + $i + ']'; }}
{{# def.assignDefault }}
{{?}}
{{~}}
#}}

View File

@@ -1,203 +0,0 @@
{{## def.setupKeyword:
{{
var $lvl = it.level;
var $dataLvl = it.dataLevel;
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $errorKeyword;
var $data = 'data' + ($dataLvl || '');
var $valid = 'valid' + $lvl;
var $errs = 'errs__' + $lvl;
}}
#}}
{{## def.setCompositeRule:
{{
var $wasComposite = it.compositeRule;
it.compositeRule = $it.compositeRule = true;
}}
#}}
{{## def.resetCompositeRule:
{{ it.compositeRule = $it.compositeRule = $wasComposite; }}
#}}
{{## def.setupNextLevel:
{{
var $it = it.util.copy(it);
var $closingBraces = '';
$it.level++;
var $nextValid = 'valid' + $it.level;
}}
#}}
{{## def.ifValid:
{{? $breakOnError }}
if ({{=$valid}}) {
{{ $closingBraces += '}'; }}
{{?}}
#}}
{{## def.ifResultValid:
{{? $breakOnError }}
if ({{=$nextValid}}) {
{{ $closingBraces += '}'; }}
{{?}}
#}}
{{## def.elseIfValid:
{{? $breakOnError }}
{{ $closingBraces += '}'; }}
else {
{{?}}
#}}
{{## def.nonEmptySchema:_schema:
(it.opts.strictKeywords
? (typeof _schema == 'object' && Object.keys(_schema).length > 0)
|| _schema === false
: it.util.schemaHasRules(_schema, it.RULES.all))
#}}
{{## def.strLength:
{{? it.opts.unicode === false }}
{{=$data}}.length
{{??}}
ucs2length({{=$data}})
{{?}}
#}}
{{## def.willOptimize:
it.util.varOccurences($code, $nextData) < 2
#}}
{{## def.generateSubschemaCode:
{{
var $code = it.validate($it);
$it.baseId = $currentBaseId;
}}
#}}
{{## def.insertSubschemaCode:
{{= it.validate($it) }}
{{ $it.baseId = $currentBaseId; }}
#}}
{{## def._optimizeValidate:
it.util.varReplace($code, $nextData, $passData)
#}}
{{## def.optimizeValidate:
{{? {{# def.willOptimize}} }}
{{= {{# def._optimizeValidate }} }}
{{??}}
var {{=$nextData}} = {{=$passData}};
{{= $code }}
{{?}}
#}}
{{## def.$data:
{{
var $isData = it.opts.$data && $schema && $schema.$data
, $schemaValue;
}}
{{? $isData }}
var schema{{=$lvl}} = {{= it.util.getData($schema.$data, $dataLvl, it.dataPathArr) }};
{{ $schemaValue = 'schema' + $lvl; }}
{{??}}
{{ $schemaValue = $schema; }}
{{?}}
#}}
{{## def.$dataNotType:_type:
{{?$isData}} ({{=$schemaValue}} !== undefined && typeof {{=$schemaValue}} != _type) || {{?}}
#}}
{{## def.check$dataIsArray:
if (schema{{=$lvl}} === undefined) {{=$valid}} = true;
else if (!Array.isArray(schema{{=$lvl}})) {{=$valid}} = false;
else {
#}}
{{## def.numberKeyword:
{{? !($isData || typeof $schema == 'number') }}
{{ throw new Error($keyword + ' must be number'); }}
{{?}}
#}}
{{## def.beginDefOut:
{{
var $$outStack = $$outStack || [];
$$outStack.push(out);
out = '';
}}
#}}
{{## def.storeDefOut:_variable:
{{
var _variable = out;
out = $$outStack.pop();
}}
#}}
{{## def.dataPath:(dataPath || ''){{? it.errorPath != '""'}} + {{= it.errorPath }}{{?}}#}}
{{## def.setParentData:
{{
var $parentData = $dataLvl ? 'data' + (($dataLvl-1)||'') : 'parentData'
, $parentDataProperty = $dataLvl ? it.dataPathArr[$dataLvl] : 'parentDataProperty';
}}
#}}
{{## def.passParentData:
{{# def.setParentData }}
, {{= $parentData }}
, {{= $parentDataProperty }}
#}}
{{## def.iterateProperties:
{{? $ownProperties }}
{{=$dataProperties}} = {{=$dataProperties}} || Object.keys({{=$data}});
for (var {{=$idx}}=0; {{=$idx}}<{{=$dataProperties}}.length; {{=$idx}}++) {
var {{=$key}} = {{=$dataProperties}}[{{=$idx}}];
{{??}}
for (var {{=$key}} in {{=$data}}) {
{{?}}
#}}
{{## def.noPropertyInData:
{{=$useData}} === undefined
{{? $ownProperties }}
|| !{{# def.isOwnProperty }}
{{?}}
#}}
{{## def.isOwnProperty:
Object.prototype.hasOwnProperty.call({{=$data}}, '{{=it.util.escapeQuotes($propertyKey)}}')
#}}

View File

@@ -1,79 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.missing }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{## def.propertyInData:
{{=$data}}{{= it.util.getProperty($property) }} !== undefined
{{? $ownProperties }}
&& Object.prototype.hasOwnProperty.call({{=$data}}, '{{=it.util.escapeQuotes($property)}}')
{{?}}
#}}
{{
var $schemaDeps = {}
, $propertyDeps = {}
, $ownProperties = it.opts.ownProperties;
for ($property in $schema) {
if ($property == '__proto__') continue;
var $sch = $schema[$property];
var $deps = Array.isArray($sch) ? $propertyDeps : $schemaDeps;
$deps[$property] = $sch;
}
}}
var {{=$errs}} = errors;
{{ var $currentErrorPath = it.errorPath; }}
var missing{{=$lvl}};
{{ for (var $property in $propertyDeps) { }}
{{ $deps = $propertyDeps[$property]; }}
{{? $deps.length }}
if ({{# def.propertyInData }}
{{? $breakOnError }}
&& ({{# def.checkMissingProperty:$deps }})) {
{{# def.errorMissingProperty:'dependencies' }}
{{??}}
) {
{{~ $deps:$propertyKey }}
{{# def.allErrorsMissingProperty:'dependencies' }}
{{~}}
{{?}}
} {{# def.elseIfValid }}
{{?}}
{{ } }}
{{
it.errorPath = $currentErrorPath;
var $currentBaseId = $it.baseId;
}}
{{ for (var $property in $schemaDeps) { }}
{{ var $sch = $schemaDeps[$property]; }}
{{? {{# def.nonEmptySchema:$sch }} }}
{{=$nextValid}} = true;
if ({{# def.propertyInData }}) {
{{
$it.schema = $sch;
$it.schemaPath = $schemaPath + it.util.getProperty($property);
$it.errSchemaPath = $errSchemaPath + '/' + it.util.escapeFragment($property);
}}
{{# def.insertSubschemaCode }}
}
{{# def.ifResultValid }}
{{?}}
{{ } }}
{{? $breakOnError }}
{{= $closingBraces }}
if ({{=$errs}} == errors) {
{{?}}

View File

@@ -1,30 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{
var $i = 'i' + $lvl
, $vSchema = 'schema' + $lvl;
}}
{{? !$isData }}
var {{=$vSchema}} = validate.schema{{=$schemaPath}};
{{?}}
var {{=$valid}};
{{?$isData}}{{# def.check$dataIsArray }}{{?}}
{{=$valid}} = false;
for (var {{=$i}}=0; {{=$i}}<{{=$vSchema}}.length; {{=$i}}++)
if (equal({{=$data}}, {{=$vSchema}}[{{=$i}}])) {
{{=$valid}} = true;
break;
}
{{? $isData }} } {{?}}
{{# def.checkError:'enum' }}
{{? $breakOnError }} else { {{?}}

View File

@@ -1,194 +0,0 @@
{{# def.definitions }}
{{## def._error:_rule:
{{ 'istanbul ignore else'; }}
{{? it.createErrors !== false }}
{
keyword: '{{= $errorKeyword || _rule }}'
, dataPath: (dataPath || '') + {{= it.errorPath }}
, schemaPath: {{=it.util.toQuotedString($errSchemaPath)}}
, params: {{# def._errorParams[_rule] }}
{{? it.opts.messages !== false }}
, message: {{# def._errorMessages[_rule] }}
{{?}}
{{? it.opts.verbose }}
, schema: {{# def._errorSchemas[_rule] }}
, parentSchema: validate.schema{{=it.schemaPath}}
, data: {{=$data}}
{{?}}
}
{{??}}
{}
{{?}}
#}}
{{## def._addError:_rule:
if (vErrors === null) vErrors = [err];
else vErrors.push(err);
errors++;
#}}
{{## def.addError:_rule:
var err = {{# def._error:_rule }};
{{# def._addError:_rule }}
#}}
{{## def.error:_rule:
{{# def.beginDefOut}}
{{# def._error:_rule }}
{{# def.storeDefOut:__err }}
{{? !it.compositeRule && $breakOnError }}
{{ 'istanbul ignore if'; }}
{{? it.async }}
throw new ValidationError([{{=__err}}]);
{{??}}
validate.errors = [{{=__err}}];
return false;
{{?}}
{{??}}
var err = {{=__err}};
{{# def._addError:_rule }}
{{?}}
#}}
{{## def.extraError:_rule:
{{# def.addError:_rule}}
{{? !it.compositeRule && $breakOnError }}
{{ 'istanbul ignore if'; }}
{{? it.async }}
throw new ValidationError(vErrors);
{{??}}
validate.errors = vErrors;
return false;
{{?}}
{{?}}
#}}
{{## def.checkError:_rule:
if (!{{=$valid}}) {
{{# def.error:_rule }}
}
#}}
{{## def.resetErrors:
errors = {{=$errs}};
if (vErrors !== null) {
if ({{=$errs}}) vErrors.length = {{=$errs}};
else vErrors = null;
}
#}}
{{## def.concatSchema:{{?$isData}}' + {{=$schemaValue}} + '{{??}}{{=$schema}}{{?}}#}}
{{## def.appendSchema:{{?$isData}}' + {{=$schemaValue}}{{??}}{{=$schemaValue}}'{{?}}#}}
{{## def.concatSchemaEQ:{{?$isData}}' + {{=$schemaValue}} + '{{??}}{{=it.util.escapeQuotes($schema)}}{{?}}#}}
{{## def._errorMessages = {
'false schema': "'boolean schema is false'",
$ref: "'can\\\'t resolve reference {{=it.util.escapeQuotes($schema)}}'",
additionalItems: "'should NOT have more than {{=$schema.length}} items'",
additionalProperties: "'{{? it.opts._errorDataPathProperty }}is an invalid additional property{{??}}should NOT have additional properties{{?}}'",
anyOf: "'should match some schema in anyOf'",
const: "'should be equal to constant'",
contains: "'should contain a valid item'",
dependencies: "'should have {{? $deps.length == 1 }}property {{= it.util.escapeQuotes($deps[0]) }}{{??}}properties {{= it.util.escapeQuotes($deps.join(\", \")) }}{{?}} when property {{= it.util.escapeQuotes($property) }} is present'",
'enum': "'should be equal to one of the allowed values'",
format: "'should match format \"{{#def.concatSchemaEQ}}\"'",
'if': "'should match \"' + {{=$ifClause}} + '\" schema'",
_limit: "'should be {{=$opStr}} {{#def.appendSchema}}",
_exclusiveLimit: "'{{=$exclusiveKeyword}} should be boolean'",
_limitItems: "'should NOT have {{?$keyword=='maxItems'}}more{{??}}fewer{{?}} than {{#def.concatSchema}} items'",
_limitLength: "'should NOT be {{?$keyword=='maxLength'}}longer{{??}}shorter{{?}} than {{#def.concatSchema}} characters'",
_limitProperties:"'should NOT have {{?$keyword=='maxProperties'}}more{{??}}fewer{{?}} than {{#def.concatSchema}} properties'",
multipleOf: "'should be multiple of {{#def.appendSchema}}",
not: "'should NOT be valid'",
oneOf: "'should match exactly one schema in oneOf'",
pattern: "'should match pattern \"{{#def.concatSchemaEQ}}\"'",
propertyNames: "'property name \\'{{=$invalidName}}\\' is invalid'",
required: "'{{? it.opts._errorDataPathProperty }}is a required property{{??}}should have required property \\'{{=$missingProperty}}\\'{{?}}'",
type: "'should be {{? $typeIsArray }}{{= $typeSchema.join(\",\") }}{{??}}{{=$typeSchema}}{{?}}'",
uniqueItems: "'should NOT have duplicate items (items ## ' + j + ' and ' + i + ' are identical)'",
custom: "'should pass \"{{=$rule.keyword}}\" keyword validation'",
patternRequired: "'should have property matching pattern \\'{{=$missingPattern}}\\''",
switch: "'should pass \"switch\" keyword validation'",
_formatLimit: "'should be {{=$opStr}} \"{{#def.concatSchemaEQ}}\"'",
_formatExclusiveLimit: "'{{=$exclusiveKeyword}} should be boolean'"
} #}}
{{## def.schemaRefOrVal: {{?$isData}}validate.schema{{=$schemaPath}}{{??}}{{=$schema}}{{?}} #}}
{{## def.schemaRefOrQS: {{?$isData}}validate.schema{{=$schemaPath}}{{??}}{{=it.util.toQuotedString($schema)}}{{?}} #}}
{{## def._errorSchemas = {
'false schema': "false",
$ref: "{{=it.util.toQuotedString($schema)}}",
additionalItems: "false",
additionalProperties: "false",
anyOf: "validate.schema{{=$schemaPath}}",
const: "validate.schema{{=$schemaPath}}",
contains: "validate.schema{{=$schemaPath}}",
dependencies: "validate.schema{{=$schemaPath}}",
'enum': "validate.schema{{=$schemaPath}}",
format: "{{#def.schemaRefOrQS}}",
'if': "validate.schema{{=$schemaPath}}",
_limit: "{{#def.schemaRefOrVal}}",
_exclusiveLimit: "validate.schema{{=$schemaPath}}",
_limitItems: "{{#def.schemaRefOrVal}}",
_limitLength: "{{#def.schemaRefOrVal}}",
_limitProperties:"{{#def.schemaRefOrVal}}",
multipleOf: "{{#def.schemaRefOrVal}}",
not: "validate.schema{{=$schemaPath}}",
oneOf: "validate.schema{{=$schemaPath}}",
pattern: "{{#def.schemaRefOrQS}}",
propertyNames: "validate.schema{{=$schemaPath}}",
required: "validate.schema{{=$schemaPath}}",
type: "validate.schema{{=$schemaPath}}",
uniqueItems: "{{#def.schemaRefOrVal}}",
custom: "validate.schema{{=$schemaPath}}",
patternRequired: "validate.schema{{=$schemaPath}}",
switch: "validate.schema{{=$schemaPath}}",
_formatLimit: "{{#def.schemaRefOrQS}}",
_formatExclusiveLimit: "validate.schema{{=$schemaPath}}"
} #}}
{{## def.schemaValueQS: {{?$isData}}{{=$schemaValue}}{{??}}{{=it.util.toQuotedString($schema)}}{{?}} #}}
{{## def._errorParams = {
'false schema': "{}",
$ref: "{ ref: '{{=it.util.escapeQuotes($schema)}}' }",
additionalItems: "{ limit: {{=$schema.length}} }",
additionalProperties: "{ additionalProperty: '{{=$additionalProperty}}' }",
anyOf: "{}",
const: "{ allowedValue: schema{{=$lvl}} }",
contains: "{}",
dependencies: "{ property: '{{= it.util.escapeQuotes($property) }}', missingProperty: '{{=$missingProperty}}', depsCount: {{=$deps.length}}, deps: '{{= it.util.escapeQuotes($deps.length==1 ? $deps[0] : $deps.join(\", \")) }}' }",
'enum': "{ allowedValues: schema{{=$lvl}} }",
format: "{ format: {{#def.schemaValueQS}} }",
'if': "{ failingKeyword: {{=$ifClause}} }",
_limit: "{ comparison: {{=$opExpr}}, limit: {{=$schemaValue}}, exclusive: {{=$exclusive}} }",
_exclusiveLimit: "{}",
_limitItems: "{ limit: {{=$schemaValue}} }",
_limitLength: "{ limit: {{=$schemaValue}} }",
_limitProperties:"{ limit: {{=$schemaValue}} }",
multipleOf: "{ multipleOf: {{=$schemaValue}} }",
not: "{}",
oneOf: "{ passingSchemas: {{=$passingSchemas}} }",
pattern: "{ pattern: {{#def.schemaValueQS}} }",
propertyNames: "{ propertyName: '{{=$invalidName}}' }",
required: "{ missingProperty: '{{=$missingProperty}}' }",
type: "{ type: '{{? $typeIsArray }}{{= $typeSchema.join(\",\") }}{{??}}{{=$typeSchema}}{{?}}' }",
uniqueItems: "{ i: i, j: j }",
custom: "{ keyword: '{{=$rule.keyword}}' }",
patternRequired: "{ missingPattern: '{{=$missingPattern}}' }",
switch: "{ caseIndex: {{=$caseIndex}} }",
_formatLimit: "{ comparison: {{=$opExpr}}, limit: {{#def.schemaValueQS}}, exclusive: {{=$exclusive}} }",
_formatExclusiveLimit: "{}"
} #}}

View File

@@ -1,106 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{## def.skipFormat:
{{? $breakOnError }} if (true) { {{?}}
{{ return out; }}
#}}
{{? it.opts.format === false }}{{# def.skipFormat }}{{?}}
{{# def.$data }}
{{## def.$dataCheckFormat:
{{# def.$dataNotType:'string' }}
({{? $unknownFormats != 'ignore' }}
({{=$schemaValue}} && !{{=$format}}
{{? $allowUnknown }}
&& self._opts.unknownFormats.indexOf({{=$schemaValue}}) == -1
{{?}}) ||
{{?}}
({{=$format}} && {{=$formatType}} == '{{=$ruleType}}'
&& !(typeof {{=$format}} == 'function'
? {{? it.async}}
(async{{=$lvl}} ? await {{=$format}}({{=$data}}) : {{=$format}}({{=$data}}))
{{??}}
{{=$format}}({{=$data}})
{{?}}
: {{=$format}}.test({{=$data}}))))
#}}
{{## def.checkFormat:
{{
var $formatRef = 'formats' + it.util.getProperty($schema);
if ($isObject) $formatRef += '.validate';
}}
{{? typeof $format == 'function' }}
{{=$formatRef}}({{=$data}})
{{??}}
{{=$formatRef}}.test({{=$data}})
{{?}}
#}}
{{
var $unknownFormats = it.opts.unknownFormats
, $allowUnknown = Array.isArray($unknownFormats);
}}
{{? $isData }}
{{
var $format = 'format' + $lvl
, $isObject = 'isObject' + $lvl
, $formatType = 'formatType' + $lvl;
}}
var {{=$format}} = formats[{{=$schemaValue}}];
var {{=$isObject}} = typeof {{=$format}} == 'object'
&& !({{=$format}} instanceof RegExp)
&& {{=$format}}.validate;
var {{=$formatType}} = {{=$isObject}} && {{=$format}}.type || 'string';
if ({{=$isObject}}) {
{{? it.async}}
var async{{=$lvl}} = {{=$format}}.async;
{{?}}
{{=$format}} = {{=$format}}.validate;
}
if ({{# def.$dataCheckFormat }}) {
{{??}}
{{ var $format = it.formats[$schema]; }}
{{? !$format }}
{{? $unknownFormats == 'ignore' }}
{{ it.logger.warn('unknown format "' + $schema + '" ignored in schema at path "' + it.errSchemaPath + '"'); }}
{{# def.skipFormat }}
{{?? $allowUnknown && $unknownFormats.indexOf($schema) >= 0 }}
{{# def.skipFormat }}
{{??}}
{{ throw new Error('unknown format "' + $schema + '" is used in schema at path "' + it.errSchemaPath + '"'); }}
{{?}}
{{?}}
{{
var $isObject = typeof $format == 'object'
&& !($format instanceof RegExp)
&& $format.validate;
var $formatType = $isObject && $format.type || 'string';
if ($isObject) {
var $async = $format.async === true;
$format = $format.validate;
}
}}
{{? $formatType != $ruleType }}
{{# def.skipFormat }}
{{?}}
{{? $async }}
{{
if (!it.async) throw new Error('async format in sync schema');
var $formatRef = 'formats' + it.util.getProperty($schema) + '.validate';
}}
if (!(await {{=$formatRef}}({{=$data}}))) {
{{??}}
if (!{{# def.checkFormat }}) {
{{?}}
{{?}}
{{# def.error:'format' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -1,73 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{## def.validateIfClause:_clause:
{{
$it.schema = it.schema['_clause'];
$it.schemaPath = it.schemaPath + '._clause';
$it.errSchemaPath = it.errSchemaPath + '/_clause';
}}
{{# def.insertSubschemaCode }}
{{=$valid}} = {{=$nextValid}};
{{? $thenPresent && $elsePresent }}
{{ $ifClause = 'ifClause' + $lvl; }}
var {{=$ifClause}} = '_clause';
{{??}}
{{ $ifClause = '\'_clause\''; }}
{{?}}
#}}
{{
var $thenSch = it.schema['then']
, $elseSch = it.schema['else']
, $thenPresent = $thenSch !== undefined && {{# def.nonEmptySchema:$thenSch }}
, $elsePresent = $elseSch !== undefined && {{# def.nonEmptySchema:$elseSch }}
, $currentBaseId = $it.baseId;
}}
{{? $thenPresent || $elsePresent }}
{{
var $ifClause;
$it.createErrors = false;
$it.schema = $schema;
$it.schemaPath = $schemaPath;
$it.errSchemaPath = $errSchemaPath;
}}
var {{=$errs}} = errors;
var {{=$valid}} = true;
{{# def.setCompositeRule }}
{{# def.insertSubschemaCode }}
{{ $it.createErrors = true; }}
{{# def.resetErrors }}
{{# def.resetCompositeRule }}
{{? $thenPresent }}
if ({{=$nextValid}}) {
{{# def.validateIfClause:then }}
}
{{? $elsePresent }}
else {
{{?}}
{{??}}
if (!{{=$nextValid}}) {
{{?}}
{{? $elsePresent }}
{{# def.validateIfClause:else }}
}
{{?}}
if (!{{=$valid}}) {
{{# def.extraError:'if' }}
}
{{? $breakOnError }} else { {{?}}
{{??}}
{{? $breakOnError }}
if (true) {
{{?}}
{{?}}

View File

@@ -1,98 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{## def.validateItems:startFrom:
for (var {{=$idx}} = {{=startFrom}}; {{=$idx}} < {{=$data}}.length; {{=$idx}}++) {
{{
$it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true);
var $passData = $data + '[' + $idx + ']';
$it.dataPathArr[$dataNxt] = $idx;
}}
{{# def.generateSubschemaCode }}
{{# def.optimizeValidate }}
{{? $breakOnError }}
if (!{{=$nextValid}}) break;
{{?}}
}
#}}
{{
var $idx = 'i' + $lvl
, $dataNxt = $it.dataLevel = it.dataLevel + 1
, $nextData = 'data' + $dataNxt
, $currentBaseId = it.baseId;
}}
var {{=$errs}} = errors;
var {{=$valid}};
{{? Array.isArray($schema) }}
{{ /* 'items' is an array of schemas */}}
{{ var $additionalItems = it.schema.additionalItems; }}
{{? $additionalItems === false }}
{{=$valid}} = {{=$data}}.length <= {{= $schema.length }};
{{
var $currErrSchemaPath = $errSchemaPath;
$errSchemaPath = it.errSchemaPath + '/additionalItems';
}}
{{# def.checkError:'additionalItems' }}
{{ $errSchemaPath = $currErrSchemaPath; }}
{{# def.elseIfValid}}
{{?}}
{{~ $schema:$sch:$i }}
{{? {{# def.nonEmptySchema:$sch }} }}
{{=$nextValid}} = true;
if ({{=$data}}.length > {{=$i}}) {
{{
var $passData = $data + '[' + $i + ']';
$it.schema = $sch;
$it.schemaPath = $schemaPath + '[' + $i + ']';
$it.errSchemaPath = $errSchemaPath + '/' + $i;
$it.errorPath = it.util.getPathExpr(it.errorPath, $i, it.opts.jsonPointers, true);
$it.dataPathArr[$dataNxt] = $i;
}}
{{# def.generateSubschemaCode }}
{{# def.optimizeValidate }}
}
{{# def.ifResultValid }}
{{?}}
{{~}}
{{? typeof $additionalItems == 'object' && {{# def.nonEmptySchema:$additionalItems }} }}
{{
$it.schema = $additionalItems;
$it.schemaPath = it.schemaPath + '.additionalItems';
$it.errSchemaPath = it.errSchemaPath + '/additionalItems';
}}
{{=$nextValid}} = true;
if ({{=$data}}.length > {{= $schema.length }}) {
{{# def.validateItems: $schema.length }}
}
{{# def.ifResultValid }}
{{?}}
{{?? {{# def.nonEmptySchema:$schema }} }}
{{ /* 'items' is a single schema */}}
{{
$it.schema = $schema;
$it.schemaPath = $schemaPath;
$it.errSchemaPath = $errSchemaPath;
}}
{{# def.validateItems: 0 }}
{{?}}
{{? $breakOnError }}
{{= $closingBraces }}
if ({{=$errs}} == errors) {
{{?}}

View File

@@ -1,39 +0,0 @@
{{## def.checkMissingProperty:_properties:
{{~ _properties:$propertyKey:$i }}
{{?$i}} || {{?}}
{{
var $prop = it.util.getProperty($propertyKey)
, $useData = $data + $prop;
}}
( ({{# def.noPropertyInData }}) && (missing{{=$lvl}} = {{= it.util.toQuotedString(it.opts.jsonPointers ? $propertyKey : $prop) }}) )
{{~}}
#}}
{{## def.errorMissingProperty:_error:
{{
var $propertyPath = 'missing' + $lvl
, $missingProperty = '\' + ' + $propertyPath + ' + \'';
if (it.opts._errorDataPathProperty) {
it.errorPath = it.opts.jsonPointers
? it.util.getPathExpr($currentErrorPath, $propertyPath, true)
: $currentErrorPath + ' + ' + $propertyPath;
}
}}
{{# def.error:_error }}
#}}
{{## def.allErrorsMissingProperty:_error:
{{
var $prop = it.util.getProperty($propertyKey)
, $missingProperty = it.util.escapeQuotes($propertyKey)
, $useData = $data + $prop;
if (it.opts._errorDataPathProperty) {
it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers);
}
}}
if ({{# def.noPropertyInData }}) {
{{# def.addError:_error }}
}
#}}

View File

@@ -1,22 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{# def.numberKeyword }}
var division{{=$lvl}};
if ({{?$isData}}
{{=$schemaValue}} !== undefined && (
typeof {{=$schemaValue}} != 'number' ||
{{?}}
(division{{=$lvl}} = {{=$data}} / {{=$schemaValue}},
{{? it.opts.multipleOfPrecision }}
Math.abs(Math.round(division{{=$lvl}}) - division{{=$lvl}}) > 1e-{{=it.opts.multipleOfPrecision}}
{{??}}
division{{=$lvl}} !== parseInt(division{{=$lvl}})
{{?}}
)
{{?$isData}} ) {{?}} ) {
{{# def.error:'multipleOf' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -1,43 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{? {{# def.nonEmptySchema:$schema }} }}
{{
$it.schema = $schema;
$it.schemaPath = $schemaPath;
$it.errSchemaPath = $errSchemaPath;
}}
var {{=$errs}} = errors;
{{# def.setCompositeRule }}
{{
$it.createErrors = false;
var $allErrorsOption;
if ($it.opts.allErrors) {
$allErrorsOption = $it.opts.allErrors;
$it.opts.allErrors = false;
}
}}
{{= it.validate($it) }}
{{
$it.createErrors = true;
if ($allErrorsOption) $it.opts.allErrors = $allErrorsOption;
}}
{{# def.resetCompositeRule }}
if ({{=$nextValid}}) {
{{# def.error:'not' }}
} else {
{{# def.resetErrors }}
{{? it.opts.allErrors }} } {{?}}
{{??}}
{{# def.addError:'not' }}
{{? $breakOnError}}
if (false) {
{{?}}
{{?}}

View File

@@ -1,54 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{
var $currentBaseId = $it.baseId
, $prevValid = 'prevValid' + $lvl
, $passingSchemas = 'passingSchemas' + $lvl;
}}
var {{=$errs}} = errors
, {{=$prevValid}} = false
, {{=$valid}} = false
, {{=$passingSchemas}} = null;
{{# def.setCompositeRule }}
{{~ $schema:$sch:$i }}
{{? {{# def.nonEmptySchema:$sch }} }}
{{
$it.schema = $sch;
$it.schemaPath = $schemaPath + '[' + $i + ']';
$it.errSchemaPath = $errSchemaPath + '/' + $i;
}}
{{# def.insertSubschemaCode }}
{{??}}
var {{=$nextValid}} = true;
{{?}}
{{? $i }}
if ({{=$nextValid}} && {{=$prevValid}}) {
{{=$valid}} = false;
{{=$passingSchemas}} = [{{=$passingSchemas}}, {{=$i}}];
} else {
{{ $closingBraces += '}'; }}
{{?}}
if ({{=$nextValid}}) {
{{=$valid}} = {{=$prevValid}} = true;
{{=$passingSchemas}} = {{=$i}};
}
{{~}}
{{# def.resetCompositeRule }}
{{= $closingBraces }}
if (!{{=$valid}}) {
{{# def.extraError:'oneOf' }}
} else {
{{# def.resetErrors }}
{{? it.opts.allErrors }} } {{?}}

View File

@@ -1,14 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{
var $regexp = $isData
? '(new RegExp(' + $schemaValue + '))'
: it.usePattern($schema);
}}
if ({{# def.$dataNotType:'string' }} !{{=$regexp}}.test({{=$data}}) ) {
{{# def.error:'pattern' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -1,245 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
{{## def.validateAdditional:
{{ /* additionalProperties is schema */
$it.schema = $aProperties;
$it.schemaPath = it.schemaPath + '.additionalProperties';
$it.errSchemaPath = it.errSchemaPath + '/additionalProperties';
$it.errorPath = it.opts._errorDataPathProperty
? it.errorPath
: it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers);
var $passData = $data + '[' + $key + ']';
$it.dataPathArr[$dataNxt] = $key;
}}
{{# def.generateSubschemaCode }}
{{# def.optimizeValidate }}
#}}
{{
var $key = 'key' + $lvl
, $idx = 'idx' + $lvl
, $dataNxt = $it.dataLevel = it.dataLevel + 1
, $nextData = 'data' + $dataNxt
, $dataProperties = 'dataProperties' + $lvl;
var $schemaKeys = Object.keys($schema || {}).filter(notProto)
, $pProperties = it.schema.patternProperties || {}
, $pPropertyKeys = Object.keys($pProperties).filter(notProto)
, $aProperties = it.schema.additionalProperties
, $someProperties = $schemaKeys.length || $pPropertyKeys.length
, $noAdditional = $aProperties === false
, $additionalIsSchema = typeof $aProperties == 'object'
&& Object.keys($aProperties).length
, $removeAdditional = it.opts.removeAdditional
, $checkAdditional = $noAdditional || $additionalIsSchema || $removeAdditional
, $ownProperties = it.opts.ownProperties
, $currentBaseId = it.baseId;
var $required = it.schema.required;
if ($required && !(it.opts.$data && $required.$data) && $required.length < it.opts.loopRequired) {
var $requiredHash = it.util.toHash($required);
}
function notProto(p) { return p !== '__proto__'; }
}}
var {{=$errs}} = errors;
var {{=$nextValid}} = true;
{{? $ownProperties }}
var {{=$dataProperties}} = undefined;
{{?}}
{{? $checkAdditional }}
{{# def.iterateProperties }}
{{? $someProperties }}
var isAdditional{{=$lvl}} = !(false
{{? $schemaKeys.length }}
{{? $schemaKeys.length > 8 }}
|| validate.schema{{=$schemaPath}}.hasOwnProperty({{=$key}})
{{??}}
{{~ $schemaKeys:$propertyKey }}
|| {{=$key}} == {{= it.util.toQuotedString($propertyKey) }}
{{~}}
{{?}}
{{?}}
{{? $pPropertyKeys.length }}
{{~ $pPropertyKeys:$pProperty:$i }}
|| {{= it.usePattern($pProperty) }}.test({{=$key}})
{{~}}
{{?}}
);
if (isAdditional{{=$lvl}}) {
{{?}}
{{? $removeAdditional == 'all' }}
delete {{=$data}}[{{=$key}}];
{{??}}
{{
var $currentErrorPath = it.errorPath;
var $additionalProperty = '\' + ' + $key + ' + \'';
if (it.opts._errorDataPathProperty) {
it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers);
}
}}
{{? $noAdditional }}
{{? $removeAdditional }}
delete {{=$data}}[{{=$key}}];
{{??}}
{{=$nextValid}} = false;
{{
var $currErrSchemaPath = $errSchemaPath;
$errSchemaPath = it.errSchemaPath + '/additionalProperties';
}}
{{# def.error:'additionalProperties' }}
{{ $errSchemaPath = $currErrSchemaPath; }}
{{? $breakOnError }} break; {{?}}
{{?}}
{{?? $additionalIsSchema }}
{{? $removeAdditional == 'failing' }}
var {{=$errs}} = errors;
{{# def.setCompositeRule }}
{{# def.validateAdditional }}
if (!{{=$nextValid}}) {
errors = {{=$errs}};
if (validate.errors !== null) {
if (errors) validate.errors.length = errors;
else validate.errors = null;
}
delete {{=$data}}[{{=$key}}];
}
{{# def.resetCompositeRule }}
{{??}}
{{# def.validateAdditional }}
{{? $breakOnError }} if (!{{=$nextValid}}) break; {{?}}
{{?}}
{{?}}
{{ it.errorPath = $currentErrorPath; }}
{{?}}
{{? $someProperties }}
}
{{?}}
}
{{# def.ifResultValid }}
{{?}}
{{ var $useDefaults = it.opts.useDefaults && !it.compositeRule; }}
{{? $schemaKeys.length }}
{{~ $schemaKeys:$propertyKey }}
{{ var $sch = $schema[$propertyKey]; }}
{{? {{# def.nonEmptySchema:$sch}} }}
{{
var $prop = it.util.getProperty($propertyKey)
, $passData = $data + $prop
, $hasDefault = $useDefaults && $sch.default !== undefined;
$it.schema = $sch;
$it.schemaPath = $schemaPath + $prop;
$it.errSchemaPath = $errSchemaPath + '/' + it.util.escapeFragment($propertyKey);
$it.errorPath = it.util.getPath(it.errorPath, $propertyKey, it.opts.jsonPointers);
$it.dataPathArr[$dataNxt] = it.util.toQuotedString($propertyKey);
}}
{{# def.generateSubschemaCode }}
{{? {{# def.willOptimize }} }}
{{
$code = {{# def._optimizeValidate }};
var $useData = $passData;
}}
{{??}}
{{ var $useData = $nextData; }}
var {{=$nextData}} = {{=$passData}};
{{?}}
{{? $hasDefault }}
{{= $code }}
{{??}}
{{? $requiredHash && $requiredHash[$propertyKey] }}
if ({{# def.noPropertyInData }}) {
{{=$nextValid}} = false;
{{
var $currentErrorPath = it.errorPath
, $currErrSchemaPath = $errSchemaPath
, $missingProperty = it.util.escapeQuotes($propertyKey);
if (it.opts._errorDataPathProperty) {
it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers);
}
$errSchemaPath = it.errSchemaPath + '/required';
}}
{{# def.error:'required' }}
{{ $errSchemaPath = $currErrSchemaPath; }}
{{ it.errorPath = $currentErrorPath; }}
} else {
{{??}}
{{? $breakOnError }}
if ({{# def.noPropertyInData }}) {
{{=$nextValid}} = true;
} else {
{{??}}
if ({{=$useData}} !== undefined
{{? $ownProperties }}
&& {{# def.isOwnProperty }}
{{?}}
) {
{{?}}
{{?}}
{{= $code }}
}
{{?}} {{ /* $hasDefault */ }}
{{?}} {{ /* def.nonEmptySchema */ }}
{{# def.ifResultValid }}
{{~}}
{{?}}
{{? $pPropertyKeys.length }}
{{~ $pPropertyKeys:$pProperty }}
{{ var $sch = $pProperties[$pProperty]; }}
{{? {{# def.nonEmptySchema:$sch}} }}
{{
$it.schema = $sch;
$it.schemaPath = it.schemaPath + '.patternProperties' + it.util.getProperty($pProperty);
$it.errSchemaPath = it.errSchemaPath + '/patternProperties/'
+ it.util.escapeFragment($pProperty);
}}
{{# def.iterateProperties }}
if ({{= it.usePattern($pProperty) }}.test({{=$key}})) {
{{
$it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers);
var $passData = $data + '[' + $key + ']';
$it.dataPathArr[$dataNxt] = $key;
}}
{{# def.generateSubschemaCode }}
{{# def.optimizeValidate }}
{{? $breakOnError }} if (!{{=$nextValid}}) break; {{?}}
}
{{? $breakOnError }} else {{=$nextValid}} = true; {{?}}
}
{{# def.ifResultValid }}
{{?}} {{ /* def.nonEmptySchema */ }}
{{~}}
{{?}}
{{? $breakOnError }}
{{= $closingBraces }}
if ({{=$errs}} == errors) {
{{?}}

View File

@@ -1,52 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.setupNextLevel }}
var {{=$errs}} = errors;
{{? {{# def.nonEmptySchema:$schema }} }}
{{
$it.schema = $schema;
$it.schemaPath = $schemaPath;
$it.errSchemaPath = $errSchemaPath;
}}
{{
var $key = 'key' + $lvl
, $idx = 'idx' + $lvl
, $i = 'i' + $lvl
, $invalidName = '\' + ' + $key + ' + \''
, $dataNxt = $it.dataLevel = it.dataLevel + 1
, $nextData = 'data' + $dataNxt
, $dataProperties = 'dataProperties' + $lvl
, $ownProperties = it.opts.ownProperties
, $currentBaseId = it.baseId;
}}
{{? $ownProperties }}
var {{=$dataProperties}} = undefined;
{{?}}
{{# def.iterateProperties }}
var startErrs{{=$lvl}} = errors;
{{ var $passData = $key; }}
{{# def.setCompositeRule }}
{{# def.generateSubschemaCode }}
{{# def.optimizeValidate }}
{{# def.resetCompositeRule }}
if (!{{=$nextValid}}) {
for (var {{=$i}}=startErrs{{=$lvl}}; {{=$i}}<errors; {{=$i}}++) {
vErrors[{{=$i}}].propertyName = {{=$key}};
}
{{# def.extraError:'propertyNames' }}
{{? $breakOnError }} break; {{?}}
}
}
{{?}}
{{? $breakOnError }}
{{= $closingBraces }}
if ({{=$errs}} == errors) {
{{?}}

View File

@@ -1,85 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{## def._validateRef:_v:
{{? it.opts.passContext }}
{{=_v}}.call(this,
{{??}}
{{=_v}}(
{{?}}
{{=$data}}, {{# def.dataPath }}{{# def.passParentData }}, rootData)
#}}
{{ var $async, $refCode; }}
{{? $schema == '#' || $schema == '#/' }}
{{
if (it.isRoot) {
$async = it.async;
$refCode = 'validate';
} else {
$async = it.root.schema.$async === true;
$refCode = 'root.refVal[0]';
}
}}
{{??}}
{{ var $refVal = it.resolveRef(it.baseId, $schema, it.isRoot); }}
{{? $refVal === undefined }}
{{ var $message = it.MissingRefError.message(it.baseId, $schema); }}
{{? it.opts.missingRefs == 'fail' }}
{{ it.logger.error($message); }}
{{# def.error:'$ref' }}
{{? $breakOnError }} if (false) { {{?}}
{{?? it.opts.missingRefs == 'ignore' }}
{{ it.logger.warn($message); }}
{{? $breakOnError }} if (true) { {{?}}
{{??}}
{{ throw new it.MissingRefError(it.baseId, $schema, $message); }}
{{?}}
{{?? $refVal.inline }}
{{# def.setupNextLevel }}
{{
$it.schema = $refVal.schema;
$it.schemaPath = '';
$it.errSchemaPath = $schema;
}}
{{ var $code = it.validate($it).replace(/validate\.schema/g, $refVal.code); }}
{{= $code }}
{{? $breakOnError}}
if ({{=$nextValid}}) {
{{?}}
{{??}}
{{
$async = $refVal.$async === true || (it.async && $refVal.$async !== false);
$refCode = $refVal.code;
}}
{{?}}
{{?}}
{{? $refCode }}
{{# def.beginDefOut}}
{{# def._validateRef:$refCode }}
{{# def.storeDefOut:__callValidate }}
{{? $async }}
{{ if (!it.async) throw new Error('async schema referenced by sync schema'); }}
{{? $breakOnError }} var {{=$valid}}; {{?}}
try {
await {{=__callValidate}};
{{? $breakOnError }} {{=$valid}} = true; {{?}}
} catch (e) {
if (!(e instanceof ValidationError)) throw e;
if (vErrors === null) vErrors = e.errors;
else vErrors = vErrors.concat(e.errors);
errors = vErrors.length;
{{? $breakOnError }} {{=$valid}} = false; {{?}}
}
{{? $breakOnError }} if ({{=$valid}}) { {{?}}
{{??}}
if (!{{=__callValidate}}) {
if (vErrors === null) vErrors = {{=$refCode}}.errors;
else vErrors = vErrors.concat({{=$refCode}}.errors);
errors = vErrors.length;
} {{? $breakOnError }} else { {{?}}
{{?}}
{{?}}

View File

@@ -1,108 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.missing }}
{{# def.setupKeyword }}
{{# def.$data }}
{{ var $vSchema = 'schema' + $lvl; }}
{{## def.setupLoop:
{{? !$isData }}
var {{=$vSchema}} = validate.schema{{=$schemaPath}};
{{?}}
{{
var $i = 'i' + $lvl
, $propertyPath = 'schema' + $lvl + '[' + $i + ']'
, $missingProperty = '\' + ' + $propertyPath + ' + \'';
if (it.opts._errorDataPathProperty) {
it.errorPath = it.util.getPathExpr($currentErrorPath, $propertyPath, it.opts.jsonPointers);
}
}}
#}}
{{## def.isRequiredOwnProperty:
Object.prototype.hasOwnProperty.call({{=$data}}, {{=$vSchema}}[{{=$i}}])
#}}
{{? !$isData }}
{{? $schema.length < it.opts.loopRequired &&
it.schema.properties && Object.keys(it.schema.properties).length }}
{{ var $required = []; }}
{{~ $schema:$property }}
{{ var $propertySch = it.schema.properties[$property]; }}
{{? !($propertySch && {{# def.nonEmptySchema:$propertySch}}) }}
{{ $required[$required.length] = $property; }}
{{?}}
{{~}}
{{??}}
{{ var $required = $schema; }}
{{?}}
{{?}}
{{? $isData || $required.length }}
{{
var $currentErrorPath = it.errorPath
, $loopRequired = $isData || $required.length >= it.opts.loopRequired
, $ownProperties = it.opts.ownProperties;
}}
{{? $breakOnError }}
var missing{{=$lvl}};
{{? $loopRequired }}
{{# def.setupLoop }}
var {{=$valid}} = true;
{{?$isData}}{{# def.check$dataIsArray }}{{?}}
for (var {{=$i}} = 0; {{=$i}} < {{=$vSchema}}.length; {{=$i}}++) {
{{=$valid}} = {{=$data}}[{{=$vSchema}}[{{=$i}}]] !== undefined
{{? $ownProperties }}
&& {{# def.isRequiredOwnProperty }}
{{?}};
if (!{{=$valid}}) break;
}
{{? $isData }} } {{?}}
{{# def.checkError:'required' }}
else {
{{??}}
if ({{# def.checkMissingProperty:$required }}) {
{{# def.errorMissingProperty:'required' }}
} else {
{{?}}
{{??}}
{{? $loopRequired }}
{{# def.setupLoop }}
{{? $isData }}
if ({{=$vSchema}} && !Array.isArray({{=$vSchema}})) {
{{# def.addError:'required' }}
} else if ({{=$vSchema}} !== undefined) {
{{?}}
for (var {{=$i}} = 0; {{=$i}} < {{=$vSchema}}.length; {{=$i}}++) {
if ({{=$data}}[{{=$vSchema}}[{{=$i}}]] === undefined
{{? $ownProperties }}
|| !{{# def.isRequiredOwnProperty }}
{{?}}) {
{{# def.addError:'required' }}
}
}
{{? $isData }} } {{?}}
{{??}}
{{~ $required:$propertyKey }}
{{# def.allErrorsMissingProperty:'required' }}
{{~}}
{{?}}
{{?}}
{{ it.errorPath = $currentErrorPath; }}
{{?? $breakOnError }}
if (true) {
{{?}}

View File

@@ -1,62 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{? ($schema || $isData) && it.opts.uniqueItems !== false }}
{{? $isData }}
var {{=$valid}};
if ({{=$schemaValue}} === false || {{=$schemaValue}} === undefined)
{{=$valid}} = true;
else if (typeof {{=$schemaValue}} != 'boolean')
{{=$valid}} = false;
else {
{{?}}
var i = {{=$data}}.length
, {{=$valid}} = true
, j;
if (i > 1) {
{{
var $itemType = it.schema.items && it.schema.items.type
, $typeIsArray = Array.isArray($itemType);
}}
{{? !$itemType || $itemType == 'object' || $itemType == 'array' ||
($typeIsArray && ($itemType.indexOf('object') >= 0 || $itemType.indexOf('array') >= 0)) }}
outer:
for (;i--;) {
for (j = i; j--;) {
if (equal({{=$data}}[i], {{=$data}}[j])) {
{{=$valid}} = false;
break outer;
}
}
}
{{??}}
var itemIndices = {}, item;
for (;i--;) {
var item = {{=$data}}[i];
{{ var $method = 'checkDataType' + ($typeIsArray ? 's' : ''); }}
if ({{= it.util[$method]($itemType, 'item', it.opts.strictNumbers, true) }}) continue;
{{? $typeIsArray}}
if (typeof item == 'string') item = '"' + item;
{{?}}
if (typeof itemIndices[item] == 'number') {
{{=$valid}} = false;
j = itemIndices[item];
break;
}
itemIndices[item] = i;
}
{{?}}
}
{{? $isData }} } {{?}}
if (!{{=$valid}}) {
{{# def.error:'uniqueItems' }}
} {{? $breakOnError }} else { {{?}}
{{??}}
{{? $breakOnError }} if (true) { {{?}}
{{?}}

View File

@@ -1,276 +0,0 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.defaults }}
{{# def.coerce }}
{{ /**
* schema compilation (render) time:
* it = { schema, RULES, _validate, opts }
* it.validate - this template function,
* it is used recursively to generate code for subschemas
*
* runtime:
* "validate" is a variable name to which this function will be assigned
* validateRef etc. are defined in the parent scope in index.js
*/ }}
{{
var $async = it.schema.$async === true
, $refKeywords = it.util.schemaHasRulesExcept(it.schema, it.RULES.all, '$ref')
, $id = it.self._getId(it.schema);
}}
{{
if (it.opts.strictKeywords) {
var $unknownKwd = it.util.schemaUnknownRules(it.schema, it.RULES.keywords);
if ($unknownKwd) {
var $keywordsMsg = 'unknown keyword: ' + $unknownKwd;
if (it.opts.strictKeywords === 'log') it.logger.warn($keywordsMsg);
else throw new Error($keywordsMsg);
}
}
}}
{{? it.isTop }}
var validate = {{?$async}}{{it.async = true;}}async {{?}}function(data, dataPath, parentData, parentDataProperty, rootData) {
'use strict';
{{? $id && (it.opts.sourceCode || it.opts.processCode) }}
{{= '/\*# sourceURL=' + $id + ' */' }}
{{?}}
{{?}}
{{? typeof it.schema == 'boolean' || !($refKeywords || it.schema.$ref) }}
{{ var $keyword = 'false schema'; }}
{{# def.setupKeyword }}
{{? it.schema === false}}
{{? it.isTop}}
{{ $breakOnError = true; }}
{{??}}
var {{=$valid}} = false;
{{?}}
{{# def.error:'false schema' }}
{{??}}
{{? it.isTop}}
{{? $async }}
return data;
{{??}}
validate.errors = null;
return true;
{{?}}
{{??}}
var {{=$valid}} = true;
{{?}}
{{?}}
{{? it.isTop}}
};
return validate;
{{?}}
{{ return out; }}
{{?}}
{{? it.isTop }}
{{
var $top = it.isTop
, $lvl = it.level = 0
, $dataLvl = it.dataLevel = 0
, $data = 'data';
it.rootId = it.resolve.fullPath(it.self._getId(it.root.schema));
it.baseId = it.baseId || it.rootId;
delete it.isTop;
it.dataPathArr = [""];
if (it.schema.default !== undefined && it.opts.useDefaults && it.opts.strictDefaults) {
var $defaultMsg = 'default is ignored in the schema root';
if (it.opts.strictDefaults === 'log') it.logger.warn($defaultMsg);
else throw new Error($defaultMsg);
}
}}
var vErrors = null; {{ /* don't edit, used in replace */ }}
var errors = 0; {{ /* don't edit, used in replace */ }}
if (rootData === undefined) rootData = data; {{ /* don't edit, used in replace */ }}
{{??}}
{{
var $lvl = it.level
, $dataLvl = it.dataLevel
, $data = 'data' + ($dataLvl || '');
if ($id) it.baseId = it.resolve.url(it.baseId, $id);
if ($async && !it.async) throw new Error('async schema in sync schema');
}}
var errs_{{=$lvl}} = errors;
{{?}}
{{
var $valid = 'valid' + $lvl
, $breakOnError = !it.opts.allErrors
, $closingBraces1 = ''
, $closingBraces2 = '';
var $errorKeyword;
var $typeSchema = it.schema.type
, $typeIsArray = Array.isArray($typeSchema);
if ($typeSchema && it.opts.nullable && it.schema.nullable === true) {
if ($typeIsArray) {
if ($typeSchema.indexOf('null') == -1)
$typeSchema = $typeSchema.concat('null');
} else if ($typeSchema != 'null') {
$typeSchema = [$typeSchema, 'null'];
$typeIsArray = true;
}
}
if ($typeIsArray && $typeSchema.length == 1) {
$typeSchema = $typeSchema[0];
$typeIsArray = false;
}
}}
{{## def.checkType:
{{
var $schemaPath = it.schemaPath + '.type'
, $errSchemaPath = it.errSchemaPath + '/type'
, $method = $typeIsArray ? 'checkDataTypes' : 'checkDataType';
}}
if ({{= it.util[$method]($typeSchema, $data, it.opts.strictNumbers, true) }}) {
#}}
{{? it.schema.$ref && $refKeywords }}
{{? it.opts.extendRefs == 'fail' }}
{{ throw new Error('$ref: validation keywords used in schema at path "' + it.errSchemaPath + '" (see option extendRefs)'); }}
{{?? it.opts.extendRefs !== true }}
{{
$refKeywords = false;
it.logger.warn('$ref: keywords ignored in schema at path "' + it.errSchemaPath + '"');
}}
{{?}}
{{?}}
{{? it.schema.$comment && it.opts.$comment }}
{{= it.RULES.all.$comment.code(it, '$comment') }}
{{?}}
{{? $typeSchema }}
{{? it.opts.coerceTypes }}
{{ var $coerceToTypes = it.util.coerceToTypes(it.opts.coerceTypes, $typeSchema); }}
{{?}}
{{ var $rulesGroup = it.RULES.types[$typeSchema]; }}
{{? $coerceToTypes || $typeIsArray || $rulesGroup === true ||
($rulesGroup && !$shouldUseGroup($rulesGroup)) }}
{{
var $schemaPath = it.schemaPath + '.type'
, $errSchemaPath = it.errSchemaPath + '/type';
}}
{{# def.checkType }}
{{? $coerceToTypes }}
{{# def.coerceType }}
{{??}}
{{# def.error:'type' }}
{{?}}
}
{{?}}
{{?}}
{{? it.schema.$ref && !$refKeywords }}
{{= it.RULES.all.$ref.code(it, '$ref') }}
{{? $breakOnError }}
}
if (errors === {{?$top}}0{{??}}errs_{{=$lvl}}{{?}}) {
{{ $closingBraces2 += '}'; }}
{{?}}
{{??}}
{{~ it.RULES:$rulesGroup }}
{{? $shouldUseGroup($rulesGroup) }}
{{? $rulesGroup.type }}
if ({{= it.util.checkDataType($rulesGroup.type, $data, it.opts.strictNumbers) }}) {
{{?}}
{{? it.opts.useDefaults }}
{{? $rulesGroup.type == 'object' && it.schema.properties }}
{{# def.defaultProperties }}
{{?? $rulesGroup.type == 'array' && Array.isArray(it.schema.items) }}
{{# def.defaultItems }}
{{?}}
{{?}}
{{~ $rulesGroup.rules:$rule }}
{{? $shouldUseRule($rule) }}
{{ var $code = $rule.code(it, $rule.keyword, $rulesGroup.type); }}
{{? $code }}
{{= $code }}
{{? $breakOnError }}
{{ $closingBraces1 += '}'; }}
{{?}}
{{?}}
{{?}}
{{~}}
{{? $breakOnError }}
{{= $closingBraces1 }}
{{ $closingBraces1 = ''; }}
{{?}}
{{? $rulesGroup.type }}
}
{{? $typeSchema && $typeSchema === $rulesGroup.type && !$coerceToTypes }}
else {
{{
var $schemaPath = it.schemaPath + '.type'
, $errSchemaPath = it.errSchemaPath + '/type';
}}
{{# def.error:'type' }}
}
{{?}}
{{?}}
{{? $breakOnError }}
if (errors === {{?$top}}0{{??}}errs_{{=$lvl}}{{?}}) {
{{ $closingBraces2 += '}'; }}
{{?}}
{{?}}
{{~}}
{{?}}
{{? $breakOnError }} {{= $closingBraces2 }} {{?}}
{{? $top }}
{{? $async }}
if (errors === 0) return data; {{ /* don't edit, used in replace */ }}
else throw new ValidationError(vErrors); {{ /* don't edit, used in replace */ }}
{{??}}
validate.errors = vErrors; {{ /* don't edit, used in replace */ }}
return errors === 0; {{ /* don't edit, used in replace */ }}
{{?}}
};
return validate;
{{??}}
var {{=$valid}} = errors === errs_{{=$lvl}};
{{?}}
{{
function $shouldUseGroup($rulesGroup) {
var rules = $rulesGroup.rules;
for (var i=0; i < rules.length; i++)
if ($shouldUseRule(rules[i]))
return true;
}
function $shouldUseRule($rule) {
return it.schema[$rule.keyword] !== undefined ||
($rule.implements && $ruleImplementsSomeKeyword($rule));
}
function $ruleImplementsSomeKeyword($rule) {
var impl = $rule.implements;
for (var i=0; i < impl.length; i++)
if (it.schema[impl[i]] !== undefined)
return true;
}
}}

View File

@@ -1,3 +0,0 @@
These files are compiled dot templates from dot folder.
Do NOT edit them directly, edit the templates and run `npm run build` from main ajv folder.

View File

@@ -1,163 +0,0 @@
'use strict';
module.exports = function generate__limit(it, $keyword, $ruleType) {
var out = ' ';
var $lvl = it.level;
var $dataLvl = it.dataLevel;
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $errorKeyword;
var $data = 'data' + ($dataLvl || '');
var $isData = it.opts.$data && $schema && $schema.$data,
$schemaValue;
if ($isData) {
out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; ';
$schemaValue = 'schema' + $lvl;
} else {
$schemaValue = $schema;
}
var $isMax = $keyword == 'maximum',
$exclusiveKeyword = $isMax ? 'exclusiveMaximum' : 'exclusiveMinimum',
$schemaExcl = it.schema[$exclusiveKeyword],
$isDataExcl = it.opts.$data && $schemaExcl && $schemaExcl.$data,
$op = $isMax ? '<' : '>',
$notOp = $isMax ? '>' : '<',
$errorKeyword = undefined;
if (!($isData || typeof $schema == 'number' || $schema === undefined)) {
throw new Error($keyword + ' must be number');
}
if (!($isDataExcl || $schemaExcl === undefined || typeof $schemaExcl == 'number' || typeof $schemaExcl == 'boolean')) {
throw new Error($exclusiveKeyword + ' must be number or boolean');
}
if ($isDataExcl) {
var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr),
$exclusive = 'exclusive' + $lvl,
$exclType = 'exclType' + $lvl,
$exclIsNumber = 'exclIsNumber' + $lvl,
$opExpr = 'op' + $lvl,
$opStr = '\' + ' + $opExpr + ' + \'';
out += ' var schemaExcl' + ($lvl) + ' = ' + ($schemaValueExcl) + '; ';
$schemaValueExcl = 'schemaExcl' + $lvl;
out += ' var ' + ($exclusive) + '; var ' + ($exclType) + ' = typeof ' + ($schemaValueExcl) + '; if (' + ($exclType) + ' != \'boolean\' && ' + ($exclType) + ' != \'undefined\' && ' + ($exclType) + ' != \'number\') { ';
var $errorKeyword = $exclusiveKeyword;
var $$outStack = $$outStack || [];
$$outStack.push(out);
out = ''; /* istanbul ignore else */
if (it.createErrors !== false) {
out += ' { keyword: \'' + ($errorKeyword || '_exclusiveLimit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} ';
if (it.opts.messages !== false) {
out += ' , message: \'' + ($exclusiveKeyword) + ' should be boolean\' ';
}
if (it.opts.verbose) {
out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
}
out += ' } ';
} else {
out += ' {} ';
}
var __err = out;
out = $$outStack.pop();
if (!it.compositeRule && $breakOnError) {
/* istanbul ignore if */
if (it.async) {
out += ' throw new ValidationError([' + (__err) + ']); ';
} else {
out += ' validate.errors = [' + (__err) + ']; return false; ';
}
} else {
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
}
out += ' } else if ( ';
if ($isData) {
out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
}
out += ' ' + ($exclType) + ' == \'number\' ? ( (' + ($exclusive) + ' = ' + ($schemaValue) + ' === undefined || ' + ($schemaValueExcl) + ' ' + ($op) + '= ' + ($schemaValue) + ') ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaValueExcl) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) : ( (' + ($exclusive) + ' = ' + ($schemaValueExcl) + ' === true) ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaValue) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) || ' + ($data) + ' !== ' + ($data) + ') { var op' + ($lvl) + ' = ' + ($exclusive) + ' ? \'' + ($op) + '\' : \'' + ($op) + '=\'; ';
if ($schema === undefined) {
$errorKeyword = $exclusiveKeyword;
$errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword;
$schemaValue = $schemaValueExcl;
$isData = $isDataExcl;
}
} else {
var $exclIsNumber = typeof $schemaExcl == 'number',
$opStr = $op;
if ($exclIsNumber && $isData) {
var $opExpr = '\'' + $opStr + '\'';
out += ' if ( ';
if ($isData) {
out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
}
out += ' ( ' + ($schemaValue) + ' === undefined || ' + ($schemaExcl) + ' ' + ($op) + '= ' + ($schemaValue) + ' ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaExcl) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) || ' + ($data) + ' !== ' + ($data) + ') { ';
} else {
if ($exclIsNumber && $schema === undefined) {
$exclusive = true;
$errorKeyword = $exclusiveKeyword;
$errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword;
$schemaValue = $schemaExcl;
$notOp += '=';
} else {
if ($exclIsNumber) $schemaValue = Math[$isMax ? 'min' : 'max']($schemaExcl, $schema);
if ($schemaExcl === ($exclIsNumber ? $schemaValue : true)) {
$exclusive = true;
$errorKeyword = $exclusiveKeyword;
$errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword;
$notOp += '=';
} else {
$exclusive = false;
$opStr += '=';
}
}
var $opExpr = '\'' + $opStr + '\'';
out += ' if ( ';
if ($isData) {
out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
}
out += ' ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' || ' + ($data) + ' !== ' + ($data) + ') { ';
}
}
$errorKeyword = $errorKeyword || $keyword;
var $$outStack = $$outStack || [];
$$outStack.push(out);
out = ''; /* istanbul ignore else */
if (it.createErrors !== false) {
out += ' { keyword: \'' + ($errorKeyword || '_limit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { comparison: ' + ($opExpr) + ', limit: ' + ($schemaValue) + ', exclusive: ' + ($exclusive) + ' } ';
if (it.opts.messages !== false) {
out += ' , message: \'should be ' + ($opStr) + ' ';
if ($isData) {
out += '\' + ' + ($schemaValue);
} else {
out += '' + ($schemaValue) + '\'';
}
}
if (it.opts.verbose) {
out += ' , schema: ';
if ($isData) {
out += 'validate.schema' + ($schemaPath);
} else {
out += '' + ($schema);
}
out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
}
out += ' } ';
} else {
out += ' {} ';
}
var __err = out;
out = $$outStack.pop();
if (!it.compositeRule && $breakOnError) {
/* istanbul ignore if */
if (it.async) {
out += ' throw new ValidationError([' + (__err) + ']); ';
} else {
out += ' validate.errors = [' + (__err) + ']; return false; ';
}
} else {
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
}
out += ' } ';
if ($breakOnError) {
out += ' else { ';
}
return out;
}

View File

@@ -1,80 +0,0 @@
'use strict';
module.exports = function generate__limitItems(it, $keyword, $ruleType) {
var out = ' ';
var $lvl = it.level;
var $dataLvl = it.dataLevel;
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $errorKeyword;
var $data = 'data' + ($dataLvl || '');
var $isData = it.opts.$data && $schema && $schema.$data,
$schemaValue;
if ($isData) {
out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; ';
$schemaValue = 'schema' + $lvl;
} else {
$schemaValue = $schema;
}
if (!($isData || typeof $schema == 'number')) {
throw new Error($keyword + ' must be number');
}
var $op = $keyword == 'maxItems' ? '>' : '<';
out += 'if ( ';
if ($isData) {
out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
}
out += ' ' + ($data) + '.length ' + ($op) + ' ' + ($schemaValue) + ') { ';
var $errorKeyword = $keyword;
var $$outStack = $$outStack || [];
$$outStack.push(out);
out = ''; /* istanbul ignore else */
if (it.createErrors !== false) {
out += ' { keyword: \'' + ($errorKeyword || '_limitItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } ';
if (it.opts.messages !== false) {
out += ' , message: \'should NOT have ';
if ($keyword == 'maxItems') {
out += 'more';
} else {
out += 'fewer';
}
out += ' than ';
if ($isData) {
out += '\' + ' + ($schemaValue) + ' + \'';
} else {
out += '' + ($schema);
}
out += ' items\' ';
}
if (it.opts.verbose) {
out += ' , schema: ';
if ($isData) {
out += 'validate.schema' + ($schemaPath);
} else {
out += '' + ($schema);
}
out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
}
out += ' } ';
} else {
out += ' {} ';
}
var __err = out;
out = $$outStack.pop();
if (!it.compositeRule && $breakOnError) {
/* istanbul ignore if */
if (it.async) {
out += ' throw new ValidationError([' + (__err) + ']); ';
} else {
out += ' validate.errors = [' + (__err) + ']; return false; ';
}
} else {
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
}
out += '} ';
if ($breakOnError) {
out += ' else { ';
}
return out;
}

View File

@@ -1,85 +0,0 @@
'use strict';
module.exports = function generate__limitLength(it, $keyword, $ruleType) {
var out = ' ';
var $lvl = it.level;
var $dataLvl = it.dataLevel;
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $errorKeyword;
var $data = 'data' + ($dataLvl || '');
var $isData = it.opts.$data && $schema && $schema.$data,
$schemaValue;
if ($isData) {
out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; ';
$schemaValue = 'schema' + $lvl;
} else {
$schemaValue = $schema;
}
if (!($isData || typeof $schema == 'number')) {
throw new Error($keyword + ' must be number');
}
var $op = $keyword == 'maxLength' ? '>' : '<';
out += 'if ( ';
if ($isData) {
out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
}
if (it.opts.unicode === false) {
out += ' ' + ($data) + '.length ';
} else {
out += ' ucs2length(' + ($data) + ') ';
}
out += ' ' + ($op) + ' ' + ($schemaValue) + ') { ';
var $errorKeyword = $keyword;
var $$outStack = $$outStack || [];
$$outStack.push(out);
out = ''; /* istanbul ignore else */
if (it.createErrors !== false) {
out += ' { keyword: \'' + ($errorKeyword || '_limitLength') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } ';
if (it.opts.messages !== false) {
out += ' , message: \'should NOT be ';
if ($keyword == 'maxLength') {
out += 'longer';
} else {
out += 'shorter';
}
out += ' than ';
if ($isData) {
out += '\' + ' + ($schemaValue) + ' + \'';
} else {
out += '' + ($schema);
}
out += ' characters\' ';
}
if (it.opts.verbose) {
out += ' , schema: ';
if ($isData) {
out += 'validate.schema' + ($schemaPath);
} else {
out += '' + ($schema);
}
out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
}
out += ' } ';
} else {
out += ' {} ';
}
var __err = out;
out = $$outStack.pop();
if (!it.compositeRule && $breakOnError) {
/* istanbul ignore if */
if (it.async) {
out += ' throw new ValidationError([' + (__err) + ']); ';
} else {
out += ' validate.errors = [' + (__err) + ']; return false; ';
}
} else {
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
}
out += '} ';
if ($breakOnError) {
out += ' else { ';
}
return out;
}

View File

@@ -1,80 +0,0 @@
'use strict';
module.exports = function generate__limitProperties(it, $keyword, $ruleType) {
var out = ' ';
var $lvl = it.level;
var $dataLvl = it.dataLevel;
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $errorKeyword;
var $data = 'data' + ($dataLvl || '');
var $isData = it.opts.$data && $schema && $schema.$data,
$schemaValue;
if ($isData) {
out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; ';
$schemaValue = 'schema' + $lvl;
} else {
$schemaValue = $schema;
}
if (!($isData || typeof $schema == 'number')) {
throw new Error($keyword + ' must be number');
}
var $op = $keyword == 'maxProperties' ? '>' : '<';
out += 'if ( ';
if ($isData) {
out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
}
out += ' Object.keys(' + ($data) + ').length ' + ($op) + ' ' + ($schemaValue) + ') { ';
var $errorKeyword = $keyword;
var $$outStack = $$outStack || [];
$$outStack.push(out);
out = ''; /* istanbul ignore else */
if (it.createErrors !== false) {
out += ' { keyword: \'' + ($errorKeyword || '_limitProperties') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } ';
if (it.opts.messages !== false) {
out += ' , message: \'should NOT have ';
if ($keyword == 'maxProperties') {
out += 'more';
} else {
out += 'fewer';
}
out += ' than ';
if ($isData) {
out += '\' + ' + ($schemaValue) + ' + \'';
} else {
out += '' + ($schema);
}
out += ' properties\' ';
}
if (it.opts.verbose) {
out += ' , schema: ';
if ($isData) {
out += 'validate.schema' + ($schemaPath);
} else {
out += '' + ($schema);
}
out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
}
out += ' } ';
} else {
out += ' {} ';
}
var __err = out;
out = $$outStack.pop();
if (!it.compositeRule && $breakOnError) {
/* istanbul ignore if */
if (it.async) {
out += ' throw new ValidationError([' + (__err) + ']); ';
} else {
out += ' validate.errors = [' + (__err) + ']; return false; ';
}
} else {
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
}
out += '} ';
if ($breakOnError) {
out += ' else { ';
}
return out;
}

View File

@@ -1,42 +0,0 @@
'use strict';
module.exports = function generate_allOf(it, $keyword, $ruleType) {
var out = ' ';
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $it = it.util.copy(it);
var $closingBraces = '';
$it.level++;
var $nextValid = 'valid' + $it.level;
var $currentBaseId = $it.baseId,
$allSchemasEmpty = true;
var arr1 = $schema;
if (arr1) {
var $sch, $i = -1,
l1 = arr1.length - 1;
while ($i < l1) {
$sch = arr1[$i += 1];
if ((it.opts.strictKeywords ? (typeof $sch == 'object' && Object.keys($sch).length > 0) || $sch === false : it.util.schemaHasRules($sch, it.RULES.all))) {
$allSchemasEmpty = false;
$it.schema = $sch;
$it.schemaPath = $schemaPath + '[' + $i + ']';
$it.errSchemaPath = $errSchemaPath + '/' + $i;
out += ' ' + (it.validate($it)) + ' ';
$it.baseId = $currentBaseId;
if ($breakOnError) {
out += ' if (' + ($nextValid) + ') { ';
$closingBraces += '}';
}
}
}
}
if ($breakOnError) {
if ($allSchemasEmpty) {
out += ' if (true) { ';
} else {
out += ' ' + ($closingBraces.slice(0, -1)) + ' ';
}
}
return out;
}

View File

@@ -1,73 +0,0 @@
'use strict';
module.exports = function generate_anyOf(it, $keyword, $ruleType) {
var out = ' ';
var $lvl = it.level;
var $dataLvl = it.dataLevel;
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $data = 'data' + ($dataLvl || '');
var $valid = 'valid' + $lvl;
var $errs = 'errs__' + $lvl;
var $it = it.util.copy(it);
var $closingBraces = '';
$it.level++;
var $nextValid = 'valid' + $it.level;
var $noEmptySchema = $schema.every(function($sch) {
return (it.opts.strictKeywords ? (typeof $sch == 'object' && Object.keys($sch).length > 0) || $sch === false : it.util.schemaHasRules($sch, it.RULES.all));
});
if ($noEmptySchema) {
var $currentBaseId = $it.baseId;
out += ' var ' + ($errs) + ' = errors; var ' + ($valid) + ' = false; ';
var $wasComposite = it.compositeRule;
it.compositeRule = $it.compositeRule = true;
var arr1 = $schema;
if (arr1) {
var $sch, $i = -1,
l1 = arr1.length - 1;
while ($i < l1) {
$sch = arr1[$i += 1];
$it.schema = $sch;
$it.schemaPath = $schemaPath + '[' + $i + ']';
$it.errSchemaPath = $errSchemaPath + '/' + $i;
out += ' ' + (it.validate($it)) + ' ';
$it.baseId = $currentBaseId;
out += ' ' + ($valid) + ' = ' + ($valid) + ' || ' + ($nextValid) + '; if (!' + ($valid) + ') { ';
$closingBraces += '}';
}
}
it.compositeRule = $it.compositeRule = $wasComposite;
out += ' ' + ($closingBraces) + ' if (!' + ($valid) + ') { var err = '; /* istanbul ignore else */
if (it.createErrors !== false) {
out += ' { keyword: \'' + ('anyOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} ';
if (it.opts.messages !== false) {
out += ' , message: \'should match some schema in anyOf\' ';
}
if (it.opts.verbose) {
out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
}
out += ' } ';
} else {
out += ' {} ';
}
out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
if (!it.compositeRule && $breakOnError) {
/* istanbul ignore if */
if (it.async) {
out += ' throw new ValidationError(vErrors); ';
} else {
out += ' validate.errors = vErrors; return false; ';
}
}
out += ' } else { errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } ';
if (it.opts.allErrors) {
out += ' } ';
}
} else {
if ($breakOnError) {
out += ' if (true) { ';
}
}
return out;
}

View File

@@ -1,14 +0,0 @@
'use strict';
module.exports = function generate_comment(it, $keyword, $ruleType) {
var out = ' ';
var $schema = it.schema[$keyword];
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $comment = it.util.toQuotedString($schema);
if (it.opts.$comment === true) {
out += ' console.log(' + ($comment) + ');';
} else if (typeof it.opts.$comment == 'function') {
out += ' self._opts.$comment(' + ($comment) + ', ' + (it.util.toQuotedString($errSchemaPath)) + ', validate.root.schema);';
}
return out;
}

Some files were not shown because too many files have changed in this diff Show More