Repository: getkin/kin-openapi (go)

Issue:
Getting `Found unresolved ref` errors during parse swagger 2.0 contract
I just tried to test this project with our swagger 2.0 json file. During implementation, i couldn't find a life-saver v2 to v3 converter func or auto converter something like: 

```
For 3.0: _ = openapi3filter.NewRouter().WithSwaggerV3FromFile("swagger.json")
For 2.0: _ = openapi3filter.NewRouter().WithSwaggerV2FromFile("swagger.json")
```

I found [this](https://github.com/getkin/kin-openapi/issues/113#issuecomment-549157194) simple implementation:

```
func V2ToV3(v2Data []byte) (*openapi3.Swagger, error) {
	v2Spec := &openapi2.Swagger{}
	if err := json.Unmarshal(v2Data, v2Spec); err != nil {
		return nil, err
	}
	v3Spec, err := openapi2conv.ToV3Swagger(v2Spec)
	if err != nil {
		return nil, err
	}
	return v3Spec, nil
}
```

Anyway, the main question is that actually why i am getting `Found unresolved ref errors`. `openapi3filter` couldn't recognize our `$ref` model i think. Simplified `json` is here:

```
{
  "swagger": "2.0",
  "info": {
    "description": "Test Golang Application",
    "version": "1.0",
    "title": "Test",
    "contact": {
      "name": "Test",
      "email": "test@test.com"
    }
  },
  "host": "",
  "basePath": "/test",
  "definitions": {
    "model.ProductSearchAttributeRequest": {
      "type": "object",
      "properties": {
        "filterField": {
          "type": "string"
        },
        "filterKey": {
          "type": "string"
        },
        "type": {
          "type": "string"
        },
        "values": {
          "$ref": "#/definitions/model.ProductSearchAttributeValueRequest"
        }
      },
      "title": "model.ProductSearchAttributeRequest"
    },
    "model.ProductSearchAttributeValueRequest": {
      "type": "object",
      "properties": {
        "imageUrl": {
          "type": "string"
        },
        "text": {
          "type": "string"
        }
      },
      "title": "model.ProductSearchAttributeValueRequest"
    }
  }
}
```

`main` func:

```
func main() {
	input, err := ioutil.ReadFile("min.json")
	if err != nil {
		panic(err)
	}
	oapi3, err := V2ToV3(input)
	if err != nil {
		panic(err)
	}
	_ = openapi3filter.NewRouter().WithSwagger(oapi3)
}
```

Output:

`panic: Validating Swagger failed: Found unresolved ref: '#/definitions/model.ProductSearchAttributeValueRequest'`

So, `#/definitions/model.ProductSearchAttributeValueRequest` is defined under `definitions` as we see. That simplified json is **auto-generated** file from [Swag](https://github.com/swaggo/swag). I don't know why it throws an error, i think it shouldn't. Should it?



Pull request description:
Fix wrong `Found unresolved ref` error when converting from Swagger/OpenAPIv2 spec
Fixes #187 

Patch:
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index dada637..408e3f5 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -23,13 +23,11 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 		ExtensionProps: swagger.ExtensionProps,
 		ExternalDocs:   swagger.ExternalDocs,
 	}
-	host := swagger.Host
-	if len(host) > 0 {
+
+	if host := swagger.Host; len(host) > 0 {
 		schemes := swagger.Schemes
 		if len(schemes) == 0 {
-			schemes = []string{
-				"https://",
-			}
+			schemes = []string{"https://"}
 		}
 		basePath := swagger.BasePath
 		for _, scheme := range schemes {
@@ -38,11 +36,10 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 				Host:   host,
 				Path:   basePath,
 			}
-			result.AddServer(&openapi3.Server{
-				URL: u.String(),
-			})
+			result.AddServer(&openapi3.Server{URL: u.String()})
 		}
 	}
+
 	if paths := swagger.Paths; paths != nil {
 		resultPaths := make(map[string]*openapi3.PathItem, len(paths))
 		for path, pathItem := range paths {
@@ -54,6 +51,7 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 		}
 		result.Paths = resultPaths
 	}
+
 	if parameters := swagger.Parameters; parameters != nil {
 		result.Components.Parameters = make(map[string]*openapi3.ParameterRef)
 		result.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef)
@@ -70,6 +68,7 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 			}
 		}
 	}
+
 	if responses := swagger.Responses; responses != nil {
 		result.Components.Responses = make(map[string]*openapi3.ResponseRef, len(responses))
 		for k, response := range responses {
@@ -80,7 +79,9 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 			result.Components.Responses[k] = r
 		}
 	}
+
 	result.Components.Schemas = ToV3Schemas(swagger.Definitions)
+
 	if m := swagger.SecurityDefinitions; m != nil {
 		resultSecuritySchemes := make(map[string]*openapi3.SecuritySchemeRef)
 		for k, v := range m {
@@ -92,7 +93,15 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 		}
 		result.Components.SecuritySchemes = resultSecuritySchemes
 	}
+
 	result.Security = ToV3SecurityRequirements(swagger.Security)
+
+	{
+		sl := openapi3.NewSwaggerLoader()
+		if err := sl.ResolveRefsIn(result, nil); err != nil {
+			return nil, err
+		}
+	}
 	return result, nil
 }
 
