Skip to content

Commit d60bde4

Browse files
committed
servers with request validation
1 parent 84546fe commit d60bde4

File tree

6 files changed

+219
-22
lines changed

6 files changed

+219
-22
lines changed

openapi_core/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ class MissingParameterError(OpenAPIMappingError):
1515

1616
class InvalidContentTypeError(OpenAPIMappingError):
1717
pass
18+
19+
20+
class InvalidServerError(OpenAPIMappingError):
21+
pass

openapi_core/servers.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from functools import lru_cache
2+
3+
from six import iteritems
4+
5+
6+
class Server(object):
7+
8+
def __init__(self, url, variables=None):
9+
self.url = url
10+
self.variables = variables and dict(variables) or {}
11+
12+
@property
13+
def default_url(self):
14+
return self.get_url()
15+
16+
@property
17+
def default_variables(self):
18+
defaults = {}
19+
for name, variable in iteritems(self.variables):
20+
defaults[name] = variable.default
21+
return defaults
22+
23+
def get_url(self, **variables):
24+
if not variables:
25+
variables = self.default_variables
26+
return self.url.format(**variables)
27+
28+
29+
class ServerVariable(object):
30+
31+
def __init__(self, name, default, enum=None):
32+
self.name = name
33+
self.default = default
34+
self.enum = enum and list(enum) or []
35+
36+
37+
class ServersGenerator(object):
38+
39+
def __init__(self, dereferencer):
40+
self.dereferencer = dereferencer
41+
42+
def generate(self, servers_spec):
43+
servers_deref = self.dereferencer.dereference(servers_spec)
44+
for server_spec in servers_deref:
45+
url = server_spec['url']
46+
variables_spec = server_spec.get('variables', {})
47+
48+
variables = None
49+
if variables_spec:
50+
variables = self.variables_generator.generate(variables_spec)
51+
52+
yield Server(url, variables=variables)
53+
54+
@property
55+
@lru_cache()
56+
def variables_generator(self):
57+
return ServerVariablesGenerator(self.dereferencer)
58+
59+
60+
class ServerVariablesGenerator(object):
61+
62+
def __init__(self, dereferencer):
63+
self.dereferencer = dereferencer
64+
65+
def generate(self, variables_spec):
66+
variables_deref = self.dereferencer.dereference(variables_spec)
67+
68+
if not variables_deref:
69+
return [Server('/'), ]
70+
71+
for variable_name, variable_spec in iteritems(variables_deref):
72+
default = variable_spec['default']
73+
enum = variable_spec.get('enum')
74+
75+
variable = ServerVariable(variable_name, default, enum=enum)
76+
yield variable_name, variable

openapi_core/specs.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from openapi_core.infos import InfoFactory
1010
from openapi_core.paths import PathsGenerator
1111
from openapi_core.schemas import SchemaRegistry
12+
from openapi_core.servers import ServersGenerator
1213

1314

1415
log = logging.getLogger(__name__)
@@ -26,8 +27,12 @@ def __init__(self, info, paths, servers=None, components=None):
2627
def __getitem__(self, path_name):
2728
return self.paths[path_name]
2829

30+
@property
31+
def default_url(self):
32+
return self.servers[0].default_url
33+
2934
def get_server_url(self, index=0):
30-
return self.servers[index]['url']
35+
return self.servers[index].default_url
3136

3237
def get_operation(self, path_pattern, http_method):
3338
return self.paths[path_pattern].operations[http_method]
@@ -59,14 +64,16 @@ def create(self, spec_dict, spec_url=''):
5964
spec_dict_deref = self.dereferencer.dereference(spec_dict)
6065

6166
info_spec = spec_dict_deref.get('info', [])
62-
servers = spec_dict_deref.get('servers', [])
67+
servers_spec = spec_dict_deref.get('servers', [])
6368
paths = spec_dict_deref.get('paths', [])
6469
components_spec = spec_dict_deref.get('components', [])
6570

