Repository: getkin/kin-openapi (go)

Issue:
input matches more than one oneOf schemas when trying to validate IPv4 or IPv6 addresses
Given this schema definition snippet for validating a string to be either a correct IPv4 or IPv6 textual representation:

```yaml
        IPvX-Address:
            description: |-
                Represents either an IPv4 or IPv6 address (without prefix or any
                associated lifetimes, et cetera)
            type: string
            oneOf:
                - type: string
                  format: ipv4
                - type: string
                  format: ipv6

```

and using github.com/getkin/kin-openapi v0.76.0 I end up with for a value, such as `Value: <string>"127.0.1.1"` with this validation errorr: `input matches more than one oneOf schemas`.

Am I defining IPvX-Address wrong or is this an issue with kin-openapi v0.76.0?

Pull request:
sort out possible mishandling of ipv4 vs v6
fixes #430 

Patch:
diff --git a/openapi3/schema_formats.go b/openapi3/schema_formats.go
index 1eb4150..095bb22 100644
--- a/openapi3/schema_formats.go
+++ b/openapi3/schema_formats.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"net"
 	"regexp"
+	"strings"
 )
 
 const (
@@ -37,24 +38,23 @@ func DefineStringFormatCallback(name string, callback FormatCallback) {
 	SchemaStringFormats[name] = Format{callback: callback}
 }
 
-func validateIP(ip string) (*net.IP, error) {
+func validateIP(ip string) error {
 	parsed := net.ParseIP(ip)
 	if parsed == nil {
-		return nil, &SchemaError{
+		return &SchemaError{
 			Value:  ip,
 			Reason: "Not an IP address",
 		}
 	}
-	return &parsed, nil
+	return nil
 }
 
 func validateIPv4(ip string) error {
-	parsed, err := validateIP(ip)
-	if err != nil {
+	if err := validateIP(ip); err != nil {
 		return err
 	}
 
-	if parsed.To4() == nil {
+	if !(strings.Count(ip, ":") < 2) {
 		return &SchemaError{
 			Value:  ip,
 			Reason: "Not an IPv4 address (it's IPv6)",
@@ -62,13 +62,13 @@ func validateIPv4(ip string) error {
 	}
 	return nil
 }
+
 func validateIPv6(ip string) error {
-	parsed, err := validateIP(ip)
-	if err != nil {
+	if err := validateIP(ip); err != nil {
 		return err
 	}
 
-	if parsed.To4() != nil {
+	if !(strings.Count(ip, ":") >= 2) {
 		return &SchemaError{
 			Value:  ip,
 			Reason: "Not an IPv6 address (it's IPv4)",


Test patch:
diff --git a/openapi3/schema_formats_test.go b/openapi3/schema_formats_test.go
new file mode 100644
index 0000000..14733c8
--- /dev/null
+++ b/openapi3/schema_formats_test.go
@@ -0,0 +1,57 @@
+package openapi3
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue430(t *testing.T) {
+	schema := NewOneOfSchema(
+		NewStringSchema().WithFormat("ipv4"),
+		NewStringSchema().WithFormat("ipv6"),
+	)
+
+	err := schema.Validate(context.Background())
+	require.NoError(t, err)
+
+	data := map[string]bool{
+		"127.0.1.1": true,
+
+		// https://stackoverflow.com/a/48519490/1418165
+
+		// v4
+		"192.168.0.1": true,
+		// "192.168.0.1:80" doesn't parse per net.ParseIP()
+
+		// v6
+		"::FFFF:C0A8:1":                        false,
+		"::FFFF:C0A8:0001":                     false,
+		"0000:0000:0000:0000:0000:FFFF:C0A8:1": false,
+		// "::FFFF:C0A8:1%1" doesn't parse per net.ParseIP()
+		"::FFFF:192.168.0.1": false,
+		// "[::FFFF:C0A8:1]:80" doesn't parse per net.ParseIP()
+		// "[::FFFF:C0A8:1%1]:80" doesn't parse per net.ParseIP()
+	}
+
+	for datum := range data {
+		err = schema.VisitJSON(datum)
+		require.Error(t, err, ErrOneOfConflict.Error())
+	}
+
+	DefineIPv4Format()
+	DefineIPv6Format()
+
+	for datum, isV4 := range data {
+		err = schema.VisitJSON(datum)
+		require.NoError(t, err)
+		if isV4 {
+			require.Nil(t, validateIPv4(datum), "%q should be IPv4", datum)
+			require.NotNil(t, validateIPv6(datum), "%q should not be IPv6", datum)
+		} else {
+			require.NotNil(t, validateIPv4(datum), "%q should not be IPv4", datum)
+			require.Nil(t, validateIPv6(datum), "%q should be IPv6", datum)
+		}
+	}
+}