Repository: getkin/kin-openapi
Pull request #127 (2019-10-05 06:08:02)

Fix server url matching
Current `MatchRawUrl` implementation for an openapi3 `Server` attempts to skip the url input to either `.` or `/` characters when encountering a variable instead of continuing to match the expected template:
https://github.com/getkin/kin-openapi/blob/c856265a25d59d95e8fcb863d779da672c0ac489/openapi3/server.go#L79

This fails in instances where a user is trying to match prefixed or suffixed templates:
- `http://example.com/a/prefix-{arg3}-suffix`
or simply trying to match any variable with a `.` in the value:
- `http://example.com/a/1.0.0` does not match `http://example.com/a/{arg3}` but `http://example.com/a/1-0-0` does.

This fix replaces `.` with the next character in the pattern or `/` whichever comes first.


Patch:
diff --git a/README.md b/README.md
index e979cfa..45b4be9 100644
--- a/README.md
+++ b/README.md
@@ -50,8 +50,8 @@ func GetOperation(httpRequest *http.Request) (*openapi3.Operation, error) {
   router := openapi3filter.NewRouter().WithSwaggerFromFile("swagger.json")
 
   // Find route
-  route, _, err := router.FindRoute("GET", req.URL.String())
-  if err!=nil {
+  route, _, err := router.FindRoute("GET", req.URL)
+  if err != nil {
     return nil, err
   }
 
diff --git a/openapi3/schema.go b/openapi3/schema.go
index f96b6a0..b68501f 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -1139,10 +1139,10 @@ func (schema *Schema) expectedType(typ string, fast bool) error {
 		return errSchema
 	}
 	return &SchemaError{
-		Value:       schema.Type,
+		Value:       typ,
 		Schema:      schema,
 		SchemaField: "type",
-		Reason:      "Field must be set to " + typ + " or not be present",
+		Reason:      "Field must be set to " + schema.Type + " or not be present",
 	}
 }
 
diff --git a/openapi3/server.go b/openapi3/server.go
index 6bb863e..4392b09 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -3,6 +3,7 @@ package openapi3
 import (
 	"context"
 	"errors"
+	"math"
 	"net/url"
 	"strings"
 )
@@ -75,8 +76,20 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) {
 			}
 			pattern = pattern[i+1:]
 
-			// Find next '.' or '/'
-			i = strings.IndexAny(input, "./")
+			// Find next matching pattern character or next '/' whichever comes first
+			np := -1
+			if len(pattern) > 0 {
+				np = strings.IndexByte(input, pattern[0])
+			}
+			ns := strings.IndexByte(input, '/')
+
+			if np < 0 {
+				i = ns
+			} else if ns < 0 {
+				i = np
+			} else {
+				i = int(math.Min(float64(np), float64(ns)))
+			}
 			if i < 0 {
 				i = len(input)
 			}
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 1009606..2f1d760 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -678,7 +678,7 @@ func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error)
 	case "integer":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid interger", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid integer", Cause: err}
 		}
 		return v, nil
 	case "number":


Test patch:
diff --git a/openapi3/server_test.go b/openapi3/server_test.go
index 5eeb8bd..b877a35 100644
--- a/openapi3/server_test.go
+++ b/openapi3/server_test.go
@@ -20,16 +20,21 @@ func TestServerParamNames(t *testing.T) {
 
 func TestServerParamValuesWithPath(t *testing.T) {
 	server := &openapi3.Server{
-		URL: "http://{arg0}.{arg1}.example.com/a/b",
+		URL: "http://{arg0}.{arg1}.example.com/a/{arg3}-version/{arg4}c{arg5}",
 	}
 	for input, expected := range map[string]*serverMatch{
-		"http://x.example.com/a/b":                 nil,
-		"http://x.y.example.com/":                  nil,
-		"http://x.y.example.com/a/":                nil,
-		"http://x.y.example.com/a/b":               newServerMatch("/", "x", "y"),
-		"http://x.y.example.com/a/b/":              newServerMatch("/", "x", "y"),
-		"http://x.y.example.com/a/b/c":             newServerMatch("/c", "x", "y"),
-		"http://domain0.domain1.example.com/a/b/c": newServerMatch("/c", "domain0", "domain1"),
+		"http://x.example.com/a/b":                                    nil,
+		"http://x.y.example.com/":                                     nil,
+		"http://x.y.example.com/a/":                                   nil,
+		"http://x.y.example.com/a/c":                                  nil,
+		"http://baddomain.com/.example.com/a/1.0.0-version/c/d":       nil,
+		"http://baddomain.com/.example.com/a/1.0.0/2/2.0.0-version/c": nil,
+		"http://x.y.example.com/a/b-version/prefixedc":                newServerMatch("/", "x", "y", "b", "prefixed", ""),
+		"http://x.y.example.com/a/b-version/c":                        newServerMatch("/", "x", "y", "b", "", ""),
+		"http://x.y.example.com/a/b-version/c/":                       newServerMatch("/", "x", "y", "b", "", ""),
+		"http://x.y.example.com/a/b-version/c/d":                      newServerMatch("/d", "x", "y", "b", "", ""),
+		"http://domain0.domain1.example.com/a/b-version/c/d":          newServerMatch("/d", "domain0", "domain1", "b", "", ""),
+		"http://domain0.domain1.example.com/a/1.0.0-version/c/d":      newServerMatch("/d", "domain0", "domain1", "1.0.0", "", ""),
 	} {
 		t.Run(input, testServerParamValues(t, server, input, expected))
 	}