Repository: json-schema-faker/json-schema-faker
Pull request #829 (2024-11-01 09:10:20)

feat: implement best effort approach to keep patter valid when stripping
When specifying both `pattern` and `maxLength` for string the stripping is performed after the regex has been generated which could render the regex invalid. 

For example
```js
{ 
  type: 'string',
  pattern: '^[a-z]+( [a-z])?$',
  maxLength: 3
}
```
Can generate 'ab c' and then be striped to 'ab ' which is not valid.

By recursively trying shorter substrings up to `minLength` sometimes we can solve the problem. I also thought of retrying the regex generation up to a number of times. It would be great if `randexp` implemented look aheads so we could add something like `(?=.{1,3}$)` but that's not the case and it doesn't seem easy to do.


Patch:
diff --git a/docs/README.md b/docs/README.md
index 2045a32..9841be1 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -133,6 +133,7 @@ jsf.locate('faker');
 - `omitNulls` &mdash; Remove any generated `null` value from the resulting value (default: `false`)
 - `minDateTime` &mdash; When generating a string with format `date-time`, set the minimum date that will get selected. If it's a number it will be the offset from `now` in milliseconds (default: `-2524608000000`)
 - `maxDateTime` &mdash; When generating a string with format `date-time`, set the maximum date that will get selected. If it's a number it will be the offset from `now` in milliseconds (default: `1000`)
+- `maxRegexRetry` &mdash; When generating a string with a `pattern` and a `maxLength`, if the `maxLength` is smaller than the generated value, the value will be cropped. If the new value is not matching the pattern, it will try to keep cropping the value until it's valid or it reaches either the minimum allowed length or the maximum number of iterations set by this option (default: `100`)
 
 ## Building
 
diff --git a/index.d.cts b/index.d.cts
index fc29bcc..24ba546 100644
--- a/index.d.cts
+++ b/index.d.cts
@@ -35,6 +35,7 @@ export interface JSONSchemaFakerOptions {
   renderComment?: boolean;
   refDepthMax?: number;
   refDepthMin?: number;
+  maxRegexRetry?: number;
 }
 
 export type JSONSchemaFakerRefs = Schema[] | { [k: string]: Schema };
diff --git a/index.d.ts b/index.d.ts
index 494c458..7717629 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -35,6 +35,7 @@ export interface JSONSchemaFakerOptions {
   renderComment?: boolean;
   refDepthMax?: number;
   refDepthMin?: number;
+  maxRegexRetry?: number;
 }
 
 export type JSONSchemaFakerRefs = Schema[] | { [k: string]: Schema };
diff --git a/src/lib/api/defaults.mjs b/src/lib/api/defaults.mjs
index 827fe4c..30346e8 100644
--- a/src/lib/api/defaults.mjs
+++ b/src/lib/api/defaults.mjs
@@ -4,6 +4,7 @@ export default defaults;
 
 defaults.defaultInvalidTypeProduct = undefined;
 defaults.defaultRandExpMax = 10;
+defaults.maxRegexRetry = 100;
 
 defaults.pruneProperties = [];
 defaults.ignoreProperties = [];
diff --git a/src/lib/core/utils.mjs b/src/lib/core/utils.mjs
index b21cf70..f82f5e6 100644
--- a/src/lib/core/utils.mjs
+++ b/src/lib/core/utils.mjs
@@ -238,6 +238,18 @@ function typecast(type, schema, callback) {
 
       if (value.length > max) {
         value = value.substr(0, max);
+        const pattern = schema.pattern ? new RegExp(schema.pattern) : null;
+        if (pattern && !pattern.test(value)) {
+          let temp = value;
+          const maxRetries = optionAPI('maxRegexRetry');
+          const minLength = Math.max(value.length - maxRetries, min);
+          while (temp.length > minLength && !pattern.test(temp)) {
+            temp = temp.slice(0, -1);
+            if (pattern.test(temp)) {
+              value = temp;
+            }
+          }
+        }
       }
 
       switch (schema.format) {


Test patch:
diff --git a/tests/schema/core/types/string.json b/tests/schema/core/types/string.json
index c5ca4c9..5b70b9c 100644
--- a/tests/schema/core/types/string.json
+++ b/tests/schema/core/types/string.json
@@ -27,6 +27,20 @@
         },
         "valid": true
       },
+      {
+        "description": "string generator",
+        "tests": [
+          {
+            "description": "should handle pattern with maxLength",
+            "schema": {
+              "type": "string",
+              "pattern": "^[a-z]{2}( [a-z])?$",
+              "maxLength": 3
+            },
+            "valid": true
+          }
+        ]
+      },
       {
         "description": "should handle format (core)",
         "schema": {