@openapi-flow/core 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { OasValueValidationCache, OasValueValidationCacheOptions } from '../../types/oas-value-validation.js';
|
|
2
|
+
/** Cache compilation only; every supplied value still runs the original validator. */
|
|
3
|
+
export declare function createOasValueValidationCache(options: OasValueValidationCacheOptions): OasValueValidationCache;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
/** Cache compilation only; every supplied value still runs the original validator. */
|
|
4
|
+
export function createOasValueValidationCache(options) {
|
|
5
|
+
for (const name of ['maximumEntries', 'maximumRetainedSchemaBytes'])
|
|
6
|
+
if (!Number.isSafeInteger(options[name]) || options[name] <= 0)
|
|
7
|
+
throw new Error(`OAS validator cache.${name} must be a positive safe integer`);
|
|
8
|
+
const { maximumEntries, maximumRetainedSchemaBytes } = options;
|
|
9
|
+
const entries = new Map();
|
|
10
|
+
let retainedSchemaBytes = 0;
|
|
11
|
+
return {
|
|
12
|
+
getOrCreate: (schema, dialect, compile) => {
|
|
13
|
+
const key = JSON.stringify([dialect, schema]);
|
|
14
|
+
const existing = entries.get(key);
|
|
15
|
+
// Serialization alone is not identity: undefined and non-JSON values can collide.
|
|
16
|
+
if (existing !== undefined &&
|
|
17
|
+
isDeepStrictEqual(existing.schema, schema)) {
|
|
18
|
+
entries.delete(key);
|
|
19
|
+
entries.set(key, existing);
|
|
20
|
+
return existing.validate;
|
|
21
|
+
}
|
|
22
|
+
// Neither returned schemas nor caller-owned objects can mutate the compiled resource.
|
|
23
|
+
const snapshot = structuredClone(schema);
|
|
24
|
+
const validate = compile(snapshot);
|
|
25
|
+
const bytes = Buffer.byteLength(key);
|
|
26
|
+
// Large schemas remain supported; only their optional retention is bounded.
|
|
27
|
+
if (bytes > maximumRetainedSchemaBytes)
|
|
28
|
+
return validate;
|
|
29
|
+
if (existing !== undefined) {
|
|
30
|
+
entries.delete(key);
|
|
31
|
+
retainedSchemaBytes -= existing.bytes;
|
|
32
|
+
}
|
|
33
|
+
while (entries.size >= maximumEntries ||
|
|
34
|
+
retainedSchemaBytes + bytes > maximumRetainedSchemaBytes) {
|
|
35
|
+
const oldest = entries.entries().next();
|
|
36
|
+
if (oldest.done)
|
|
37
|
+
throw new Error('OAS validator cache retained-size invariant is invalid');
|
|
38
|
+
entries.delete(oldest.value[0]);
|
|
39
|
+
retainedSchemaBytes -= oldest.value[1].bytes;
|
|
40
|
+
}
|
|
41
|
+
entries.set(key, { schema: snapshot, validate, bytes });
|
|
42
|
+
retainedSchemaBytes += bytes;
|
|
43
|
+
return validate;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -2,6 +2,12 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { createValidator } from '@scalar/json-schema-validator';
|
|
3
3
|
import { normalizeOasValueSchema } from './normalize-oas-value-schema.js';
|
|
4
4
|
import { isObject } from '../../utils/is-object.js';
|
|
5
|
+
import { createOasValueValidationCache } from './create-oas-value-validation-cache.js';
|
|
6
|
+
// Internal retention budgets, not limits on accepted OAS contracts or request values.
|
|
7
|
+
const compiledValidators = createOasValueValidationCache({
|
|
8
|
+
maximumEntries: 64,
|
|
9
|
+
maximumRetainedSchemaBytes: 1024 * 1024,
|
|
10
|
+
});
|
|
5
11
|
export function createOasValueValidator(input, dialect, source) {
|
|
6
12
|
if (typeof input !== 'boolean' && !isObject(input))
|
|
7
13
|
throw new Error(`${source} must be an OAS schema object or boolean`);
|
|
@@ -24,7 +30,11 @@ export function createOasValueValidator(input, dialect, source) {
|
|
|
24
30
|
};
|
|
25
31
|
let validate;
|
|
26
32
|
try {
|
|
27
|
-
validate =
|
|
33
|
+
validate = compiledValidators.getOrCreate(schema, dialect, (snapshot) => {
|
|
34
|
+
const compiled = createValidator(snapshot);
|
|
35
|
+
// Scalar parses string documents as YAML; encode actual string values first.
|
|
36
|
+
return (value) => compiled(typeof value === 'string' ? JSON.stringify(value) : value);
|
|
37
|
+
});
|
|
28
38
|
}
|
|
29
39
|
catch (error) {
|
|
30
40
|
throw new Error(`${source} cannot compile its OAS value schema`, {
|
|
@@ -33,7 +43,6 @@ export function createOasValueValidator(input, dialect, source) {
|
|
|
33
43
|
}
|
|
34
44
|
return {
|
|
35
45
|
schema,
|
|
36
|
-
|
|
37
|
-
validate: (value) => validate(typeof value === 'string' ? JSON.stringify(value) : value),
|
|
46
|
+
validate,
|
|
38
47
|
};
|
|
39
48
|
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import type { createValidator } from '@scalar/json-schema-validator';
|
|
2
2
|
import type { JsonObject } from './openapi.js';
|
|
3
|
+
import type { SchemaDialect } from './schema-dialect.js';
|
|
3
4
|
export type OasValueSchema = JsonObject | boolean;
|
|
4
5
|
export type OasValueValidation = ReturnType<typeof createValidator>;
|
|
5
6
|
export interface OasValueValidator {
|
|
6
7
|
schema: JsonObject;
|
|
7
8
|
validate: OasValueValidation;
|
|
8
9
|
}
|
|
10
|
+
export interface OasValueValidationCacheOptions {
|
|
11
|
+
maximumEntries: number;
|
|
12
|
+
maximumRetainedSchemaBytes: number;
|
|
13
|
+
}
|
|
14
|
+
export interface OasValueValidationCacheEntry {
|
|
15
|
+
schema: JsonObject;
|
|
16
|
+
validate: OasValueValidation;
|
|
17
|
+
bytes: number;
|
|
18
|
+
}
|
|
19
|
+
export interface OasValueValidationCache {
|
|
20
|
+
getOrCreate(schema: JsonObject, dialect: SchemaDialect, compile: (schema: JsonObject) => OasValueValidation): OasValueValidation;
|
|
21
|
+
}
|