@@ -508,7 +517,7 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
 		return schema
 	}
 	if schema.Value.Items != nil {
-		schema.Value.Items = FromV3SchemaRef((schema.Value.Items))
+		schema.Value.Items = FromV3SchemaRef(schema.Value.Items)
 	}
 	for k, v := range schema.Value.Properties {
 		schema.Value.Properties[k] = FromV3SchemaRef(v)
@@ -814,7 +823,7 @@ var attemptedBodyParameterNames = []string{
 }
 
 func stripNonCustomExtensions(extensions map[string]interface{}) {
-	for extName, _ := range extensions {
+	for extName := range extensions {
 		if !strings.HasPrefix(extName, "x-") {
 			delete(extensions, extName)
 		}
diff --git a/openapi3/components.go b/openapi3/components.go
index 78b66aa..a8ebd79 100644
--- a/openapi3/components.go
+++ b/openapi3/components.go
@@ -92,7 +92,7 @@ func (components *Components) Validate(c context.Context) (err error) {
 	return
 }
 
-const identifierPattern = `^[a-zA-Z0-9.\-_]+$`
+const identifierPattern = `^[a-zA-Z0-9._-]+$`
 
 var identifierRegExp = regexp.MustCompile(identifierPattern)
 
@@ -100,5 +100,5 @@ func ValidateIdentifier(value string) error {
 	if identifierRegExp.MatchString(value) {
 		return nil
 	}
-	return fmt.Errorf("Identifier '%s' is not supported by OpenAPI version 3 standard (regexp: '%s')", value, identifierPattern)
+	return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (regexp: %q)", value, identifierPattern)
 }


Test patch:
diff --git a/openapi2conv/issue187_test.go b/openapi2conv/issue187_test.go
new file mode 100644
index 0000000..0fccf9d
--- /dev/null
+++ b/openapi2conv/issue187_test.go
@@ -0,0 +1,100 @@
+package openapi2conv
+
+import (
+	"context"
+	"encoding/json"
+	"testing"
+
+	"github.com/getkin/kin-openapi/openapi2"
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/stretchr/testify/require"
+)
+
+func v2v3(spec2 []byte) (doc3 *openapi3.Swagger, err error) {
+	var doc2 openapi2.Swagger
+	if err = json.Unmarshal(spec2, &doc2); err != nil {
+		return
+	}
+	doc3, err = ToV3Swagger(&doc2)
+	return
+}
+
+func TestIssue187(t *testing.T) {
+	spec := `
+{
+  "swagger": "2.0",
+  "info": {
+    "description": "Test Golang Application",
+    "version": "1.0",
+    "title": "Test",
+    "contact": {
+      "name": "Test",
+      "email": "test@test.com"
+    }
+  },
+
+  "paths": {
+    "/me": {
+        "get": {
+          "description": "",
+          "operationId": "someTest",
+          "summary": "Some test",
+          "tags": ["probe"],
+          "produces": ["application/json"],
+          "responses": {
+            "200": {
+              "description": "successful operation",
+              "schema": {"$ref": "#/definitions/model.ProductSearchAttributeRequest"}
+            }
+          }
+        }
+      }
+  },
+
+  "host": "",
+  "basePath": "/test",
+  "definitions": {
+    "model.ProductSearchAttributeRequest": {
+      "type": "object",
+      "properties": {
+        "filterField": {
+          "type": "string"
+        },
+        "filterKey": {
+          "type": "string"
+        },
+        "type": {
+          "type": "string"
+        },
+        "values": {
+          "$ref": "#/definitions/model.ProductSearchAttributeValueRequest"
+        }
+      },
+      "title": "model.ProductSearchAttributeRequest"
+    },
+    "model.ProductSearchAttributeValueRequest": {
+      "type": "object",
+      "properties": {
+        "imageUrl": {
+          "type": "string"
+        },
+        "text": {
+          "type": "string"
+        }
+      },
+      "title": "model.ProductSearchAttributeValueRequest"
+    }
+  }
+}
+`
+	doc3, err := v2v3([]byte(spec))
+	require.NoError(t, err)
+
+	spec3, err := json.Marshal(doc3)
+	require.NoError(t, err)
+	const expected = `{"components":{"schemas":{"model.ProductSearchAttributeRequest":{"properties":{"filterField":{"type":"string"},"filterKey":{"type":"string"},"type":{"type":"string"},"values":{"$ref":"#/components/schemas/model.ProductSearchAttributeValueRequest"}},"title":"model.ProductSearchAttributeRequest","type":"object"},"model.ProductSearchAttributeValueRequest":{"properties":{"imageUrl":{"type":"string"},"text":{"type":"string"}},"title":"model.ProductSearchAttributeValueRequest","type":"object"}}},"info":{"contact":{"email":"test@test.com","name":"Test"},"description":"Test Golang Application","title":"Test","version":"1.0"},"openapi":"3.0.2","paths":{"/me":{"get":{"operationId":"someTest","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/model.ProductSearchAttributeRequest"}}},"description":"successful operation"}},"summary":"Some test","tags":["probe"]}}}}`
+	require.Equal(t, string(spec3), expected)
+
+	err = doc3.Validate(context.Background())
+	require.NoError(t, err)
+}