Skip to content

proposal: extend netip.Addr support to ipv6 #792

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 docs/docs/features/request-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
Expand Down
2 changes: 1 addition & 1 deletion schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"math"
"net"
"net/mail"
"net/netip"
"net/url"
"reflect"
"regexp"
Expand Down Expand Up @@ -235,6 +236,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)
}
Comment on lines +239 to +242
Copy link
Owner

@danielgtaylor danielgtaylor Apr 22, 2025

Choose a reason for hiding this comment

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

@costela I love this! One concern I have is that ipv4 and ipv6 are actually in the JSON Schema spec but ip is not. See https://json-schema.org/understanding-json-schema/reference/type#ip-addresses.

I wonder if we should support all three? The two standard ones, and then ip for either one since Go supports it well. What do you think?

edit: just saw your other comment which is similar to mine, sorry 😂

case "uri", "uri-reference", "iri", "iri-reference":
if _, err := url.Parse(str); err != nil {
res.Add(path, str, ErrorFormatter(validation.MsgExpectedRFC3986URI, err))
Expand Down
22 changes: 22 additions & 0 deletions validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions validation/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down