Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fmt.Print(zen.StructToZodSchema(User{}, zen.WithZodV3()))

The main migration differences are:

- string format tags such as `email`, `http_url`, `ipv4`, `uuid4`, and `md5` now use Zod v4 helpers like `z.email()`, `z.httpUrl()`, `z.ipv4()`, `z.uuid({ version: "v4" })`, and `z.hash("md5")`
- string format tags such as `email`, `http_url`, `ipv4`, `uuid4`, and `md5` now use Zod v4 helpers like `z.email()`, `z.url({ protocol: /^https?$/ })`, `z.ipv4()`, `z.uuid({ version: "v4" })`, and `z.hash("md5")`
- `ip` and `ip_addr` now emit `z.union([z.ipv4(), z.ipv6()])`
- embedded anonymous structs now expand through `.shape` spreads instead of `.merge(...)`
- enum-like map keys now emit `z.partialRecord(...)`
Expand Down
2 changes: 1 addition & 1 deletion testdata/TestFormatValidators/format_only/v4.golden
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const urlSchema = z.object({
export type url = z.infer<typeof urlSchema>

export const http_urlSchema = z.object({
value: z.string().check(z.httpUrl()),
value: z.string().check(z.url({ protocol: /^https?$/ })),
})
export type http_url = z.infer<typeof http_urlSchema>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const urlSchema = z.object({
export type url = z.infer<typeof urlSchema>

export const http_urlSchema = z.object({
value: z.string().min(1).check(z.httpUrl()),
value: z.string().min(1).check(z.url({ protocol: /^https?$/ })),
})
export type http_url = z.infer<typeof http_urlSchema>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// @typecheck
export const PayloadSchema = z.object({
Email: z.string().check(z.email()),
Link: z.string().check(z.httpUrl()),
Link: z.string().check(z.url({ protocol: /^https?$/ })),
Base64: z.string().check(z.base64()),
ID: z.string().check(z.uuid({ version: "v4" })),
Checksum: z.string().check(z.hash("md5")),
Expand Down
33 changes: 32 additions & 1 deletion tests/cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,10 @@ export const cases: TestCase[] = [
success: false,
},

// --- http_urlSchema (z.httpUrl() only accepts http/https) zod 3 accepts any URL ---
// --- http_urlSchema (z.url({ protocol: /^https?$/ }) only accepts http/https,
// but unlike z.httpUrl() does not require a dotted/TLD-style hostname —
// this mirrors go-playground/validator's http_url rule zod 3's .url() accepts
// any scheme. ---
{
name: "http_url: accepts https",
golden: "TestFormatValidators/format_only",
Expand Down Expand Up @@ -1498,6 +1501,34 @@ export const cases: TestCase[] = [
input: { value: "mailto:user@example.com" },
success: false,
},
{
name: "http_url: v4 accepts localhost with port",
golden: "TestFormatValidators/format_only/v4.golden",
schema: "http_urlSchema",
input: { value: "https://localhost:8080/path" },
success: true,
},
{
name: "http_url: v4 accepts IP address with port",
golden: "TestFormatValidators/format_only/v4.golden",
schema: "http_urlSchema",
input: { value: "http://127.0.0.1:9000/x" },
success: true,
},
{
name: "http_url: v4 accepts single-label intranet host",
golden: "TestFormatValidators/format_only/v4.golden",
schema: "http_urlSchema",
input: { value: "https://intranet-host/browse/X" },
success: true,
},
{
name: "http_url: v4 rejects missing host",
golden: "TestFormatValidators/format_only/v4.golden",
schema: "http_urlSchema",
input: { value: "http://" },
success: false,
},

// --- ipv4Schema ---
{
Expand Down
4 changes: 2 additions & 2 deletions zod.go
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ func (c *Converter) parseStringValidators(validate string) []stringValidator {
case valName == "omitempty":
// skip
case valName == "oneof" && valValue != "":
vals := splitParamsRegex.FindAllString(rawPart[len("oneof="):], -1)
vals := splitParamsRegex.FindAllString(valValue, -1)
for i := range vals {
vals[i] = escapeJSString(strings.ReplaceAll(vals[i], "'", ""))
}
Expand Down Expand Up @@ -1410,7 +1410,7 @@ func (c *Converter) renderV4FormatCheck(v stringValidator) string {
case "url":
return ".check(z.url())"
case "http_url":
return ".check(z.httpUrl())"
return ".check(z.url({ protocol: /^https?$/ }))"
case "ipv4", "ip4_addr":
return ".check(z.ipv4())"
case "ipv6", "ip6_addr":
Expand Down