Repository: getkin/kin-openapi
Pull request #67 (2019-02-10 15:16:04)

Support loading specifications from YAML files
Enables YAML format support for specifications. Since JSON is a subset of YAML, we can just use the YAML parser to read both (thanks @fenollp).

I also removed `LoadSwaggerFromYAMLData` from `swagger_loader` as it does the same as `LoadSwaggerFromData` now and adjusted the tests accordingly.

fenollp: cc https://github.com/getkin/kin-openapi/pull/59
LGTM. Im on my phone rn. Will merge once I’m home. Thanks :)

Patch:
diff --git a/.gitignore b/.gitignore
index a1338d6..a5bf17c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,10 @@
 
 # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
 .glide/
+
+ # Macos file system
+.DS_Store
+
+ # IntelliJ / GoLand
+.idea
+
diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go
index 375d30b..d667462 100644
--- a/openapi3/swagger_loader.go
+++ b/openapi3/swagger_loader.go
@@ -2,7 +2,6 @@ package openapi3
 
 import (
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io/ioutil"
@@ -90,26 +89,18 @@ func (swaggerLoader *SwaggerLoader) LoadSwaggerFromFile(path string) (*Swagger,
 
 func (swaggerLoader *SwaggerLoader) LoadSwaggerFromData(data []byte) (*Swagger, error) {
 	swagger := &Swagger{}
-	if err := json.Unmarshal(data, swagger); err != nil {
+	if err := yaml.Unmarshal(data, swagger); err != nil {
 		return nil, err
 	}
 	return swagger, swaggerLoader.ResolveRefsIn(swagger, nil)
 }
 
 func (swaggerLoader *SwaggerLoader) LoadSwaggerFromDataWithPath(data []byte, path *url.URL) (*Swagger, error) {
-	swagger := &Swagger{}
-	if err := json.Unmarshal(data, swagger); err != nil {
-		return nil, err
-	}
-	return swagger, swaggerLoader.ResolveRefsIn(swagger, path)
-}
-
-func (swaggerLoader *SwaggerLoader) LoadSwaggerFromYAMLData(data []byte) (*Swagger, error) {
 	swagger := &Swagger{}
 	if err := yaml.Unmarshal(data, swagger); err != nil {
 		return nil, err
 	}
-	return swagger, swaggerLoader.ResolveRefsIn(swagger, nil)
+	return swagger, swaggerLoader.ResolveRefsIn(swagger, path)
 }
 
 func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, path *url.URL) (err error) {


Test patch:
diff --git a/openapi3/swagger_loader_test.go b/openapi3/swagger_loader_test.go
index f132916..1e1bcae 100644
--- a/openapi3/swagger_loader_test.go
+++ b/openapi3/swagger_loader_test.go
@@ -56,7 +56,7 @@ paths:
 `)
 
 	loader := openapi3.NewSwaggerLoader()
-	doc, err := loader.LoadSwaggerFromYAMLData(spec)
+	doc, err := loader.LoadSwaggerFromData(spec)
 	require.NoError(t, err)
 	require.Equal(t, "An API", doc.Info.Title)
 	require.Equal(t, 2, len(doc.Components.Schemas))
@@ -123,7 +123,7 @@ paths:
                 test:
                   $ref: '#/components/examples/test'`)
 	loader := openapi3.NewSwaggerLoader()
-	doc, err := loader.LoadSwaggerFromYAMLData(source)
+	doc, err := loader.LoadSwaggerFromData(source)
 	require.NoError(t, err)
 
 	err = doc.Validate(loader.Context)
@@ -224,7 +224,7 @@ paths:
 `)
 
 	loader := openapi3.NewSwaggerLoader()
-	_, err := loader.LoadSwaggerFromYAMLData(spec)
+	_, err := loader.LoadSwaggerFromData(spec)
 	require.Error(t, err)
 }
 
@@ -252,7 +252,7 @@ paths:
 `)
 
 	loader := openapi3.NewSwaggerLoader()
-	swagger, err := loader.LoadSwaggerFromYAMLData(spec)
+	swagger, err := loader.LoadSwaggerFromData(spec)
 	require.NoError(t, err)
 
 	require.NotNil(t, swagger.Paths["/"].Parameters[0].Value)
@@ -284,7 +284,7 @@ paths:
 `)
 
 	loader := openapi3.NewSwaggerLoader()
-	swagger, err := loader.LoadSwaggerFromYAMLData(spec)
+	swagger, err := loader.LoadSwaggerFromData(spec)
 	require.NoError(t, err)
 
 	require.NotNil(t, swagger.Paths["/"].Post.RequestBody.Value.Content.Get("application/json").Examples["test"])
@@ -405,3 +405,21 @@ func TestLoadFromDataWithExternalRequestResponseHeaderRemoteRef(t *testing.T) {
 	require.NotNil(t, swagger.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
 	require.Equal(t, "description", swagger.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
 }
+
+func TestLoadYamlFile(t *testing.T) {
+	loader := openapi3.NewSwaggerLoader()
+	loader.IsExternalRefsAllowed = true
+	swagger, err := loader.LoadSwaggerFromFile("testdata/test.openapi.yml")
+	require.NoError(t, err)
+
+	require.Equal(t, "OAI Specification in YAML", swagger.Info.Title)
+}
+
+func TestLoadYamlFileWithExternalSchemaRef(t *testing.T) {
+	loader := openapi3.NewSwaggerLoader()
+	loader.IsExternalRefsAllowed = true
+	swagger, err := loader.LoadSwaggerFromFile("testdata/testref.openapi.yml")
+	require.NoError(t, err)
+
+	require.NotNil(t, swagger.Components.Schemas["AnotherTestSchema"].Value.Type)
+}
diff --git a/openapi3/swagger_test.go b/openapi3/swagger_test.go
index 363e453..a57a124 100644
--- a/openapi3/swagger_test.go
+++ b/openapi3/swagger_test.go
@@ -65,7 +65,7 @@ func TestRefsYAML(t *testing.T) {
 	err = loader.ResolveRefsIn(docA, nil)
 	require.NoError(t, err)
 	t.Log("Resolve refs in marshalled *openapi3.Swagger")
-	docB, err := loader.LoadSwaggerFromYAMLData(data)
+	docB, err := loader.LoadSwaggerFromData(data)
 	require.NoError(t, err)
 	require.NotEmpty(t, docB)
 
diff --git a/openapi3/testdata/components.openapi.yml b/openapi3/testdata/components.openapi.yml
new file mode 100644
index 0000000..6c30742
--- /dev/null
+++ b/openapi3/testdata/components.openapi.yml
@@ -0,0 +1,44 @@
+---
+openapi: 3.0.0
+info:
+  title: ''
+  version: '1'
+paths: {}
+components:
+  schemas:
+    Name:
+      type: string
+    CustomTestSchema:
+      "$ref": "#/components/schemas/Name"
+  responses:
+    Name:
+      description: description
+    CustomTestResponse:
+      "$ref": "#/components/responses/Name"
+  parameters:
+    Name:
+      name: id
+      in: header
+    CustomTestParameter:
+      "$ref": "#/components/parameters/Name"
+  examples:
+    Name:
+      description: description
+    CustomTestExample:
+      "$ref": "#/components/examples/Name"
+  requestBodies:
+    Name:
+      content: {}
+    CustomTestRequestBody:
+      "$ref": "#/components/requestBodies/Name"
+  headers:
+    Name:
+      description: description
+    CustomTestHeader:
+      "$ref": "#/components/headers/Name"
+  securitySchemes:
+    Name:
+      type: cookie
+      description: description
+    CustomTestSecurityScheme:
+      "$ref": "#/components/securitySchemes/Name"
diff --git a/openapi3/testdata/test.openapi.yml b/openapi3/testdata/test.openapi.yml
new file mode 100644
index 0000000..b1ff8e5
--- /dev/null
+++ b/openapi3/testdata/test.openapi.yml
@@ -0,0 +1,10 @@
+---
+openapi: 3.0.0
+info:
+  title: 'OAI Specification in YAML'
+  version: 0.0.1
+paths: {}
+components:
+  schemas:
+    TestSchema:
+      type: string
diff --git a/openapi3/testdata/testref.openapi.yml b/openapi3/testdata/testref.openapi.yml
new file mode 100644
index 0000000..fe75593
--- /dev/null
+++ b/openapi3/testdata/testref.openapi.yml
@@ -0,0 +1,10 @@
+---
+openapi: 3.0.0
+info:
+  title: 'OAI Specification w/ refs in YAML'
+  version: '1'
+paths: {}
+components:
+  schemas:
+    AnotherTestSchema:
+      "$ref": components.openapi.yml#/components/schemas/CustomTestSchema