diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c4473a3..cd34e3bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes - Relative paths are now allowed in securitySchemes/OAuthFlow/tokenUrl (#130). - Schema validation errors will no longer print a stack trace (#131). - +- Invalid YAML/URL will no longer print stack trace (#128) ## 0.5.0 - 2020-08-05 ### Changes diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index 0b753c5fc..4650d6295 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import Any, Dict, Optional, Sequence, Union +import httpcore import httpx import yaml from jinja2 import Environment, PackageLoader @@ -27,6 +28,8 @@ def _get_project_for_url_or_path(url: Optional[str], path: Optional[Path]) -> Union[_Project, GeneratorError]: data_dict = _get_document(url=url, path=path) + if isinstance(data_dict, GeneratorError): + return data_dict openapi = GeneratorData.from_dict(data_dict) if isinstance(openapi, GeneratorError): return openapi @@ -70,18 +73,24 @@ def update_existing_client(*, url: Optional[str], path: Optional[Path]) -> Seque return project.update() -def _get_document(*, url: Optional[str], path: Optional[Path]) -> Dict[str, Any]: +def _get_document(*, url: Optional[str], path: Optional[Path]) -> Union[Dict[str, Any], GeneratorError]: yaml_bytes: bytes if url is not None and path is not None: - raise ValueError("Provide URL or Path, not both.") + return GeneratorError(header="Provide URL or Path, not both.") if url is not None: - response = httpx.get(url) - yaml_bytes = response.content + try: + response = httpx.get(url) + yaml_bytes = response.content + except (httpx.HTTPError, httpcore.NetworkError): + return GeneratorError(header="Could not get OpenAPI document from provided URL") elif path is not None: yaml_bytes = path.read_bytes() else: - raise ValueError("No URL or Path provided") - return yaml.safe_load(yaml_bytes) + return GeneratorError(header="No URL or Path provided") + try: + return yaml.safe_load(yaml_bytes) + except yaml.YAMLError: + return GeneratorError(header="Invalid YAML from provided source") class _Project: diff --git a/tests/test___init__.py b/tests/test___init__.py index 809dd40ba..1700d638a 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -1,7 +1,9 @@ import pathlib +import httpcore import jinja2 import pytest +import yaml from openapi_python_client import GeneratorError @@ -44,6 +46,23 @@ def test__get_project_for_url_or_path_generator_error(mocker): assert project == error +def test__get_project_for_url_or_path_document_error(mocker): + error = GeneratorError() + _get_document = mocker.patch("openapi_python_client._get_document", return_value=error) + + from_dict = mocker.patch("openapi_python_client.parser.GeneratorData.from_dict") + url = mocker.MagicMock() + path = mocker.MagicMock() + + from openapi_python_client import _get_project_for_url_or_path + + project = _get_project_for_url_or_path(url=url, path=path) + + _get_document.assert_called_once_with(url=url, path=path) + from_dict.assert_not_called() + assert project == error + + def test_create_new_client(mocker): project = mocker.MagicMock() _get_project_for_url_or_path = mocker.patch( @@ -118,9 +137,9 @@ def test__get_document_no_url_or_path(self, mocker): from openapi_python_client import _get_document - with pytest.raises(ValueError): - _get_document(url=None, path=None) + result = _get_document(url=None, path=None) + assert result == GeneratorError(header="No URL or Path provided") get.assert_not_called() Path.assert_not_called() loads.assert_not_called() @@ -132,13 +151,28 @@ def test__get_document_url_and_path(self, mocker): from openapi_python_client import _get_document - with pytest.raises(ValueError): - _get_document(url=mocker.MagicMock(), path=mocker.MagicMock()) + result = _get_document(url=mocker.MagicMock(), path=mocker.MagicMock()) + assert result == GeneratorError(header="Provide URL or Path, not both.") get.assert_not_called() Path.assert_not_called() loads.assert_not_called() + def test__get_document_bad_url(self, mocker): + get = mocker.patch("httpx.get", side_effect=httpcore.NetworkError) + Path = mocker.patch("openapi_python_client.Path") + loads = mocker.patch("yaml.safe_load") + + from openapi_python_client import _get_document + + url = mocker.MagicMock() + result = _get_document(url=url, path=None) + + assert result == GeneratorError(header="Could not get OpenAPI document from provided URL") + get.assert_called_once_with(url) + Path.assert_not_called() + loads.assert_not_called() + def test__get_document_url_no_path(self, mocker): get = mocker.patch("httpx.get") Path = mocker.patch("openapi_python_client.Path") @@ -166,6 +200,20 @@ def test__get_document_path_no_url(self, mocker): path.read_bytes.assert_called_once() loads.assert_called_once_with(path.read_bytes()) + def test__get_document_bad_yaml(self, mocker): + get = mocker.patch("httpx.get") + loads = mocker.patch("yaml.safe_load", side_effect=yaml.YAMLError) + + from openapi_python_client import _get_document + + path = mocker.MagicMock() + result = _get_document(url=None, path=path) + + get.assert_not_called() + path.read_bytes.assert_called_once() + loads.assert_called_once_with(path.read_bytes()) + assert result == GeneratorError(header="Invalid YAML from provided source") + class TestProject: def test___init__(self, mocker):