diff --git a/docs/docs/features/request-inputs.md b/docs/docs/features/request-inputs.md index f6084d24..849d074e 100644 --- a/docs/docs/features/request-inputs.md +++ b/docs/docs/features/request-inputs.md @@ -102,7 +102,7 @@ The following special types are supported out of the box: | `time.Time` | `{"type": "string", "format": "date-time"}` | `"2020-01-01T12:00:00Z"` | | `url.URL` | `{"type": "string", "format": "uri"}` | `"https://example.com"` | | `net.IP` | `{"type": "string", "format": "ipv4"}` | `"127.0.0.1"` | -| `netip.Addr` | `{"type": "string", "format": "ipv4"}` | `"127.0.0.1"` | +| `netip.Addr` | `{"type": "string", "format": "ip"}` | `"127.0.0.1"` or `fe80::1` | | `json.RawMessage` | `{}` | `["whatever", "you", "want"]` | You can override this default behavior if needed as described in [Schema Customization](./schema-customization.md) and [Request Validation](./request-validation.md), e.g. setting a custom `format` tag for IPv6. diff --git a/schema.go b/schema.go index 94b4530d..6383e631 100644 --- a/schema.go +++ b/schema.go @@ -722,7 +722,7 @@ func schemaFromType(r Registry, t reflect.Type) *Schema { case ipType: return &Schema{Type: TypeString, Nullable: isPointer, Format: "ipv4"} case ipAddrType: - return &Schema{Type: TypeString, Nullable: isPointer, Format: "ipv4"} + return &Schema{Type: TypeString, Nullable: isPointer, Format: "ip"} case rawMessageType: return &Schema{} } diff --git a/schema_test.go b/schema_test.go index 18ab17a1..ac390197 100644 --- a/schema_test.go +++ b/schema_test.go @@ -190,7 +190,7 @@ func TestSchema(t *testing.T) { { name: "ipAddr", input: netip.AddrFrom4([4]byte{127, 0, 0, 1}), - expected: `{"type": "string", "format": "ipv4"}`, + expected: `{"type": "string", "format": "ip"}`, }, { name: "json.RawMessage", diff --git a/validate.go b/validate.go index b17d26fb..f597c6a1 100644 --- a/validate.go +++ b/validate.go @@ -6,6 +6,7 @@ import ( "math" "net" "net/mail" + "net/netip" "net/url" "reflect" "regexp" @@ -247,6 +248,10 @@ func validateFormat(path *PathBuffer, str string, s *Schema, res *ValidateResult if ip := net.ParseIP(str); ip == nil || ip.To16() == nil { res.Add(path, str, validation.MsgExpectedRFC2373IPv6) } + case "ip": + if _, err := netip.ParseAddr(str); err != nil { + res.Add(path, str, validation.MsgExpectedRFCIPAddr) + } case "uri", "uri-reference", "iri", "iri-reference": if _, err := url.Parse(str); err != nil { res.Add(path, str, ErrorFormatter(validation.MsgExpectedRFC3986URI, err)) diff --git a/validate_test.go b/validate_test.go index c0ab25a0..d842e640 100644 --- a/validate_test.go +++ b/validate_test.go @@ -433,6 +433,28 @@ var validateTests = []struct { input: map[string]any{"value": "1234"}, errs: []string{"expected string to be RFC 2373 ipv6"}, }, + { + name: "ipv4 success", + typ: reflect.TypeOf(struct { + Value string `json:"value" format:"ip"` + }{}), + input: map[string]any{"value": "127.0.0.1"}, + }, + { + name: "ipv6 success", + typ: reflect.TypeOf(struct { + Value string `json:"value" format:"ip"` + }{}), + input: map[string]any{"value": "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, + }, + { + name: "expected ipv4 or ipv6", + typ: reflect.TypeOf(struct { + Value string `json:"value" format:"ip"` + }{}), + input: map[string]any{"value": "1234"}, + errs: []string{"expected string to be either RFC 2673 ipv4 or RFC 2373 ipv6"}, + }, { name: "uri success", typ: reflect.TypeOf(struct { diff --git a/validation/messages.go b/validation/messages.go index 6cf2f7ea..e1243511 100644 --- a/validation/messages.go +++ b/validation/messages.go @@ -11,6 +11,7 @@ var ( MsgExpectedRFC5890Hostname = "expected string to be RFC 5890 hostname" MsgExpectedRFC2673IPv4 = "expected string to be RFC 2673 ipv4" MsgExpectedRFC2373IPv6 = "expected string to be RFC 2373 ipv6" + MsgExpectedRFCIPAddr = "expected string to be either RFC 2673 ipv4 or RFC 2373 ipv6" MsgExpectedRFC3986URI = "expected string to be RFC 3986 uri: %v" MsgExpectedRFC4122UUID = "expected string to be RFC 4122 uuid: %v" MsgExpectedRFC6570URITemplate = "expected string to be RFC 6570 uri-template"