diff --git a/README.md b/README.md index fdba115..cccc008 100644 --- a/README.md +++ b/README.md @@ -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(...)` diff --git a/testdata/TestFormatValidators/format_only/v4.golden b/testdata/TestFormatValidators/format_only/v4.golden index 96e56b5..c191fc9 100644 --- a/testdata/TestFormatValidators/format_only/v4.golden +++ b/testdata/TestFormatValidators/format_only/v4.golden @@ -11,7 +11,7 @@ export const urlSchema = z.object({ export type url = z.infer 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 diff --git a/testdata/TestFormatValidators/format_with_required/v4.golden b/testdata/TestFormatValidators/format_with_required/v4.golden index 7cf7050..d36256d 100644 --- a/testdata/TestFormatValidators/format_with_required/v4.golden +++ b/testdata/TestFormatValidators/format_with_required/v4.golden @@ -11,7 +11,7 @@ export const urlSchema = z.object({ export type url = z.infer 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 diff --git a/testdata/TestZodV4Defaults/string_formats_use_zod_v4_builders.golden b/testdata/TestZodV4Defaults/string_formats_use_zod_v4_builders.golden index f3bbb37..cda3cf2 100644 --- a/testdata/TestZodV4Defaults/string_formats_use_zod_v4_builders.golden +++ b/testdata/TestZodV4Defaults/string_formats_use_zod_v4_builders.golden @@ -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")), diff --git a/tests/cases.ts b/tests/cases.ts index c24b782..f96b3a9 100644 --- a/tests/cases.ts +++ b/tests/cases.ts @@ -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", @@ -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 --- { diff --git a/zod.go b/zod.go index 5e44fb2..fe9dfa8 100644 --- a/zod.go +++ b/zod.go @@ -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], "'", "")) } @@ -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":