From c86a4c534778faba0d9e68f98a405b0cafb01931 Mon Sep 17 00:00:00 2001 From: Twan Kamp Date: Thu, 11 Apr 2024 17:15:30 -0700 Subject: [PATCH 1/3] feat(json-schema-2020-12-samples): support randomness option --- .../fn/api/optionAPI.js | 2 + .../fn/class/OptionRegistry.js | 12 ++- .../fn/class/Registry.js | 6 ++ .../fn/core/random.js | 34 ++++--- .../fn/generators/date-time.js | 4 +- .../fn/generators/date.js | 4 +- .../fn/generators/time.js | 4 +- .../fn/types/boolean.js | 3 +- .../plugins/json-schema-2020-12-samples/fn.js | 90 +++++++++++++++++++ 9 files changed, 144 insertions(+), 15 deletions(-) diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/api/optionAPI.js b/src/core/plugins/json-schema-2020-12-samples/fn/api/optionAPI.js index 0b766198e46..fdd9a2234b0 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/api/optionAPI.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/api/optionAPI.js @@ -13,5 +13,7 @@ const optionAPI = (optionName, optionValue) => { return registry.get(optionName) } +optionAPI.getDefaults = () => registry.defaults +optionAPI.setDefaults = () => registry.registerMany(registry.defaults) export default optionAPI diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js b/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js index cad83bb1b52..580c53558eb 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js @@ -4,7 +4,17 @@ import Registry from "./Registry" class OptionRegistry extends Registry { - #defaults = {} + #defaults = { + randomEnabled: false, + random: Math.random, + minInt: 0, + maxInt: 2147483647, + minLen: 4, + maxLen: 10, + minDateTime: new Date("1970-01-01T00:00:00.000Z"), + maxDateTime: new Date("2038-01-19T00:00:00.000Z"), + maxRandExp: 100, + } data = { ...this.#defaults } diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js b/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js index e47f95074a4..819413a7d99 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js @@ -8,6 +8,12 @@ class Registry { this.data[name] = value } + registerMany(nameValueMap) { + Object.keys(nameValueMap).forEach(name => { + this.data[name] = nameValueMap[name] + }) + } + unregister(name) { if (typeof name === "undefined") { this.data = {} diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js b/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js index 6a707639cba..feee8aa6724 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js @@ -3,19 +3,22 @@ */ import randomBytes from "randombytes" import RandExp from "randexp" +import optionAPI from "../api/optionAPI" /** - * Some of the functions returns constants. This is due to the nature - * of SwaggerUI expectations - provide as stable data as possible. + * Most of the functions return constants by default unless randomness option is enabled. + * This is due to the nature of SwaggerUI expectations - provide as stable data as possible. * - * In future, we may decide to randomize these function and provide - * true random values. */ +export const randInt = (min, max) => min + Math.floor(optionAPI("random")() * (1 + (max - min))) export const bytes = (length) => randomBytes(length) export const randexp = (pattern) => { try { + RandExp.prototype.max = optionAPI("maxRandExp") + RandExp.prototype.randInt = randInt + const randexpInstance = new RandExp(pattern) return randexpInstance.gen() } catch { @@ -24,12 +27,23 @@ export const randexp = (pattern) => { } } -export const pick = (list) => { - return list.at(0) -} +export const pick = (list) => optionAPI("randomEnabled") ? list[Math.floor(optionAPI("random")() * list.length)] : list.at(0) + +export const string = () => optionAPI("randomEnabled") ? randexp(`[a-z]{${optionAPI("minLen")},${optionAPI("maxLen")}}`) : "string" + +export const integer = () => optionAPI("randomEnabled") ? randInt(optionAPI("minInt"), optionAPI("maxInt")) : 0 -export const string = () => "string" +export const number = () => integer() + +export const boolean = () => optionAPI("randomEnabled") ? optionAPI("random")() > 0.5 : true + +export const date = () => { + if (!optionAPI("randomEnabled")) { + return new Date() + } -export const number = () => 0 + let earliest = new Date(optionAPI("minDateTime")) + let latest = new Date(optionAPI("maxDateTime")) -export const integer = () => 0 + return new Date(randInt(earliest.getTime(), latest.getTime())) +} \ No newline at end of file diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/generators/date-time.js b/src/core/plugins/json-schema-2020-12-samples/fn/generators/date-time.js index 30cee3a3a2d..03e837370d8 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/generators/date-time.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/generators/date-time.js @@ -1,6 +1,8 @@ /** * @prettier */ -const dateTimeGenerator = () => new Date().toISOString() +import { date as randomDate } from "../core/random" + +const dateTimeGenerator = () => randomDate().toISOString() export default dateTimeGenerator diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/generators/date.js b/src/core/plugins/json-schema-2020-12-samples/fn/generators/date.js index a1c217dbe50..7274dca6509 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/generators/date.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/generators/date.js @@ -1,6 +1,8 @@ /** * @prettier */ -const dateGenerator = () => new Date().toISOString().substring(0, 10) +import { date as randomDate } from "../core/random" + +const dateGenerator = () => randomDate().toISOString().substring(0, 10) export default dateGenerator diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/generators/time.js b/src/core/plugins/json-schema-2020-12-samples/fn/generators/time.js index 5b583fe7f3e..31cf13c3a5a 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/generators/time.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/generators/time.js @@ -1,6 +1,8 @@ /** * @prettier */ -const timeGenerator = () => new Date().toISOString().substring(11) +import { date as randomDate } from "../core/random" + +const timeGenerator = () => randomDate().toISOString().substring(11) export default timeGenerator diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/types/boolean.js b/src/core/plugins/json-schema-2020-12-samples/fn/types/boolean.js index e506215ff9f..fdfbe985be5 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/types/boolean.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/types/boolean.js @@ -1,9 +1,10 @@ /** * @prettier */ +import { boolean as randomBoolean } from "../core/random" const booleanType = (schema) => { - return typeof schema.default === "boolean" ? schema.default : true + return typeof schema.default === "boolean" ? schema.default : randomBoolean() } export default booleanType diff --git a/test/unit/core/plugins/json-schema-2020-12-samples/fn.js b/test/unit/core/plugins/json-schema-2020-12-samples/fn.js index b9a51d6d1c9..9d7070cabf5 100644 --- a/test/unit/core/plugins/json-schema-2020-12-samples/fn.js +++ b/test/unit/core/plugins/json-schema-2020-12-samples/fn.js @@ -10,8 +10,11 @@ import { memoizedSampleFromSchema, mergeJsonSchema, } from "core/plugins/json-schema-2020-12-samples/fn" +import optionAPI from "core/plugins/json-schema-2020-12-samples/fn/api/optionAPI" describe("sampleFromSchema", () => { + beforeEach(() => optionAPI.setDefaults()) + it("should return appropriate example for primitive types + format", function () { const sample = (schema) => sampleFromSchema(fromJS(schema)) @@ -89,6 +92,83 @@ describe("sampleFromSchema", () => { expect(sample({ type: "null" })).toStrictEqual(null) }) + it("should return appropriate example for primitive types + format with randomness enabled", function () { + optionAPI("randomEnabled", true) + optionAPI("random", () => 0) + optionAPI("minInt", 4) + optionAPI("minLen", 6) + optionAPI("minDateTime", "2024-01-01T00:00:00.000Z") + + const sample = (schema) => sampleFromSchema(fromJS(schema)) + + expect(sample({ type: "string" })).toStrictEqual("aaaaaa") + expect(sample({ type: "string", pattern: "^abc$" })).toStrictEqual("abc") + expect(sample({ type: "string", format: "email" })).toStrictEqual( + "user@example.com" + ) + expect(sample({ type: "string", format: "idn-email" })).toStrictEqual( + "실례@example.com" + ) + expect(sample({ type: "string", format: "hostname" })).toStrictEqual( + "example.com" + ) + expect(sample({ type: "string", format: "idn-hostname" })).toStrictEqual( + "실례.com" + ) + expect(sample({ type: "string", format: "ipv4" })).toStrictEqual( + "198.51.100.42" + ) + expect(sample({ type: "string", format: "ipv6" })).toStrictEqual( + "2001:0db8:5b96:0000:0000:426f:8e17:642a" + ) + expect(sample({ type: "string", format: "uri" })).toStrictEqual( + "https://example.com/" + ) + expect(sample({ type: "string", format: "uri-reference" })).toStrictEqual( + "path/index.html" + ) + expect(sample({ type: "string", format: "iri" })).toStrictEqual( + "https://실례.com/" + ) + expect(sample({ type: "string", format: "iri-reference" })).toStrictEqual( + "path/실례.html" + ) + expect(sample({ type: "string", format: "uuid" })).toStrictEqual( + "3fa85f64-5717-4562-b3fc-2c963f66afa6" + ) + expect(sample({ type: "string", format: "uri-template" })).toStrictEqual( + "https://example.com/dictionary/{term:1}/{term}" + ) + expect(sample({ type: "string", format: "json-pointer" })).toStrictEqual( + "/a/b/c" + ) + expect( + sample({ type: "string", format: "relative-json-pointer" }) + ).toStrictEqual("1/0") + expect(sample({ type: "string", format: "date-time" })).toStrictEqual("2024-01-01T00:00:00.000Z") + expect(sample({ type: "string", format: "date" })).toStrictEqual("2024-01-01") + expect(sample({ type: "string", format: "time" })).toStrictEqual("00:00:00.000Z") + expect(sample({ type: "string", format: "duration" })).toStrictEqual("P3D") + expect(sample({ type: "string", format: "password" })).toStrictEqual( + "********" + ) + expect(sample({ type: "string", format: "regex" })).toStrictEqual( + "^[a-z]+$" + ) + expect(sample({ type: "number" })).toStrictEqual(4) + expect(sample({ type: "number", format: "float" })).toStrictEqual(0.1) + expect(sample({ type: "number", format: "double" })).toStrictEqual(0.1) + expect(sample({ type: "integer" })).toStrictEqual(4) + expect(sample({ type: "integer", format: "int32" })).toStrictEqual( + (2 ** 30) >>> 0 + ) + expect(sample({ type: "integer", format: "int64" })).toStrictEqual( + 2 ** 53 - 1 + ) + expect(sample({ type: "boolean" })).toStrictEqual(false) + expect(sample({ type: "null" })).toStrictEqual(null) + }) + it("should return appropriate example given contentEncoding", function () { const sample = (schema) => sampleFromSchema(fromJS(schema)) @@ -420,6 +500,16 @@ describe("sampleFromSchema", () => { ) }) + it("should return random enum value when randomness is enabled", function () { + optionAPI("randomEnabled", true) + optionAPI("random", () => 0.5) + + const definition = fromJS({enum: ["a", "b", "c"]}) + const expected = "b" + + expect(sampleFromSchema(definition)).toStrictEqual(expected) + }) + it("combine first oneOf or anyOf with schema's definitions", function () { let definition = { type: "object", From 7ceb2ca709e2d908085d680e84cd0fcf0676d340 Mon Sep 17 00:00:00 2001 From: Twan Kamp Date: Fri, 12 Apr 2024 10:45:12 -0700 Subject: [PATCH 2/3] feat(json-schema-2020-12-samples): implement review comments --- .../fn/class/OptionRegistry.js | 12 +++++------ .../fn/core/random.js | 20 ++++++++++--------- .../plugins/json-schema-2020-12-samples/fn.js | 8 ++++---- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js b/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js index 580c53558eb..ebebe294eb1 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/class/OptionRegistry.js @@ -5,15 +5,15 @@ import Registry from "./Registry" class OptionRegistry extends Registry { #defaults = { - randomEnabled: false, + mode: "constant", random: Math.random, - minInt: 0, - maxInt: 2147483647, - minLen: 4, - maxLen: 10, + minInteger: -100000000, + maxInteger: +100000000, + minLength: 4, + maxLength: 10, minDateTime: new Date("1970-01-01T00:00:00.000Z"), maxDateTime: new Date("2038-01-19T00:00:00.000Z"), - maxRandExp: 100, + defaultRandExpMax: 10, } data = { ...this.#defaults } diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js b/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js index feee8aa6724..071025f9930 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/core/random.js @@ -10,13 +10,15 @@ import optionAPI from "../api/optionAPI" * This is due to the nature of SwaggerUI expectations - provide as stable data as possible. * */ -export const randInt = (min, max) => min + Math.floor(optionAPI("random")() * (1 + (max - min))) +const randomMode = () => optionAPI("mode") === "random" + +const randInt = (min, max) => min + Math.floor(optionAPI("random")() * (1 + (max - min))) export const bytes = (length) => randomBytes(length) export const randexp = (pattern) => { try { - RandExp.prototype.max = optionAPI("maxRandExp") + RandExp.prototype.max = optionAPI("defaultRandExpMax") RandExp.prototype.randInt = randInt const randexpInstance = new RandExp(pattern) @@ -27,23 +29,23 @@ export const randexp = (pattern) => { } } -export const pick = (list) => optionAPI("randomEnabled") ? list[Math.floor(optionAPI("random")() * list.length)] : list.at(0) +export const pick = (list) => randomMode() ? list[Math.floor(optionAPI("random")() * list.length)] : list.at(0) -export const string = () => optionAPI("randomEnabled") ? randexp(`[a-z]{${optionAPI("minLen")},${optionAPI("maxLen")}}`) : "string" +export const string = () => randomMode() ? randexp(`[a-z]{${optionAPI("minLength")},${optionAPI("maxLength")}}`) : "string" -export const integer = () => optionAPI("randomEnabled") ? randInt(optionAPI("minInt"), optionAPI("maxInt")) : 0 +export const integer = () => randomMode() ? randInt(optionAPI("minInteger"), optionAPI("maxInteger")) : 0 export const number = () => integer() -export const boolean = () => optionAPI("randomEnabled") ? optionAPI("random")() > 0.5 : true +export const boolean = () => randomMode() ? optionAPI("random")() > 0.5 : true export const date = () => { - if (!optionAPI("randomEnabled")) { + if (!randomMode()) { return new Date() } - let earliest = new Date(optionAPI("minDateTime")) - let latest = new Date(optionAPI("maxDateTime")) + const earliest = new Date(optionAPI("minDateTime")) + const latest = new Date(optionAPI("maxDateTime")) return new Date(randInt(earliest.getTime(), latest.getTime())) } \ No newline at end of file diff --git a/test/unit/core/plugins/json-schema-2020-12-samples/fn.js b/test/unit/core/plugins/json-schema-2020-12-samples/fn.js index 9d7070cabf5..108f8c8b6ce 100644 --- a/test/unit/core/plugins/json-schema-2020-12-samples/fn.js +++ b/test/unit/core/plugins/json-schema-2020-12-samples/fn.js @@ -93,10 +93,10 @@ describe("sampleFromSchema", () => { }) it("should return appropriate example for primitive types + format with randomness enabled", function () { - optionAPI("randomEnabled", true) + optionAPI("mode", "random") optionAPI("random", () => 0) - optionAPI("minInt", 4) - optionAPI("minLen", 6) + optionAPI("minInteger", 4) + optionAPI("minLength", 6) optionAPI("minDateTime", "2024-01-01T00:00:00.000Z") const sample = (schema) => sampleFromSchema(fromJS(schema)) @@ -501,7 +501,7 @@ describe("sampleFromSchema", () => { }) it("should return random enum value when randomness is enabled", function () { - optionAPI("randomEnabled", true) + optionAPI("mode", "random") optionAPI("random", () => 0.5) const definition = fromJS({enum: ["a", "b", "c"]}) From 0061436f407de46fa378d73c20a0327a1aa39d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Tue, 30 Apr 2024 13:31:33 +0200 Subject: [PATCH 3/3] Update Registry.js --- .../plugins/json-schema-2020-12-samples/fn/class/Registry.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js b/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js index 819413a7d99..925f0541812 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/class/Registry.js @@ -9,9 +9,7 @@ class Registry { } registerMany(nameValueMap) { - Object.keys(nameValueMap).forEach(name => { - this.data[name] = nameValueMap[name] - }) + Object.assign(this.data, nameValueMap) } unregister(name) {