Skip to content

Conversation

@hi-rai
Copy link
Contributor

@hi-rai hi-rai commented Jul 7, 2025

This should fix #16 following colinhacks/zod#2984

Note that recursive schema handling is much simpler in v4 of zod. We won't have the need to handle recursive types differently (so a lot of code can be cleaned up) and we won't even get the current issue. Maybe we can support v4 of zod in v2 of zen

@hi-rai hi-rai self-assigned this Jul 7, 2025
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: String Length Validation Now Counts Code Points

String validation methods (len, min, max, gt, gte, lt, lte) now use custom .refine() functions with [...val].length instead of Zod's native .min(), .max(), and .length() methods. This changes string length counting from UTF-16 code units to Unicode code points/graphemes, leading to different validation results for multi-byte characters and emojis. This breaking change appears to be an accidental side effect unrelated to the PR's goal of fixing recursive embedded structs.

zod.go#L939-L970

zen/zod.go

Lines 939 to 970 in 924b7cd

case "oneof":
vals := splitParamsRegex.FindAllString(part[6:], -1)
for i := 0; i < len(vals); i++ {
vals[i] = strings.Replace(vals[i], "'", "", -1)
}
if len(vals) == 0 {
panic("oneof= must be followed by a list of values")
}
// const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
validateStr.WriteString(fmt.Sprintf(".enum([\"%s\"] as const)", strings.Join(vals, "\", \"")))
case "len":
refines = append(refines, fmt.Sprintf(".refine((val) => [...val].length === %s, 'String must contain %s character(s)')", valValue, valValue))
case "min":
refines = append(refines, fmt.Sprintf(".refine((val) => [...val].length >= %s, 'String must contain at least %s character(s)')", valValue, valValue))
case "max":
refines = append(refines, fmt.Sprintf(".refine((val) => [...val].length <= %s, 'String must contain at most %s character(s)')", valValue, valValue))
case "gt":
val, err := strconv.Atoi(valValue)
if err != nil {
panic("gt= must be followed by a number")
}
refines = append(refines, fmt.Sprintf(".refine((val) => [...val].length > %d, 'String must contain at least %d character(s)')", val, val+1))
case "gte":
refines = append(refines, fmt.Sprintf(".refine((val) => [...val].length >= %s, 'String must contain at least %s character(s)')", valValue, valValue))
case "lt":
val, err := strconv.Atoi(valValue)
if err != nil {
panic("lt= must be followed by a number")
}
refines = append(refines, fmt.Sprintf(".refine((val) => [...val].length < %d, 'String must contain at most %d character(s)')", val, val-1))
case "lte":
refines = append(refines, fmt.Sprintf(".refine((val) => [...val].length <= %s, 'String must contain at most %s character(s)')", valValue, valValue))

Fix in CursorFix in Web


Was this report helpful? Give feedback by reacting with 👍 or 👎

@satvik007 satvik007 merged commit 67d8f78 into main Jul 22, 2025
2 checks passed
@satvik007 satvik007 deleted the dev/himanshu/fix-schema-generation-embedded-recursive-structs branch July 22, 2025 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recursive embedded struct generates TypeScript errors

4 participants