Open fcrozatier opened 1 year ago
I'm trying to do something similar, with a multiline regex but the second line is not validated at all.
//simple dd-MM-yyyy format
const regEx=new RegExp(/^[0-9]{2,2}\/[0-9]{2,2}\/[0-9]{4,4}$/gim)
const formSchema=z.object({
values: z.string().regex(regEx, "Invalid format")
});
test data:
22/09/2023
11/11/2222 something else
@fritzmatias I think your example is actually quite different: your string will “match” the provided regular expression, but will not be split will all results. I’m not entirely sure what you want to achieve, but if you wanted to get an array of matches you would have to use transform
like so:
const REGEX = new RegExp(/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/);
const formSchema = z.object({
values: z.string().transform(
(lines) => lines.split("\n").filter((line) => line.match(REGEX)),
),
});
Note that this regular expression is very simplistic, and dates like 32/13/0000
will still “match”
@mxdvl Thanks for your comment. What i'm trying to acchieve is validate the textarea has all lines matching the same regex. To acomplish that with a multiline RegEx + grouping, it should be good enougth since schema accepts string().regex(). (Or maybe i'm wrong). If i don't miss understand your example, you do the same by hand line by line. Executing transformation at validation time, and i expect to do it on action time based on the multiline RegEx validation. I'm newy with this library so sorry for any misussage.
What i found is, if i define the schema with as a z.string().regex(). I loose the first line of data at machAll call. But if i set it as z.string() it works properly. Could be a bug ?
Real RegEx with tags (check the global multiline): can be tested on https://regexr.com
const regEx=new RegExp(/^[ \t]*(?<date>[0-9]{2,2}\/[0-9]{2,2}\/[0-9]{4,4})[ \t]+(?<value>[0-9]+([.,][0-9]*)?)[ \t]*$/gm)
const formSchema=z.object({
values: z.string().regex(regEx, "Invalid format") // validates first line only but consumes first line of data at matchAll
// values: z.string() // does not validate but works fine at matchAll
});
const onSubmit = (schema: z.infer<typeof formSchema>) => {
console.log(`Submited: ${JSON.stringify(schema.values,null,2)}`);
const matches = schema.values.matchAll(regEx);
const arr=Array.from(matches);
console.log(`Array size: ${JSON.stringify(arr,null,2)}`);
const convertedArray=arr.map(matchConverter);
}
test data:
23/09/2023 130,6879
23/09/2023 130,6879
22/11/2232 12,1
22/09/2023 130,1797
23/09/2023 130,6879
24/09/2023 131,1981
25/09/2023 131,7103
Your example is definitely vastly different to @fcrozatier and I don’t think this issue is the right place for your comments. I do not think there’s a bug in zod
, but rather that you are using it incorrectly. Unfortunately, I do not have the resources to help you solve your current problem: it’s too specific to be adressed in a public issue.
@mxdvl Thanks, just a last comment , I think your proposal misess the validation itself, since the use of filter is going to keep the good lines only, and not notify about the bad ones.
@mxdvl Just to complete my scenario, and maybe this post helps someone else. I was able to work arround it using refine() for validation. ( this is a similar behaviour i expected for the z.string().regEx() call). And transform() to create the final object (not expected from z.string().regEx() call)
const regEx=new RegExp(/^[ \t]*([0-9]{2,2}\/[0-9]{2,2}\/[0-9]{4,4})[ \t]+([0-9]+([.,][0-9]*)?)[ \t]*$/gm)
const emptyLineRegEx = /^[ \r\n\t]*$/;
const formSchema=z.object({
values: z.string().refine(
lines => { const splittedLines = lines.split("\n");
const matchedLines = splittedLines.filter(line=>line.match(regEx)).length;
const notEmptyLines = splittedLines.filter(line=>!line.match(emptyLineRegEx)).length ;
return matchedLines === notEmptyLines;
},
(lines)=>{
const failedLines = lines.split("\n")
.map((line,index)=>{return {data:line,index}})
.filter((line)=> ! (line.data.match(regEx) || line.data.match(emptyLineRegEx)) )
.map((line) =>`(${line.index}) ${line.data}`);
return {
message: `Invalid lines: ${JSON.stringify(failedLines, null,2)}`
} as CustomErrorParams;
}).transform( lines =>
Array.from(lines.matchAll(regEx)).map(matchConverter)
)
const onSubmit = (schema: z.infer<typeof formSchema>) => {
// Gets an array of objects converted by my custom matchConverter() function
console.log(`Submited: ${JSON.stringify(schema.values,null,2)}`);
}
});
The problem:
As per html spec, the new lines of a text area are normalized to \n in browser and \r\n when the form is sent. So a simple count in characters will yield inconsistent length between browser and server.
This inconsistency is inevitable (it's cross browser as part of the html spec) and only concerns new lines so the validation of textarea elements.
So one cannot validate a textarea with a
maxlength
in browser and themax
validation in the schema (if there are new lines). This also impacts libraries trying to use a zod schema as single source of truth https://github.com/ciscoheat/sveltekit-superforms/issues/253Workaround
refine
instead of amax
liketransform
:But both solutions add some boilerplate for every
maxlength
on a textarea.Suggested solution:
Maybe a
text
validation could be useful. It would be likestring
but would implementmax
to prevent the browser/server inconsistency? Or amultiline
option to adapt thestring
validation behavior?Refs
https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4