@schmock/faker 2.1.4 → 2.1.6
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-name-matcher.d.ts","sourceRoot":"","sources":["../src/field-name-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAmBxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAsC5E;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,EACnB,QAAQ,GAAE,YAAY,EAAuB,GAC5C,WAAW,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"field-name-matcher.d.ts","sourceRoot":"","sources":["../src/field-name-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAmBxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAsC5E;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,EACnB,QAAQ,GAAE,YAAY,EAAuB,GAC5C,WAAW,GAAG,SAAS,CA6FzB"}
|
|
@@ -85,10 +85,16 @@ export function findBestMapping(fieldName, schema, mappings = ALL_FIELD_MAPPINGS
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
// Skip if schema already has pattern, enum, faker, or $ref constraint
|
|
88
|
+
// Also skip string mappings when minLength/maxLength is set — name-based
|
|
89
|
+
// mappings (e.g. lorem.word for "label") don't honor JSON Schema length
|
|
90
|
+
// constraints, so they'd produce out-of-range strings ~20% of the time.
|
|
91
|
+
// Mirrors the numeric constraint skip just below.
|
|
88
92
|
if (schemaAny.pattern ||
|
|
89
93
|
schemaAny.enum ||
|
|
90
94
|
schemaAny.faker ||
|
|
91
|
-
schemaAny.$ref
|
|
95
|
+
schemaAny.$ref ||
|
|
96
|
+
(schemaType === "string" &&
|
|
97
|
+
(schema.minLength !== undefined || schema.maxLength !== undefined))) {
|
|
92
98
|
return undefined;
|
|
93
99
|
}
|
|
94
100
|
// Skip numeric faker mapping when schema already constrains the range
|
package/dist/index.js
CHANGED
|
@@ -1,122 +1 @@
|
|
|
1
|
-
/// <reference path="../../core/schmock.d.ts" />
|
|
2
|
-
import { ResourceLimitError, SchemaGenerationError, SchemaValidationError, } from "@schmock/core";
|
|
3
|
-
import { version as packageVersion } from "../package.json";
|
|
4
|
-
import { MAX_ARRAY_SIZE, NULLABLE_NULL_PROBABILITY } from "./constants.js";
|
|
5
|
-
import { generateWithJsf } from "./jsf-config.js";
|
|
6
|
-
import { applyOverrides, determineArrayCount } from "./overrides.js";
|
|
7
|
-
import { enhanceSchemaWithSmartMapping } from "./schema-enhancement.js";
|
|
8
|
-
import { isJSONSchema7, validateSchema } from "./validation.js";
|
|
9
|
-
export function fakerPlugin(options) {
|
|
10
|
-
// Validate schema immediately when plugin is created (fail-fast)
|
|
11
|
-
validateSchema(options.schema);
|
|
12
|
-
return {
|
|
13
|
-
name: "faker",
|
|
14
|
-
version: packageVersion,
|
|
15
|
-
async process(context, response) {
|
|
16
|
-
// If response already exists, pass it through
|
|
17
|
-
if (response !== undefined && response !== null) {
|
|
18
|
-
return { context, response };
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
const generatedResponse = await generateFromSchema({
|
|
22
|
-
schema: options.schema,
|
|
23
|
-
count: options.count,
|
|
24
|
-
overrides: options.overrides,
|
|
25
|
-
params: context.params,
|
|
26
|
-
state: context.routeState,
|
|
27
|
-
query: context.query,
|
|
28
|
-
seed: options.seed,
|
|
29
|
-
});
|
|
30
|
-
return {
|
|
31
|
-
context,
|
|
32
|
-
response: generatedResponse,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
// Re-throw schema-specific errors as-is
|
|
37
|
-
if (error instanceof SchemaValidationError ||
|
|
38
|
-
error instanceof ResourceLimitError) {
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
// Wrap other errors
|
|
42
|
-
throw new SchemaGenerationError(context.path, error instanceof Error ? error : new Error(String(error)), options.schema);
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
export async function generateFromSchema(options) {
|
|
48
|
-
const { schema, count, overrides, params, state, query, seed } = options;
|
|
49
|
-
validateSchema(schema);
|
|
50
|
-
// Handle array schemas with count
|
|
51
|
-
if (schema.type === "array" && schema.items) {
|
|
52
|
-
const itemCount = determineArrayCount(schema, count);
|
|
53
|
-
// Check for resource limits
|
|
54
|
-
if (itemCount > MAX_ARRAY_SIZE) {
|
|
55
|
-
throw new ResourceLimitError("array_size", MAX_ARRAY_SIZE, itemCount);
|
|
56
|
-
}
|
|
57
|
-
const rawItemSchema = Array.isArray(schema.items)
|
|
58
|
-
? schema.items[0]
|
|
59
|
-
: schema.items;
|
|
60
|
-
if (!rawItemSchema || typeof rawItemSchema === "boolean") {
|
|
61
|
-
throw new SchemaValidationError("$.items", "Array schema must have valid items definition");
|
|
62
|
-
}
|
|
63
|
-
const itemSchema = rawItemSchema;
|
|
64
|
-
const enhancedItemSchema = enhanceSchemaWithSmartMapping(itemSchema);
|
|
65
|
-
const items = [];
|
|
66
|
-
for (let i = 0; i < itemCount; i++) {
|
|
67
|
-
let item = await generateWithJsf(enhancedItemSchema, seed);
|
|
68
|
-
item = postProcessGenerated(item, enhancedItemSchema);
|
|
69
|
-
item = applyOverrides(item, overrides, params, state, query);
|
|
70
|
-
items.push(item);
|
|
71
|
-
}
|
|
72
|
-
return items;
|
|
73
|
-
}
|
|
74
|
-
// Handle object schemas
|
|
75
|
-
const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
|
|
76
|
-
let generated = await generateWithJsf(enhancedSchema, seed);
|
|
77
|
-
generated = postProcessGenerated(generated, enhancedSchema);
|
|
78
|
-
generated = applyOverrides(generated, overrides, params, state, query);
|
|
79
|
-
return generated;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Post-process generated data to apply nullable probability and boolean weighting.
|
|
83
|
-
* Walks the schema and generated data in parallel, applying:
|
|
84
|
-
* - schmockNullable: ~5% chance of null
|
|
85
|
-
* - schmockTrueProbability: weighted boolean generation
|
|
86
|
-
*/
|
|
87
|
-
function postProcessGenerated(data, schema) {
|
|
88
|
-
if (data === null ||
|
|
89
|
-
data === undefined ||
|
|
90
|
-
!schema ||
|
|
91
|
-
typeof schema !== "object") {
|
|
92
|
-
return data;
|
|
93
|
-
}
|
|
94
|
-
const schemaExt = schema;
|
|
95
|
-
// Apply nullable probability at this level
|
|
96
|
-
if (schemaExt.schmockNullable === true) {
|
|
97
|
-
if (Math.random() < NULLABLE_NULL_PROBABILITY) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Apply boolean weighting at this level
|
|
102
|
-
if (schema.type === "boolean" &&
|
|
103
|
-
typeof schemaExt.schmockTrueProbability === "number") {
|
|
104
|
-
return Math.random() < schemaExt.schmockTrueProbability;
|
|
105
|
-
}
|
|
106
|
-
// Recurse into object properties
|
|
107
|
-
if (typeof data === "object" && !Array.isArray(data) && schema.properties) {
|
|
108
|
-
const record = data;
|
|
109
|
-
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
110
|
-
if (key in record && isJSONSchema7(propSchema)) {
|
|
111
|
-
record[key] = postProcessGenerated(record[key], propSchema);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Recurse into array items
|
|
116
|
-
if (Array.isArray(data) && schema.items && isJSONSchema7(schema.items)) {
|
|
117
|
-
for (let i = 0; i < data.length; i++) {
|
|
118
|
-
data[i] = postProcessGenerated(data[i], schema.items);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return data;
|
|
122
|
-
}
|
|
1
|
+
import{ResourceLimitError as o,SchemaGenerationError as v1,SchemaValidationError as n}from"@schmock/core";var k="2.1.6";var Y=1e4,M=10,V=3,F=3,H=100,E=0.05;import{base as m,en as s,Faker as h}from"@faker-js/faker";import{generate as c}from"json-schema-faker";function g(Q){let $=new h({locale:[s,m]});if(Q!==void 0)$.seed(Q);return $}async function x(Q,$){let Z=$??Math.floor(Math.random()*2147483647);return c(Q,{seed:Z,optionalsProbability:1,alwaysFakeOptionals:!0,useDefaultValue:!0,ignoreMissingRefs:!0,failOnInvalidTypes:!1,failOnInvalidFormat:!1,extensions:{faker:g($)}})}var f=new Set(["__proto__","constructor","prototype"]);function S(Q,$,Z=Math.random){if($!==void 0){if($<0)return 0;return $}if(Q.minItems!==void 0&&Q.maxItems!==void 0){let B=Q.minItems,K=Math.max(B,Q.maxItems);return Math.floor(Z()*(K-B+1))+B}if(Q.minItems!==void 0)return Math.max(Q.minItems,V);if(Q.maxItems!==void 0)return Math.min(Q.maxItems,V);return V}function J(Q,$,Z,B,K){if(!$)return Q;if(typeof Q!=="object"||Q===null)return Q;let j=structuredClone(Q);for(let[z,X]of Object.entries($)){if(f.has(z))continue;if(z.includes("."))t(j,z,X,{params:Z,state:B,query:K});else if(typeof X==="object"&&X!==null&&!Array.isArray(X)){let q=X;if(j[z]&&typeof j[z]==="object")j[z]=J(j[z],q,Z,B,K);else j[z]=J({},q,Z,B,K)}else if(typeof X==="string"&&X.includes("{{"))j[z]=l(X,{params:Z,state:B,query:K});else j[z]=X}return j}function t(Q,$,Z,B){let K=$.split("."),j=Q;for(let X=0;X<K.length-1;X++){let q=K[X];if(f.has(q))return;if(!(q in j)||typeof j[q]!=="object"||j[q]===null)j[q]={};j=j[q]}let z=K[K.length-1];if(f.has(z))return;if(typeof Z==="string"&&Z.includes("{{"))j[z]=l(Z,B);else j[z]=Z}function u(Q,$){let Z=$.trim().split("."),B=Q;for(let K of Z)if(B&&typeof B==="object")B=B[K];else return;return B}function l(Q,$){let Z=Q.match(/^\{\{\s*([^}]+)\s*\}\}$/);if(Z){let B=u($,Z[1]);return B!==void 0?B:Q}return Q.replace(/\{\{\s*([^}]+)\s*\}\}/g,(B,K)=>{let j=u($,K);return j!==void 0?String(j):B})}var a=[{keywords:["email"],fakerMethod:"internet.email",schemaType:"string",minScore:0.6,format:"email"},{keywords:["username","user_name","login","handle"],fakerMethod:"internet.username",schemaType:"string",minScore:0.6},{keywords:["password","passwd","secret"],fakerMethod:"internet.password",schemaType:"string",minScore:0.6},{keywords:["token","api_key","apikey","access_token","refresh_token"],fakerMethod:"string.alphanumeric",schemaType:"string",minScore:0.8}],e=[{keywords:["first_name","firstname","given_name","forename"],fakerMethod:"person.firstName",schemaType:"string",minScore:0.6},{keywords:["last_name","lastname","surname","family_name"],fakerMethod:"person.lastName",schemaType:"string",minScore:0.6},{keywords:["full_name","fullname","display_name","displayname","full_name"],fakerMethod:"person.fullName",schemaType:"string",minScore:0.6},{keywords:["name"],fakerMethod:"person.fullName",schemaType:"string",minScore:0.6},{keywords:["middle_name","middlename"],fakerMethod:"person.middleName",schemaType:"string",minScore:0.8},{keywords:["prefix","honorific","salutation"],fakerMethod:"person.prefix",schemaType:"string",minScore:0.8},{keywords:["suffix"],fakerMethod:"person.suffix",schemaType:"string",minScore:0.9},{keywords:["gender","sex"],fakerMethod:"person.sex",schemaType:"string",minScore:0.8},{keywords:["bio","biography"],fakerMethod:"lorem.paragraph",schemaType:"string",minScore:0.8},{keywords:["job_title","jobtitle","position","occupation","job","role"],fakerMethod:"person.jobTitle",schemaType:"string",minScore:0.6},{keywords:["job_area","department","division","team"],fakerMethod:"person.jobArea",schemaType:"string",minScore:0.8},{keywords:["zodiac","zodiac_sign"],fakerMethod:"person.zodiacSign",schemaType:"string",minScore:0.9}],Q1=[{keywords:["phone","mobile","cell","tel","telephone","cellphone","fax"],fakerMethod:"phone.number",schemaType:"string",minScore:0.6},{keywords:["imei"],fakerMethod:"phone.imei",schemaType:"string",minScore:0.9}],$1=[{keywords:["street","street_address"],fakerMethod:"location.streetAddress",schemaType:"string",minScore:0.6},{keywords:["address","mailing_address","postal_address"],fakerMethod:"location.streetAddress",schemaType:"string",minScore:0.6},{keywords:["city","town"],fakerMethod:"location.city",schemaType:"string",minScore:0.6},{keywords:["state","province","region"],fakerMethod:"location.state",schemaType:"string",minScore:0.8},{keywords:["country"],fakerMethod:"location.country",schemaType:"string",minScore:0.8},{keywords:["country_code","countrycode"],fakerMethod:"location.countryCode",schemaType:"string",minScore:0.8},{keywords:["county"],fakerMethod:"location.county",schemaType:"string",minScore:0.9},{keywords:["continent"],fakerMethod:"location.continent",schemaType:"string",minScore:0.9},{keywords:["zip","zipcode","zip_code","postal","postal_code"],fakerMethod:"location.zipCode",schemaType:"string",minScore:0.6},{keywords:["latitude","lat"],fakerMethod:"location.latitude",schemaType:"number",minScore:0.6},{keywords:["longitude","lng","lon","long"],fakerMethod:"location.longitude",schemaType:"number",minScore:0.6},{keywords:["timezone","time_zone","tz"],fakerMethod:"location.timeZone",schemaType:"string",minScore:0.8},{keywords:["building_number"],fakerMethod:"location.buildingNumber",schemaType:"string",minScore:0.9}],B1=[{keywords:["url","website","link","href","homepage","webpage"],fakerMethod:"internet.url",schemaType:"string",minScore:0.6},{keywords:["avatar","photo_url","profile_image","image_url","picture","thumbnail","icon"],fakerMethod:"image.avatar",schemaType:"string",minScore:0.6},{keywords:["domain","hostname","domain_name"],fakerMethod:"internet.domainName",schemaType:"string",minScore:0.8},{keywords:["ip","ip_address","ipaddress","ipv4"],fakerMethod:"internet.ipv4",schemaType:"string",minScore:0.8},{keywords:["ipv6"],fakerMethod:"internet.ipv6",schemaType:"string",minScore:0.9},{keywords:["mac","mac_address"],fakerMethod:"internet.mac",schemaType:"string",minScore:0.9},{keywords:["user_agent","useragent"],fakerMethod:"internet.userAgent",schemaType:"string",minScore:0.9},{keywords:["port"],fakerMethod:"internet.port",schemaType:"number",minScore:0.9},{keywords:["protocol"],fakerMethod:"internet.protocol",schemaType:"string",minScore:0.9}],Z1=[{keywords:["uuid","guid"],fakerMethod:"string.uuid",schemaType:"string",minScore:0.6},{keywords:["sku"],fakerMethod:"string.alphanumeric",schemaType:"string",minScore:0.9},{keywords:["slug"],fakerMethod:"lorem.slug",schemaType:"string",minScore:0.8},{keywords:["isbn"],fakerMethod:"commerce.isbn",schemaType:"string",minScore:0.9},{keywords:["vin"],fakerMethod:"vehicle.vin",schemaType:"string",minScore:0.9},{keywords:["iban"],fakerMethod:"finance.iban",schemaType:"string",minScore:0.9},{keywords:["bic","swift"],fakerMethod:"finance.bic",schemaType:"string",minScore:0.9},{keywords:["ssn","social_security"],fakerMethod:"string.numeric",schemaType:"string",minScore:0.9}],K1=[{keywords:["description","desc","summary","overview","about","excerpt"],fakerMethod:"lorem.paragraph",schemaType:"string",minScore:0.6},{keywords:["content","body","text","message"],fakerMethod:"lorem.paragraphs",schemaType:"string",minScore:0.6},{keywords:["title","heading","subject","headline"],fakerMethod:"lorem.sentence",schemaType:"string",minScore:0.6},{keywords:["tag","label","category","keyword"],fakerMethod:"lorem.word",schemaType:"string",minScore:0.8},{keywords:["comment","note","remark","feedback","review"],fakerMethod:"lorem.sentence",schemaType:"string",minScore:0.8},{keywords:["sentence"],fakerMethod:"lorem.sentence",schemaType:"string",minScore:0.9},{keywords:["paragraph"],fakerMethod:"lorem.paragraph",schemaType:"string",minScore:0.9},{keywords:["word"],fakerMethod:"lorem.word",schemaType:"string",minScore:0.9}],j1=[{keywords:["company","organization","org","employer","brand"],fakerMethod:"company.name",schemaType:"string",minScore:0.6},{keywords:["product","product_name","item_name"],fakerMethod:"commerce.productName",schemaType:"string",minScore:0.8},{keywords:["price","amount","cost","fee","salary","wage","total","subtotal","balance","revenue","income"],fakerMethod:"commerce.price",minScore:0.6},{keywords:["currency","currency_code"],fakerMethod:"finance.currencyCode",schemaType:"string",minScore:0.8},{keywords:["currency_name"],fakerMethod:"finance.currencyName",schemaType:"string",minScore:0.8},{keywords:["currency_symbol"],fakerMethod:"finance.currencySymbol",schemaType:"string",minScore:0.8},{keywords:["account_number","account_no"],fakerMethod:"finance.accountNumber",schemaType:"string",minScore:0.8},{keywords:["credit_card","card_number"],fakerMethod:"finance.creditCardNumber",schemaType:"string",minScore:0.8},{keywords:["cvv"],fakerMethod:"finance.creditCardCVV",schemaType:"string",minScore:0.9},{keywords:["transaction_type"],fakerMethod:"finance.transactionType",schemaType:"string",minScore:0.9}],X1=[{keywords:["created_at","createdat","creation_date","date_created","createddate"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["updated_at","updatedat","modified_at","modifiedat","date_updated","last_modified","date_modified"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["deleted_at","deletedat"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["published_at","publishedat","publish_date"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["expires_at","expiresat","expiration","expiry"],fakerMethod:"date.future",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["birthday","date_of_birth","dob","birthdate","born"],fakerMethod:"date.birthdate",schemaType:"string",minScore:0.8},{keywords:["start_date","begin_date"],fakerMethod:"date.past",schemaType:"string",minScore:0.8},{keywords:["end_date","due_date","deadline"],fakerMethod:"date.future",schemaType:"string",minScore:0.8},{keywords:["timestamp","ts"],fakerMethod:"date.recent",schemaType:"string",minScore:0.8}],z1=[{keywords:["color","colour"],fakerMethod:"color.human",schemaType:"string",minScore:0.8},{keywords:["hex_color","hexcolor"],fakerMethod:"color.rgb",schemaType:"string",minScore:0.9}],q1=[{keywords:["age"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:18,max:80}},{keywords:["rating","score","stars"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:5}},{keywords:["count","quantity","qty"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:100}},{keywords:["percentage","percent"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:0,max:100}},{keywords:["weight"],fakerMethod:"number.float",schemaType:"number",minScore:0.8,fakerArgs:{min:0.1,max:200}},{keywords:["height"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:100,max:220}},{keywords:["width","length","depth"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:1000}},{keywords:["priority","order","rank","level"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:10}},{keywords:["version"],fakerMethod:"system.semver",schemaType:"string",minScore:0.8}],W1=[{keywords:["is_active","isactive","active","enabled","is_enabled"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.9},{keywords:["is_deleted","isdeleted","deleted"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.05},{keywords:["is_archived","isarchived","archived"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.1},{keywords:["is_verified","isverified","verified","confirmed","is_confirmed"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.8},{keywords:["is_admin","isadmin","admin"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.1},{keywords:["is_public","ispublic","public","visible","is_visible"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.7},{keywords:["is_read","isread","read","seen","is_seen"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.6},{keywords:["is_default","isdefault"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.8,trueProbability:0.1},{keywords:["is_required","isrequired","required","mandatory"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.5},{keywords:["is_featured","isfeatured","featured","promoted"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.8,trueProbability:0.15},{keywords:["available","in_stock"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.8,trueProbability:0.85}],b1=[{keywords:["filename","file_name"],fakerMethod:"system.fileName",schemaType:"string",minScore:0.8},{keywords:["file_type","filetype","mime_type","mimetype","content_type"],fakerMethod:"system.mimeType",schemaType:"string",minScore:0.8},{keywords:["file_ext","extension"],fakerMethod:"system.fileExt",schemaType:"string",minScore:0.9},{keywords:["file_path","filepath","path"],fakerMethod:"system.filePath",schemaType:"string",minScore:0.9},{keywords:["cron","cron_expression"],fakerMethod:"system.cron",schemaType:"string",minScore:0.9},{keywords:["semver"],fakerMethod:"system.semver",schemaType:"string",minScore:0.9}],U1=[{keywords:["aircraft_type","airplane","aircraft"],fakerMethod:"airline.aircraftType",schemaType:"string",minScore:0.9},{keywords:["flight_number","flight"],fakerMethod:"airline.flightNumber",schemaType:"string",minScore:0.9},{keywords:["record_locator","booking_ref","pnr"],fakerMethod:"airline.recordLocator",schemaType:"string",minScore:0.9},{keywords:["seat","seat_number"],fakerMethod:"airline.seat",schemaType:"string",minScore:0.9}],w1=[{keywords:["animal","animal_type","species"],fakerMethod:"animal.type",schemaType:"string",minScore:0.9},{keywords:["pet_name"],fakerMethod:"animal.petName",schemaType:"string",minScore:0.9},{keywords:["breed"],fakerMethod:"animal.dog",schemaType:"string",minScore:0.9},{keywords:["bird"],fakerMethod:"animal.bird",schemaType:"string",minScore:0.9},{keywords:["fish"],fakerMethod:"animal.fish",schemaType:"string",minScore:0.9},{keywords:["horse"],fakerMethod:"animal.horse",schemaType:"string",minScore:0.9},{keywords:["insect"],fakerMethod:"animal.insect",schemaType:"string",minScore:0.9}],V1=[{keywords:["book_title"],fakerMethod:"book.title",schemaType:"string",minScore:0.9},{keywords:["author","book_author","writer"],fakerMethod:"book.author",schemaType:"string",minScore:0.9},{keywords:["genre","book_genre"],fakerMethod:"book.genre",schemaType:"string",minScore:0.9},{keywords:["publisher","publishing_house"],fakerMethod:"book.publisher",schemaType:"string",minScore:0.9},{keywords:["book_series","series_name"],fakerMethod:"book.series",schemaType:"string",minScore:0.9}],Y1=[{keywords:["dish","meal","recipe_name"],fakerMethod:"food.dish",schemaType:"string",minScore:0.9},{keywords:["ingredient"],fakerMethod:"food.ingredient",schemaType:"string",minScore:0.9},{keywords:["spice"],fakerMethod:"food.spice",schemaType:"string",minScore:0.9},{keywords:["fruit"],fakerMethod:"food.fruit",schemaType:"string",minScore:0.9},{keywords:["vegetable"],fakerMethod:"food.vegetable",schemaType:"string",minScore:0.9},{keywords:["meat"],fakerMethod:"food.meat",schemaType:"string",minScore:0.9},{keywords:["food_description"],fakerMethod:"food.description",schemaType:"string",minScore:0.9},{keywords:["ethnic_category","cuisine"],fakerMethod:"food.ethnicCategory",schemaType:"string",minScore:0.9}],F1=[{keywords:["song","song_name","track","track_name"],fakerMethod:"music.songName",schemaType:"string",minScore:0.9},{keywords:["album","album_name"],fakerMethod:"music.album",schemaType:"string",minScore:0.9},{keywords:["artist","musician","band"],fakerMethod:"music.artist",schemaType:"string",minScore:0.9},{keywords:["music_genre"],fakerMethod:"music.genre",schemaType:"string",minScore:0.9}],C1=[],G1=[{keywords:["vehicle","vehicle_name"],fakerMethod:"vehicle.vehicle",schemaType:"string",minScore:0.9},{keywords:["vehicle_type"],fakerMethod:"vehicle.type",schemaType:"string",minScore:0.9},{keywords:["vehicle_manufacturer","car_make"],fakerMethod:"vehicle.manufacturer",schemaType:"string",minScore:0.9},{keywords:["vehicle_model","car_model"],fakerMethod:"vehicle.model",schemaType:"string",minScore:0.9},{keywords:["fuel","fuel_type"],fakerMethod:"vehicle.fuel",schemaType:"string",minScore:0.9},{keywords:["bicycle"],fakerMethod:"vehicle.bicycle",schemaType:"string",minScore:0.9},{keywords:["vehicle_color","car_color"],fakerMethod:"vehicle.color",schemaType:"string",minScore:0.9}],H1=[{keywords:["branch","branch_name","git_branch"],fakerMethod:"git.branch",schemaType:"string",minScore:0.9},{keywords:["commit_sha","commit_hash","sha"],fakerMethod:"git.commitSha",schemaType:"string",minScore:0.9},{keywords:["commit_message"],fakerMethod:"git.commitMessage",schemaType:"string",minScore:0.9}],J1=[{keywords:["db_column","column_name"],fakerMethod:"database.column",schemaType:"string",minScore:0.9},{keywords:["db_type","column_type"],fakerMethod:"database.type",schemaType:"string",minScore:0.9},{keywords:["db_engine","database_engine"],fakerMethod:"database.engine",schemaType:"string",minScore:0.9},{keywords:["collation"],fakerMethod:"database.collation",schemaType:"string",minScore:0.9},{keywords:["mongodb_id","object_id"],fakerMethod:"database.mongodbObjectId",schemaType:"string",minScore:0.9}],I1=[{keywords:["abbreviation","tech_abbreviation"],fakerMethod:"hacker.abbreviation",schemaType:"string",minScore:0.9},{keywords:["hacker_phrase","tech_phrase"],fakerMethod:"hacker.phrase",schemaType:"string",minScore:0.9}],M1=[{keywords:["adjective"],fakerMethod:"word.adjective",schemaType:"string",minScore:0.8},{keywords:["adverb"],fakerMethod:"word.adverb",schemaType:"string",minScore:0.8},{keywords:["noun"],fakerMethod:"word.noun",schemaType:"string",minScore:0.8},{keywords:["verb"],fakerMethod:"word.verb",schemaType:"string",minScore:0.8}],d=[...a,...e,...Q1,...$1,...B1,...Z1,...K1,...j1,...X1,...z1,...q1,...W1,...b1,...U1,...w1,...V1,...Y1,...F1,...C1,...G1,...H1,...J1,...I1,...M1];function p(Q){let $=Q.split(/[_-]/),Z=[];for(let B of $){if(!B)continue;let K=B.replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z0-9])([A-Z])/g,"$1_$2").split("_");for(let j of K)if(j)Z.push(j.toLowerCase())}return Z}function P1(Q,$){let Z=Q.join(""),B=0;for(let K of $){let j=p(K),z=j.join("");if(Z===z)return 1;if(j.length>0&&j.every((X)=>Q.includes(X))){let q=j.length/Q.length>0.5?0.9:0.65;B=Math.max(B,q)}if(j.length>0&&j.length<=Q.length){if(Q.slice(-j.length).every((q,W)=>q===j[W]))B=Math.max(B,0.8)}if(z.length>=3&&Z.includes(z))B=Math.max(B,0.7)}return B}function i(Q,$,Z=d){let B=$,K=typeof $.type==="string"?$.type:void 0;if(K==="string"&&$.format==="uuid")return{mapping:{keywords:["uuid"],fakerMethod:"string.uuid",schemaType:"string",minScore:0.5},score:1};if(B.pattern||B.enum||B.faker||B.$ref||K==="string"&&($.minLength!==void 0||$.maxLength!==void 0))return;let j=$.minimum!==void 0||$.maximum!==void 0||$.exclusiveMinimum!==void 0||$.exclusiveMaximum!==void 0,z=p(Q),X;for(let q of Z){if(j&&(q.schemaType==="number"||q.schemaType==="integer"))continue;if(K==="object"||K==="array")continue;if(q.schemaType&&K&&q.schemaType!==K){if(!(q.schemaType==="number"&&K==="integer"||q.schemaType==="integer"&&K==="number"))continue}let W=P1(z,q.keywords);if(W>=q.minScore&&(!X||W>X.score))X={mapping:q,score:W}}if(K==="string"){if(z[z.length-1]==="id"&&z.length>1){if(!X||X.score<0.8)X={mapping:{keywords:["id"],fakerMethod:"string.uuid",schemaType:"string",minScore:0.5},score:0.8}}}return X}import{ResourceLimitError as L,SchemaValidationError as U}from"@schmock/core";var _;function b(Q){return typeof Q==="object"&&Q!==null&&!Array.isArray(Q)}function C(Q,$="$"){if(!Q||typeof Q!=="object")throw new U($,"Schema must be a valid JSON Schema object");if(Object.keys(Q).length===0)throw new U($,"Schema cannot be empty");let Z=["object","array","string","number","integer","boolean","null"];if(Q.type&&typeof Q.type==="string"&&!Z.includes(Q.type))throw new U($,`Invalid schema type: "${Q.type}"`,"Supported types are: object, array, string, number, integer, boolean, null");if(Q.type==="object"&&Q.properties){if(typeof Q.properties!=="object"||Array.isArray(Q.properties))throw new U(`${$}.properties`,"Properties must be an object mapping property names to schemas",'Use { "propertyName": { "type": "string" } } format');for(let[B,K]of Object.entries(Q.properties))if(typeof K==="object"&&K!==null){let j="faker"in K?String(K.faker):void 0;if(j)try{y(j)}catch(z){if(z instanceof U){let X=z.context,q="Invalid faker method",W;if(X&&typeof X==="object"){if("issue"in X&&typeof X.issue==="string")q=X.issue;if("suggestion"in X&&typeof X.suggestion==="string")W=X.suggestion}throw new U(`${$}.properties.${B}.faker`,q,W)}if(z instanceof Error)throw z;throw Error(String(z))}C(K,`${$}.properties.${B}`)}}if(Q.type==="array"){if(Q.items===null||Q.items===void 0)throw new U(`${$}.items`,"Array schema must have valid items definition","Define items as a schema object or array of schemas");if(Array.isArray(Q.items)){if(Q.items.length===0)throw new U(`${$}.items`,"Array items cannot be empty array","Provide at least one item schema");Q.items.forEach((B,K)=>{if(typeof B==="object"&&B!==null)C(B,`${$}.items[${K}]`)})}else if(typeof Q.items==="object"&&Q.items!==null)C(Q.items,`${$}.items`)}if($==="$"){if(R(Q))throw new U($,"Schema contains circular references which are not supported");if(Q.$ref==="#")throw new U($,"Self-referencing schemas are not supported");let B=O(Q);if(B>M)throw new L("schema_nesting_depth",M,B);if(B>=F)L1(Q,$);P(Q,$)}}function R(Q,$=new Set){if($.has(Q))return!0;if(Q.$ref==="#")return!0;if($.add(Q),Q.type==="object"&&Q.properties){for(let Z of Object.values(Q.properties))if(b(Z)){if(R(Z,$))return!0}}if(Q.type==="array"&&Q.items){let Z=Array.isArray(Q.items)?Q.items:[Q.items];for(let B of Z)if(b(B)){if(R(B,$))return!0}}return $.delete(Q),!1}function O(Q,$=0){if($>M)return $;let Z=$;if(Q.type==="object"&&Q.properties){for(let B of Object.values(Q.properties))if(b(B))Z=Math.max(Z,O(B,$+1))}if(Q.type==="array"&&Q.items){let B=Array.isArray(Q.items)?Q.items:[Q.items];for(let K of B)if(b(K))Z=Math.max(Z,O(K,$+1))}return Z}function L1(Q,$){function Z(B,K){let j=B.type;if(Array.isArray(j)?j.includes("array"):j==="array"){let X=B.maxItems||V;if(K>=F&&X>=H)throw new L("deep_nesting_memory_risk",F*H,K*X);if(B.items){let q=Array.isArray(B.items)?B.items:[B.items];for(let W of q)if(b(W))Z(W,K+1)}return}if(j==="object"&&B.properties){for(let X of Object.values(B.properties))if(b(X))Z(X,K+1)}}Z(Q,0)}function P(Q,$){if(Q.type==="array"){if(Q.maxItems&&Q.maxItems>Y)throw new L("array_max_items",Y,Q.maxItems);let Z=O(Q),B=Q.maxItems||Q.minItems||V;if(Z>F&&B>H)throw new L("memory_estimation",F*H,Z*B)}if(Q.type==="object"&&Q.properties){for(let[Z,B]of Object.entries(Q.properties))if(b(B))P(B,`${$}.properties.${Z}`)}if(Q.type==="array"&&Q.items){if(Array.isArray(Q.items))Q.items.forEach((Z,B)=>{if(b(Z))P(Z,`${$}.items[${B}]`)});else if(b(Q.items))P(Q.items,`${$}.items`)}}function y(Q){let $=Q.split(".");if($.length<2)throw new U("$.faker",`Invalid faker method format: "${Q}"`,"Use format like 'person.firstName' or 'internet.email'");if(!_)_=g();let B=_;for(let K of $)if(B&&typeof B==="object"&&K in B)B=B[K];else throw new U("$.faker",`Invalid faker method: "${Q}"`,"Check faker.js documentation for valid methods");if(typeof B!=="function")throw new U("$.faker",`Invalid faker method: "${Q}" is not a function`,"Check faker.js documentation for valid methods")}function w(Q){if(!Q||typeof Q!=="object")return Q;let $={...Q};if($.properties){$.properties={...$.properties};for(let[Z,B]of Object.entries($.properties))if(b(B))$.properties[Z]=O1(Z,B)}for(let Z of["allOf","anyOf","oneOf"]){let B=$[Z];if(Array.isArray(B))$[Z]=B.map((K)=>b(K)?w(K):K)}if($.items){if(Array.isArray($.items))$.items=$.items.map((Z)=>b(Z)?w(Z):Z);else if(b($.items))$.items=w($.items)}if($.additionalProperties&&typeof $.additionalProperties==="object"&&!Array.isArray($.additionalProperties))$.additionalProperties=w($.additionalProperties);return $}function O1(Q,$){let Z={...$};if(Z.faker)return y(Z.faker),Z;let B=Z.allOf||Z.anyOf||Z.oneOf;if(Z.properties||B||Z.items){let j=w(Z);Object.assign(Z,j)}if(B)return Z;if(Z.const!==void 0||Z.enum)return Z;let K=i(Q,Z);if(K){if(Z.faker=K.mapping.fakerMethod,K.mapping.format)Z.format=K.mapping.format;if(K.mapping.trueProbability!==void 0)Z.schmockTrueProbability=K.mapping.trueProbability}return Z}function a1(Q){return C(Q.schema),{name:"faker",version:k,async process($,Z){if(Z!==void 0&&Z!==null)return{context:$,response:Z};try{let B=await D1({schema:Q.schema,count:Q.count,overrides:Q.overrides,params:$.params,state:$.routeState,query:$.query,seed:Q.seed});return{context:$,response:B}}catch(B){if(B instanceof n||B instanceof o)throw B;throw new v1($.path,B instanceof Error?B:Error(String(B)),Q.schema)}}}}async function D1(Q){let{schema:$,count:Z,overrides:B,params:K,state:j,query:z,seed:X}=Q;if(C($),$.type==="array"&&$.items){let I=S($,Z);if(I>Y)throw new o("array_size",Y,I);let D=Array.isArray($.items)?$.items[0]:$.items;if(!D||typeof D==="boolean")throw new n("$.items","Array schema must have valid items definition");let A=w(D),N=[];for(let T=0;T<I;T++){let G=await x(A,X);G=v(G,A),G=J(G,B,K,j,z),N.push(G)}return N}let q=w($),W=await x(q,X);return W=v(W,q),W=J(W,B,K,j,z),W}function v(Q,$){if(Q===null||Q===void 0||!$||typeof $!=="object")return Q;let Z=$;if(Z.schmockNullable===!0){if(Math.random()<E)return null}if($.type==="boolean"&&typeof Z.schmockTrueProbability==="number")return Math.random()<Z.schmockTrueProbability;if(typeof Q==="object"&&!Array.isArray(Q)&&$.properties){let B=Q;for(let[K,j]of Object.entries($.properties))if(K in B&&b(j))B[K]=v(B[K],j)}if(Array.isArray(Q)&&$.items&&b($.items))for(let B=0;B<Q.length;B++)Q[B]=v(Q[B],$.items);return Q}export{D1 as generateFromSchema,a1 as fakerPlugin};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schmock/faker",
|
|
3
3
|
"description": "Faker-powered automatic data generation for Schmock",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"json-schema-faker": "^0.6.1"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@schmock/core": "^2.1.
|
|
37
|
+
"@schmock/core": "^2.1.6"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@amiceli/vitest-cucumber": "^6.5.0",
|
|
@@ -228,6 +228,46 @@ describe("findBestMapping", () => {
|
|
|
228
228
|
const result = findBestMapping("email", schema);
|
|
229
229
|
expect(result).toBeUndefined();
|
|
230
230
|
});
|
|
231
|
+
|
|
232
|
+
// Regression: name-based mappings (e.g. lorem.word for 'label') don't
|
|
233
|
+
// honor JSON Schema length constraints, so they'd produce out-of-range
|
|
234
|
+
// strings ~20% of the time when the schema asked for a specific length.
|
|
235
|
+
// Skip the mapping and let json-schema-faker generate a length-respecting
|
|
236
|
+
// string instead. Mirrors the numeric constraint skip in the loop below.
|
|
237
|
+
it("skips string mapping when schema has minLength", () => {
|
|
238
|
+
const result = findBestMapping("label", {
|
|
239
|
+
type: "string",
|
|
240
|
+
minLength: 3,
|
|
241
|
+
});
|
|
242
|
+
expect(result).toBeUndefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("skips string mapping when schema has maxLength", () => {
|
|
246
|
+
const result = findBestMapping("label", {
|
|
247
|
+
type: "string",
|
|
248
|
+
maxLength: 20,
|
|
249
|
+
});
|
|
250
|
+
expect(result).toBeUndefined();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("skips string mapping when both length constraints are set", () => {
|
|
254
|
+
const result = findBestMapping("label", {
|
|
255
|
+
type: "string",
|
|
256
|
+
minLength: 3,
|
|
257
|
+
maxLength: 20,
|
|
258
|
+
});
|
|
259
|
+
expect(result).toBeUndefined();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("does not skip non-string types when minLength happens to be set", () => {
|
|
263
|
+
// minLength is a string-only keyword; on a non-string schema it's
|
|
264
|
+
// meaningless. The number mapping for 'age' should still apply.
|
|
265
|
+
const result = findBestMapping("age", {
|
|
266
|
+
type: "number",
|
|
267
|
+
minLength: 3, // nonsensical on a number, but shouldn't block the mapping
|
|
268
|
+
} as any);
|
|
269
|
+
expect(result).toBeDefined();
|
|
270
|
+
});
|
|
231
271
|
});
|
|
232
272
|
|
|
233
273
|
it("does not map unrecognized fields", () => {
|
|
@@ -110,11 +110,17 @@ export function findBestMapping(
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// Skip if schema already has pattern, enum, faker, or $ref constraint
|
|
113
|
+
// Also skip string mappings when minLength/maxLength is set — name-based
|
|
114
|
+
// mappings (e.g. lorem.word for "label") don't honor JSON Schema length
|
|
115
|
+
// constraints, so they'd produce out-of-range strings ~20% of the time.
|
|
116
|
+
// Mirrors the numeric constraint skip just below.
|
|
113
117
|
if (
|
|
114
118
|
schemaAny.pattern ||
|
|
115
119
|
schemaAny.enum ||
|
|
116
120
|
schemaAny.faker ||
|
|
117
|
-
schemaAny.$ref
|
|
121
|
+
schemaAny.$ref ||
|
|
122
|
+
(schemaType === "string" &&
|
|
123
|
+
(schema.minLength !== undefined || schema.maxLength !== undefined))
|
|
118
124
|
) {
|
|
119
125
|
return undefined;
|
|
120
126
|
}
|
package/src/performance.test.ts
CHANGED
|
@@ -175,10 +175,19 @@ describe("Performance and Memory", () => {
|
|
|
175
175
|
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
+
const medNarrow = median(narrowTimes);
|
|
178
179
|
const medWide = median(wideTimes);
|
|
179
180
|
|
|
180
|
-
//
|
|
181
|
-
|
|
181
|
+
// Scale assertion (machine-speed-independent): wide has 5x the
|
|
182
|
+
// properties of narrow, so its generation time should scale roughly
|
|
183
|
+
// with that. Allow 20x headroom for measurement noise + small superlinear
|
|
184
|
+
// behavior; this catches order-of-magnitude regressions without flaking
|
|
185
|
+
// on shared CI runners where absolute wall-clock thresholds are unreliable.
|
|
186
|
+
if (medNarrow > 0.1) {
|
|
187
|
+
// Only check scaling if we have measurable timing
|
|
188
|
+
expect(medWide).toBeGreaterThan(medNarrow * 0.5); // sanity: more work, not less
|
|
189
|
+
expect(medWide).toBeLessThan(medNarrow * 20); // catches >4x-per-property regressions
|
|
190
|
+
}
|
|
182
191
|
});
|
|
183
192
|
});
|
|
184
193
|
|