diff --git a/openapi_core/spec/paths.py b/openapi_core/spec/paths.py index f6e8228a..3d441de0 100644 --- a/openapi_core/spec/paths.py +++ b/openapi_core/spec/paths.py @@ -62,3 +62,14 @@ def from_dict( ref_resolver_handlers=ref_resolver_handlers, separator=separator, ) + + def exists(self) -> bool: + try: + self.content() + except KeyError: + return False + else: + return True + + def uri(self) -> str: + return f"#/{str(self)}" diff --git a/openapi_core/templating/paths/exceptions.py b/openapi_core/templating/paths/exceptions.py index 4e38c480..8eccde4a 100644 --- a/openapi_core/templating/paths/exceptions.py +++ b/openapi_core/templating/paths/exceptions.py @@ -9,7 +9,7 @@ class PathError(OpenAPIError): @dataclass class PathNotFound(PathError): - """Find path error""" + """Path not found""" url: str @@ -17,6 +17,14 @@ def __str__(self) -> str: return f"Path not found for {self.url}" +@dataclass +class PathsNotFound(PathNotFound): + """Paths not found""" + + def __str__(self) -> str: + return f"Paths not found in spec: {self.url}" + + @dataclass class OperationNotFound(PathError): """Find path operation error""" diff --git a/openapi_core/templating/paths/finders.py b/openapi_core/templating/paths/finders.py index f4c9cb04..e6f70841 100644 --- a/openapi_core/templating/paths/finders.py +++ b/openapi_core/templating/paths/finders.py @@ -15,6 +15,7 @@ from openapi_core.templating.paths.datatypes import PathOperationServer from openapi_core.templating.paths.exceptions import OperationNotFound from openapi_core.templating.paths.exceptions import PathNotFound +from openapi_core.templating.paths.exceptions import PathsNotFound from openapi_core.templating.paths.exceptions import ServerNotFound from openapi_core.templating.paths.util import template_path_len from openapi_core.templating.util import parse @@ -73,8 +74,10 @@ def __init__(self, spec: Spec, base_url: Optional[str] = None): self.base_url = base_url def _get_paths_iter(self, name: str) -> Iterator[Path]: - template_paths: List[Path] = [] paths = self.spec / "paths" + if not paths.exists(): + raise PathsNotFound(paths.uri()) + template_paths: List[Path] = [] for path_pattern, path in list(paths.items()): # simple path. # Return right away since it is always the most concrete @@ -140,9 +143,9 @@ def _get_servers_iter( class WebhookPathFinder(BasePathFinder): def _get_paths_iter(self, name: str) -> Iterator[Path]: - if "webhooks" not in self.spec: - raise PathNotFound("Webhooks not found") webhooks = self.spec / "webhooks" + if not webhooks.exists(): + raise PathsNotFound(webhooks.uri()) for webhook_name, path in list(webhooks.items()): if name == webhook_name: path_result = TemplateResult(webhook_name, {}) diff --git a/tests/unit/templating/test_paths_finders.py b/tests/unit/templating/test_paths_finders.py index 5c3fd065..13240112 100644 --- a/tests/unit/templating/test_paths_finders.py +++ b/tests/unit/templating/test_paths_finders.py @@ -4,6 +4,7 @@ from openapi_core.templating.datatypes import TemplateResult from openapi_core.templating.paths.exceptions import OperationNotFound from openapi_core.templating.paths.exceptions import PathNotFound +from openapi_core.templating.paths.exceptions import PathsNotFound from openapi_core.templating.paths.exceptions import ServerNotFound from openapi_core.templating.paths.finders import APICallPathFinder from openapi_core.testing import MockRequest @@ -272,6 +273,22 @@ def test_raises(self, finder): finder.find(method, full_url) +class BaseTestPathsNotFound: + @pytest.fixture + def spec(self, info): + spec = { + "info": info, + } + return Spec.from_dict(spec, validator=None) + + def test_raises(self, finder): + method = "get" + full_url = "http://petstore.swagger.io/resource" + + with pytest.raises(PathsNotFound): + finder.find(method, full_url) + + class TestSpecSimpleServerServerNotFound( BaseTestServerNotFound, BaseTestSpecServer, @@ -299,6 +316,14 @@ class TestSpecSimpleServerPathNotFound( pass +class TestSpecSimpleServerPathsNotFound( + BaseTestPathsNotFound, + BaseTestSpecServer, + BaseTestSimpleServer, +): + pass + + class TestOperationSimpleServerServerNotFound( BaseTestServerNotFound, BaseTestOperationServer, @@ -326,6 +351,14 @@ class TestOperationSimpleServerPathNotFound( pass +class TestOperationSimpleServerPathsNotFound( + BaseTestPathsNotFound, + BaseTestOperationServer, + BaseTestSimpleServer, +): + pass + + class TestPathSimpleServerServerNotFound( BaseTestServerNotFound, BaseTestPathServer, @@ -353,6 +386,14 @@ class TestPathSimpleServerPathNotFound( pass +class TestPathSimpleServerPathsNotFound( + BaseTestPathsNotFound, + BaseTestPathServer, + BaseTestSimpleServer, +): + pass + + class TestSpecSimpleServerValid( BaseTestValid, BaseTestSpecServer, BaseTestSimplePath, BaseTestSimpleServer ): @@ -428,6 +469,14 @@ class TestSpecVariableServerPathNotFound( pass +class TestSpecVariableServerPathsNotFound( + BaseTestPathsNotFound, + BaseTestSpecServer, + BaseTestVariableServer, +): + pass + + class TestOperationVariableServerServerNotFound( BaseTestServerNotFound, BaseTestOperationServer, @@ -455,6 +504,14 @@ class TestOperationVariableServerPathNotFound( pass +class TestOperationVariableServerPathsNotFound( + BaseTestPathsNotFound, + BaseTestOperationServer, + BaseTestVariableServer, +): + pass + + class TestPathVariableServerServerNotFound( BaseTestServerNotFound, BaseTestPathServer, @@ -482,6 +539,14 @@ class TestPathVariableServerPathNotFound( pass +class TestPathVariableServerPathsNotFound( + BaseTestPathsNotFound, + BaseTestPathServer, + BaseTestVariableServer, +): + pass + + class TestSpecVariableServerValid( BaseTestVariableValid, BaseTestSpecServer,