Repository: Yelp/swagger_spec_validator

Issue:
Make use of spec_url being passed to `validate_spec`
`spec_url` [here](https://github.com/Yelp/swagger_spec_validator/blob/master/swagger_spec_validator/validator20.py#L27) is not getting used as of now. Use it to resolve `$ref`s.

Discussion:

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 1624c2b..c55f47a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+Version 1.1.0 (2015-08-24)
+-------------
+ * Validate crossrefs - #33, #34
+
 Version 1.0.12 (2015-07-02)
 -------------
  * Handle API level parameters - #29
diff --git a/swagger_spec_validator/__about__.py b/swagger_spec_validator/__about__.py
index 2ef8969..75e6007 100644
--- a/swagger_spec_validator/__about__.py
+++ b/swagger_spec_validator/__about__.py
@@ -7,7 +7,7 @@ __title__ = "swagger_spec_validator"
 __summary__ = "Validation of Swagger specifications"
 __uri__ = "http://github.com/Yelp/swagger_spec_validator"
 
-__version__ = "1.0.12"
+__version__ = "1.1.0"
 
 __author__ = "John Billings"
 __email__ = "billings@yelp.com"
diff --git a/swagger_spec_validator/util.py b/swagger_spec_validator/util.py
index 1975850..ff60fc3 100644
--- a/swagger_spec_validator/util.py
+++ b/swagger_spec_validator/util.py
@@ -44,7 +44,9 @@ def validate_spec_url(spec_url):
 
     :param spec_url:
       For Swagger 1.2, this is the URL to the resource listing in api-docs.
-      For Swagger 2.0, this is the URL to swagger.json in api-docs.
+      For Swagger 2.0, this is the URL to swagger.json in api-docs. If given
+                       as `file://` this must be an absolute url for
+                       cross-refs to work correctly.
     """
     spec_json = load_json(spec_url)
     validator = get_validator(spec_json, spec_url)
diff --git a/swagger_spec_validator/validator20.py b/swagger_spec_validator/validator20.py
index e1d1a94..4882f24 100644
--- a/swagger_spec_validator/validator20.py
+++ b/swagger_spec_validator/validator20.py
@@ -21,16 +21,17 @@ def validate_spec_url(spec_url):
     :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
     """
     log.info('Validating %s' % spec_url)
-    validate_spec(load_json(spec_url))
+    validate_spec(load_json(spec_url), spec_url)
 
 
-def validate_spec(spec_json, _spec_url=None):
+def validate_spec(spec_json, spec_url=None):
     """Validates a Swagger 2.0 API Specification given a Swagger Spec.
 
     :param spec_json: the json dict of the swagger spec.
     :type spec_json: dict
-    :param _spec_url: url serving the spec json (currently not used)
-    :type _spec_url: string
+    :param spec_url: url serving the spec json. Used for dereferencing
+                     relative refs. eg: file:///foo/swagger.json
+    :type spec_url: string
     :returns: `None` in case of success, otherwise raises an exception.
     :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
     """
@@ -38,7 +39,8 @@ def validate_spec(spec_json, _spec_url=None):
 
     # Dereference all $refs so we don't have to deal with them
     fix_malformed_model_refs(spec_json)
-    spec_json = jsonref.JsonRef.replace_refs(spec_json)
+    spec_json = jsonref.JsonRef.replace_refs(spec_json,
+                                             base_uri=spec_url or '')
     replace_jsonref_proxies(spec_json)
 
     # TODO: Extract 'parameters', 'responses' from the spec as well.
@@ -159,7 +161,9 @@ def replace_jsonref_proxies(obj):
                     fragment[k] = v.__subject__
                 descend(fragment[k])
         elif isinstance(fragment, list):
-            for element in fragment:
+            for index, element in enumerate(fragment):
+                if isinstance(element, jsonref.JsonRef):
+                    fragment[index] = element.__subject__
                 descend(element)
 
     descend(obj)


Test patch:
diff --git a/tests/data/v2.0/pingpong.json b/tests/data/v2.0/pingpong.json
new file mode 100644
index 0000000..541d9ee
--- /dev/null
+++ b/tests/data/v2.0/pingpong.json
@@ -0,0 +1,21 @@
+{
+  "ping": {
+    "get": {
+      "operationId": "ping",
+      "parameters": [
+          {
+            "name": "pung",
+            "in": "query",
+            "description": "true or false",
+            "type": "boolean"
+          }
+      ],
+      "responses": {
+        "200": {
+          "description": "Successful response"
+        }
+      }
+    }
+  }
+}
+
diff --git a/tests/data/v2.0/relative_ref.json b/tests/data/v2.0/relative_ref.json
new file mode 100644
index 0000000..0c9f600
--- /dev/null
+++ b/tests/data/v2.0/relative_ref.json
@@ -0,0 +1,19 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "version": "1.0.0",
+    "title": "Simple"
+  },
+  "tags": [
+      {
+          "name": "pingpong",
+          "description": "pingpong related specs"
+      }
+  ],
+  "paths": {
+    "/ping": {
+        "$ref": "pingpong.json#/ping"
+    }
+  }
+}
+
diff --git a/tests/validator20/validate_spec_url_test.py b/tests/validator20/validate_spec_url_test.py
index 890d7d2..4112eeb 100644
--- a/tests/validator20/validate_spec_url_test.py
+++ b/tests/validator20/validate_spec_url_test.py
@@ -1,5 +1,6 @@
 import json
 import mock
+import os
 import pytest
 
 from swagger_spec_validator.common import SwaggerValidationError
@@ -14,6 +15,13 @@ def test_success(petstore_contents):
         mock_load_json.assert_called_once_with('http://localhost/api-docs')
 
 
+def test_success_crossref_url():
+    my_dir = os.path.abspath(os.path.dirname(__file__))
+    urlpath = "file://{0}".format(os.path.join(
+        my_dir, "../data/v2.0/relative_ref.json"))
+    validate_spec_url(urlpath)
+
+
 def test_raise_SwaggerValidationError_on_urlopen_error():
     with pytest.raises(SwaggerValidationError) as excinfo:
         validate_spec_url('http://foo')