6671
info = self.info_factory.create(info_spec)
72+
servers = self.servers_generator.generate(servers_spec)
6773
paths = self.paths_generator.generate(paths)
6874
components = self.components_factory.create(components_spec)
69-
spec = Spec(info, list(paths), servers=servers, components=components)
75+
spec = Spec(
76+
info, list(paths), servers=list(servers), components=components)
7077
return spec
7178

7279
@property
@@ -79,6 +86,11 @@ def schemas_registry(self):
7986
def info_factory(self):
8087
return InfoFactory(self.dereferencer)
8188

89+
@property
90+
@lru_cache()
91+
def servers_generator(self):
92+
return ServersGenerator(self.dereferencer)
93+
8294
@property
8395
@lru_cache()
8496
def paths_generator(self):

openapi_core/wrappers.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"""OpenAPI core wrappers module"""
22
from six import iteritems
3+
from six.moves.urllib.parse import urljoin
34

45
from openapi_core.exceptions import (
56
OpenAPIMappingError, MissingParameterError, InvalidContentTypeError,
7+
InvalidServerError,
68
)
79

810
SPEC_LOCATION_TO_REQUEST_LOCATION_MAPPING = {
@@ -32,13 +34,32 @@ def validate_location(cls, location):
3234
"Unknown parameter location: {0}".format(str(location)))
3335

3436

35-
class RequestParametersFactory(object):
37+
class BaseRequestFactory(object):
38+
39+
def get_operation(self, request, spec):
40+
server = self._get_server(request, spec)
41+
42+
operation_pattern = request.full_url_pattern.replace(
43+
server.default_url, '')
44+
45+
return spec.get_operation(operation_pattern, request.method)
46+
47+
def _get_server(self, request, spec):
48+
for server in spec.servers:
49+
if server.default_url in request.full_url_pattern:
50+
return server
51+
52+
raise InvalidServerError(
53+
"Invalid request server {0}".format(request.full_url_pattern))
54+
55+
56+
class RequestParametersFactory(BaseRequestFactory):
3657

3758
def __init__(self, attr_mapping=SPEC_LOCATION_TO_REQUEST_LOCATION_MAPPING):
3859
self.attr_mapping = attr_mapping
3960

4061
def create(self, request, spec):
41-
operation = spec.get_operation(request.path_pattern, request.method)
62+
operation = self.get_operation(request, spec)
4263

4364
params = RequestParameters()
4465
for param_name, param in iteritems(operation.parameters):
@@ -65,10 +86,10 @@ def _unmarshal_param(self, request, param):
6586
return param.unmarshal(raw_value)
6687

6788

68-
class RequestBodyFactory(object):
89+
class RequestBodyFactory(BaseRequestFactory):
6990

7091
def create(self, request, spec):
71-
operation = spec.get_operation(request.path_pattern, request.method)
92+
operation = self.get_operation(request, spec)
7293

7394
try:
7495
media_type = operation.request_body[request.content_type]
@@ -78,9 +99,13 @@ def create(self, request, spec):
7899

79100
return media_type.unmarshal(request.data)
80101

102+
def _get_operation(self, request, spec):
103+
return spec.get_operation(request.path_pattern, request.method)
104+
81105

82106
class BaseOpenAPIRequest(object):
83107

108+
host_url = NotImplemented
84109
path = NotImplemented
85110
path_pattern = NotImplemented
86111
method = NotImplemented
@@ -94,6 +119,10 @@ class BaseOpenAPIRequest(object):
94119

95120
content_type = NotImplemented
96121

122+
@property
123+
def full_url_pattern(self):
124+
return urljoin(self.host_url, self.path_pattern)
125+
97126
def get_parameters(self, spec):
98127
return RequestParametersFactory().create(self, spec)
99128

tests/integration/data/v3.0/petstore.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ info:
55
license:
66
name: MIT
77
servers:
8-
- url: http://petstore.swagger.io/v1
8+
- url: http://petstore.swagger.io/{version}
9+
variables:
10+
version:
11+
enum:
12+
- v1
13+
- v2
14+
default: v1
915
paths:
1016
/pets:
1117
get:

0 commit comments

Comments
 (0)