Connexion 2.0 (#619)
- App and Api options must be provided through the "options" argument (``old_style_options`` have been removed). - You must specify a form content-type in 'consumes' in order to consume form data. - The `Operation` interface has been formalized in the `AbstractOperation` class. - The `Operation` class has been renamed to `Swagger2Operation`. - Array parameter deserialization now follows the Swagger 2.0 spec more closely. In situations when a query parameter is passed multiple times, and the collectionFormat is either csv or pipes, the right-most value will be used. For example, `?q=1,2,3&q=4,5,6` will result in `q = [4, 5, 6]`. The old behavior is available by setting the collectionFormat to `multi`, or by importing `decorators.uri_parsing.AlwaysMultiURIParser` and passing `parser_class=AlwaysMultiURIParser` to your Api. - The spec validator library has changed from `swagger-spec-validator` to `openapi-spec-validator`. - Errors that previously raised `SwaggerValidationError` now raise the `InvalidSpecification` exception. All spec validation errors should be wrapped with `InvalidSpecification`. - Support for nullable/x-nullable, readOnly and writeOnly/x-writeOnly has been added to the standard json schema validator. - Custom validators can now be specified on api level (instead of app level). - Added support for basic authentication and apikey authentication - If unsupported security requirements are defined or ``x-tokenInfoFunc``/``x-tokenInfoUrl`` is missing, connexion now denies requests instead of allowing access without security-check. - Accessing ``connexion.request.user`` / ``flask.request.user`` is no longer supported, use ``connexion.context['user']`` instead
111
README.rst
@@ -29,15 +29,12 @@ Connexion
|
||||
:target: https://github.com/zalando/connexion/blob/master/LICENSE
|
||||
:alt: License
|
||||
|
||||
Connexion is a framework on top of Flask_ that automagically handles
|
||||
HTTP requests based on `OpenAPI 2.0 Specification`_ (formerly known as
|
||||
Swagger Spec) of your API described in `YAML format`_. Connexion
|
||||
allows you to write a Swagger specification, then maps the
|
||||
endpoints to your Python functions; this makes it unique, as many
|
||||
tools generate the specification based on your Python
|
||||
code. You can describe your REST API in as much detail as
|
||||
you want; then Connexion guarantees that it will work as
|
||||
you specified.
|
||||
Connexion is a framework that automagically handles HTTP requests based on `OpenAPI Specification`_
|
||||
(formerly known as Swagger Spec) of your API described in `YAML format`_. Connexion allows you to
|
||||
write an OpenAPI specification, then maps the endpoints to your Python functions; this makes it
|
||||
unique, as many tools generate the specification based on your Python code. You can describe your
|
||||
REST API in as much detail as you want; then Connexion guarantees that it will work as you
|
||||
specified.
|
||||
|
||||
We built Connexion this way in order to:
|
||||
|
||||
@@ -77,6 +74,25 @@ Other Sources/Mentions
|
||||
- Connexion listed on Swagger_'s website
|
||||
- Blog post: `Crafting effective Microservices in Python`_
|
||||
|
||||
New in Connexion 2.0:
|
||||
---------------------
|
||||
- App and Api options must be provided through the "options" argument (``old_style_options`` have been removed).
|
||||
- You must specify a form content-type in 'consumes' in order to consume form data.
|
||||
- The `Operation` interface has been formalized in the `AbstractOperation` class.
|
||||
- The `Operation` class has been renamed to `Swagger2Operation`.
|
||||
- Array parameter deserialization now follows the Swagger 2.0 spec more closely.
|
||||
In situations when a query parameter is passed multiple times, and the collectionFormat is either csv or pipes, the right-most value will be used.
|
||||
For example, `?q=1,2,3&q=4,5,6` will result in `q = [4, 5, 6]`.
|
||||
The old behavior is available by setting the collectionFormat to `multi`, or by importing `decorators.uri_parsing.AlwaysMultiURIParser` and passing `parser_class=AlwaysMultiURIParser` to your Api.
|
||||
- The spec validator library has changed from `swagger-spec-validator` to `openapi-spec-validator`.
|
||||
- Errors that previously raised `SwaggerValidationError` now raise the `InvalidSpecification` exception.
|
||||
All spec validation errors should be wrapped with `InvalidSpecification`.
|
||||
- Support for nullable/x-nullable, readOnly and writeOnly/x-writeOnly has been added to the standard json schema validator.
|
||||
- Custom validators can now be specified on api level (instead of app level).
|
||||
- Added support for basic authentication and apikey authentication
|
||||
- If unsupported security requirements are defined or ``x-tokenInfoFunc``/``x-tokenInfoUrl`` is missing, connexion now denies requests instead of allowing access without security-check.
|
||||
- Accessing ``connexion.request.user`` / ``flask.request.user`` is no longer supported, use ``connexion.context['user']`` instead
|
||||
|
||||
How to Use
|
||||
==========
|
||||
|
||||
@@ -160,8 +176,8 @@ identify which Python function should handle each URL.
|
||||
If you provide this path in your specification POST requests to
|
||||
``http://MYHOST/hello_world``, it will be handled by the function
|
||||
``hello_world`` in the ``myapp.api`` module. Optionally, you can include
|
||||
``x-swagger-router-controller`` in your operation definition, making
|
||||
``operationId`` relative:
|
||||
``x-swagger-router-controller`` (or ``x-openapi-router-controller``) in your
|
||||
operation definition, making ``operationId`` relative:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@@ -249,8 +265,8 @@ function expects an argument named ``message`` and assigns the value
|
||||
of the endpoint parameter ``message`` to your view function.
|
||||
|
||||
.. warning:: When you define a parameter at your endpoint as *not* required, and
|
||||
this argument does not have default value in your Python view, you will get
|
||||
a "missing positional argument" exception whenever you call this endpoint
|
||||
this argument does not have default value in your Python view, you will get
|
||||
a "missing positional argument" exception whenever you call this endpoint
|
||||
WITHOUT the parameter. Provide a default value for a named argument or use
|
||||
``**kwargs`` dict.
|
||||
|
||||
@@ -300,22 +316,25 @@ You can implement your own URI parsing behavior by inheriting from
|
||||
``connextion.decorators.uri_parsing.AbstractURIParser``.
|
||||
|
||||
There are three URI parsers included with connection.
|
||||
1. AlwaysMultiURIParser (default)
|
||||
This parser is backwards compatible, and joins together multiple instances
|
||||
of the same query parameter.
|
||||
2. Swagger2URIParser
|
||||
This parser adheres to the Swagger 2.0 spec, and will only join together
|
||||
multiple instance of the same query parameter if the ``collectionFormat``
|
||||
is set to ``multi``. Query parameters are parsed from left to right, so
|
||||
if a query parameter is defined twice, then the right-most definition wins.
|
||||
For example, if you provided a URI with the query string
|
||||
``?letters=a,b,c&letters=d,e,f``, and ``collectionFormat: csv``, then
|
||||
connexion will set ``letters = ['d', 'e', 'f']``
|
||||
3. FirstValueURIParser
|
||||
This parser behaves like the Swagger2URIParser, except that it prefers the
|
||||
first defined value. For example, if you provided a URI with the query
|
||||
string ``?letters=a,b,c&letters=d,e,f`` and ``collectionFormat: csv``
|
||||
then connexion will set ``letters = ['a', 'b', 'c']``
|
||||
|
||||
+----------------------+---------------------------------------------------------------------------+
|
||||
| AlwaysMultiURIParser | This parser is backwards compatible, and joins together multiple instances|
|
||||
| (default) | of the same query parameter. |
|
||||
+----------------------+---------------------------------------------------------------------------+
|
||||
| Swagger2URIParser | This parser adheres to the Swagger 2.0 spec, and will only join together |
|
||||
| | multiple instance of the same query parameter if the ``collectionFormat`` |
|
||||
| | is set to ``multi``. Query parameters are parsed from left to right, so |
|
||||
| | if a query parameter is defined twice, then the right-most definition |
|
||||
| | wins. For example, if you provided a URI with the query string |
|
||||
| | ``?letters=a,b,c&letters=d,e,f``, and ``collectionFormat: csv``, then |
|
||||
| | connexion will set ``letters = ['d', 'e', 'f']`` |
|
||||
+----------------------+---------------------------------------------------------------------------+
|
||||
| FirstValueURIParser | This parser behaves like the Swagger2URIParser, except that it prefers |
|
||||
| | the first defined value. For example, if you provided a URI with the query|
|
||||
| | string ``?letters=a,b,c&letters=d,e,f`` and ``collectionFormat: csv`` |
|
||||
| | hen connexion will set ``letters = ['a', 'b', 'c']`` |
|
||||
+----------------------+---------------------------------------------------------------------------+
|
||||
|
||||
|
||||
Parameter validation
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -400,15 +419,17 @@ parameters to the underlying `werkzeug`_ server.
|
||||
The Swagger UI Console
|
||||
----------------------
|
||||
|
||||
The Swagger UI for an API is available, by default, in
|
||||
``{base_path}/ui/`` where ``base_path`` is the base path of the API.
|
||||
The Swagger UI for an API is available through pip extras.
|
||||
You can install it with ``pip install connexion[swagger-ui]``.
|
||||
It will be served up at ``{base_path}/ui/`` where ``base_path`` is the
|
||||
base path of the API.
|
||||
|
||||
You can disable the Swagger UI at the application level:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = connexion.App(__name__, specification_dir='swagger/',
|
||||
swagger_ui=False)
|
||||
options={"swagger_ui": False})
|
||||
app.add_api('my_api.yaml')
|
||||
|
||||
|
||||
@@ -417,10 +438,10 @@ You can also disable it at the API level:
|
||||
.. code-block:: python
|
||||
|
||||
app = connexion.App(__name__, specification_dir='swagger/')
|
||||
app.add_api('my_api.yaml', swagger_ui=False)
|
||||
app.add_api('my_api.yaml', options={"swagger_ui": False})
|
||||
|
||||
If necessary, you can explicitly specify the path to the directory with
|
||||
swagger-ui to not use the connexion-embedded swagger-ui distro.
|
||||
swagger-ui to not use the connexion[swagger-ui] distro.
|
||||
In order to do this, you should specify the following option:
|
||||
|
||||
.. code-block:: python
|
||||
@@ -428,17 +449,29 @@ In order to do this, you should specify the following option:
|
||||
options = {'swagger_path': '/path/to/swagger_ui/'}
|
||||
app = connexion.App(__name__, specification_dir='swagger/', options=options)
|
||||
|
||||
Make sure that ``swagger_ui/index.html`` loads by default local swagger json.
|
||||
You can use the ``api_url`` jinja variable for this purpose:
|
||||
If you wish to provide your own swagger-ui distro, note that connextion
|
||||
expects a jinja2 file called ``swagger_ui/index.j2`` in order to load the
|
||||
correct ``swagger.json`` by default. Your ``index.j2`` file can use the
|
||||
``openapi_spec_url`` jinja variable for this purpose:
|
||||
|
||||
.. code-block::
|
||||
|
||||
const ui = SwaggerUIBundle({ url: "{{ api_url }}/swagger.json"})
|
||||
const ui = SwaggerUIBundle({ url: "{{ openapi_spec_url }}"})
|
||||
|
||||
Additionally, if you wish to use swagger-ui-3.x.x, it is also provided by
|
||||
installing connexion[swagger-ui], and can be enabled like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from swagger_ui_bundle import swagger_ui_3_path
|
||||
options = {'swagger_path': swagger_ui_3_path}
|
||||
app = connexion.App(__name__, specification_dir='swagger/', options=options)
|
||||
|
||||
|
||||
Server Backend
|
||||
--------------
|
||||
|
||||
Connexion uses the default Flask server. For asynchronous
|
||||
By default Connexion uses the Flask_ server. For asynchronous
|
||||
applications, you can also use Tornado_ as the HTTP server. To do
|
||||
this, set your server to ``tornado``:
|
||||
|
||||
@@ -534,7 +567,7 @@ Unless required by applicable law or agreed to in writing, software distributed
|
||||
.. _Swagger: http://swagger.io/open-source-integrations/
|
||||
.. _Jinja2: http://jinja.pocoo.org/
|
||||
.. _rfc6750: https://tools.ietf.org/html/rfc6750
|
||||
.. _OpenAPI 2.0 Specification: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
|
||||
.. _OpenAPI Specification: https://www.openapis.org/
|
||||
.. _Operation Object: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#operation-object
|
||||
.. _swager.spec.security_definition: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object
|
||||
.. _swager.spec.security_requirement: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-requirement-object
|
||||
|
||||
@@ -20,7 +20,7 @@ def not_installed_error(): # pragma: no cover
|
||||
|
||||
|
||||
try:
|
||||
from .apis.flask_api import FlaskApi
|
||||
from .apis.flask_api import FlaskApi, context # NOQA
|
||||
from .apps.flask_app import FlaskApp
|
||||
from flask import request # NOQA
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
@@ -8,19 +8,26 @@ from typing import AnyStr, List # NOQA
|
||||
import jinja2
|
||||
import six
|
||||
import yaml
|
||||
from swagger_spec_validator.validator20 import validate_spec
|
||||
from openapi_spec_validator.exceptions import OpenAPIValidationError
|
||||
from six.moves.urllib.parse import urlsplit
|
||||
|
||||
from ..exceptions import ResolverError
|
||||
from ..operation import Operation
|
||||
from ..exceptions import InvalidSpecification, ResolverError
|
||||
from ..json_schema import resolve_refs
|
||||
from ..operations import OpenAPIOperation, Swagger2Operation
|
||||
from ..options import ConnexionOptions
|
||||
from ..resolver import Resolver
|
||||
from ..utils import Jsonifier
|
||||
|
||||
MODULE_PATH = pathlib.Path(__file__).absolute().parent.parent
|
||||
SWAGGER_UI_PATH = MODULE_PATH / 'vendor' / 'swagger-ui'
|
||||
SWAGGER_UI_URL = 'ui'
|
||||
try:
|
||||
import collections.abc as collections_abc # python 3.3+
|
||||
except ImportError:
|
||||
import collections as collections_abc
|
||||
|
||||
RESOLVER_ERROR_ENDPOINT_RANDOM_DIGITS = 6
|
||||
MODULE_PATH = pathlib.Path(__file__).absolute().parent.parent
|
||||
SWAGGER_UI_URL = 'ui'
|
||||
NO_SPEC_VERSION_ERR_MSG = """Unable to get the spec version.
|
||||
You are missing either '"swagger": "2.0"' or '"openapi": "3.0.0"'
|
||||
from the top level of your spec."""
|
||||
|
||||
logger = logging.getLogger('connexion.apis.abstract')
|
||||
|
||||
@@ -32,6 +39,196 @@ class AbstractAPIMeta(abc.ABCMeta):
|
||||
cls._set_jsonifier()
|
||||
|
||||
|
||||
class Specification(collections_abc.Mapping):
|
||||
|
||||
def __init__(self, raw_spec):
|
||||
self._raw_spec = copy.deepcopy(raw_spec)
|
||||
self._spec = resolve_refs(raw_spec)
|
||||
self._set_defaults()
|
||||
self._validate_spec()
|
||||
|
||||
@abc.abstractmethod
|
||||
def _set_defaults(self):
|
||||
""" set some default values in the spec
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_spec(self):
|
||||
""" validate spec against schema
|
||||
"""
|
||||
|
||||
@property
|
||||
def raw(self):
|
||||
return self._raw_spec
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._get_spec_version(self._spec)
|
||||
|
||||
@property
|
||||
def security(self):
|
||||
return self._spec.get('security')
|
||||
|
||||
def __getitem__(self, k):
|
||||
return self._spec[k]
|
||||
|
||||
def __iter__(self):
|
||||
return self._spec.__iter__()
|
||||
|
||||
def __len__(self):
|
||||
return self._spec.__len__()
|
||||
|
||||
@staticmethod
|
||||
def _load_spec_from_file(arguments, specification):
|
||||
from openapi_spec_validator.loaders import ExtendedSafeLoader
|
||||
arguments = arguments or {}
|
||||
|
||||
with specification.open(mode='rb') as openapi_yaml:
|
||||
contents = openapi_yaml.read()
|
||||
try:
|
||||
openapi_template = contents.decode()
|
||||
except UnicodeDecodeError:
|
||||
openapi_template = contents.decode('utf-8', 'replace')
|
||||
|
||||
openapi_string = jinja2.Template(openapi_template).render(**arguments)
|
||||
return yaml.load(openapi_string, ExtendedSafeLoader)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, spec, arguments=None):
|
||||
specification_path = pathlib.Path(spec)
|
||||
spec = cls._load_spec_from_file(arguments, specification_path)
|
||||
return cls.from_dict(spec)
|
||||
|
||||
@staticmethod
|
||||
def _get_spec_version(spec):
|
||||
try:
|
||||
version_string = spec.get('openapi') or spec.get('swagger')
|
||||
except AttributeError:
|
||||
raise InvalidSpecification(NO_SPEC_VERSION_ERR_MSG)
|
||||
if version_string is None:
|
||||
raise InvalidSpecification(NO_SPEC_VERSION_ERR_MSG)
|
||||
try:
|
||||
version_tuple = tuple(map(int, version_string.split(".")))
|
||||
except TypeError:
|
||||
err = ('Unable to convert version string to semantic version tuple: '
|
||||
'{version_string}.')
|
||||
err = err.format(version_string=version_string)
|
||||
raise InvalidSpecification(err)
|
||||
return version_tuple
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, spec):
|
||||
version = cls._get_spec_version(spec)
|
||||
if version < (3, 0, 0):
|
||||
return Swagger2Specification(spec)
|
||||
return OpenAPISpecification(spec)
|
||||
|
||||
@classmethod
|
||||
def load(cls, spec, arguments=None):
|
||||
if not isinstance(spec, dict):
|
||||
return cls.from_file(spec, arguments=arguments)
|
||||
return cls.from_dict(spec)
|
||||
|
||||
|
||||
class Swagger2Specification(Specification):
|
||||
yaml_name = 'swagger.yaml'
|
||||
operation_cls = Swagger2Operation
|
||||
|
||||
def _set_defaults(self):
|
||||
self._spec.setdefault('produces', [])
|
||||
self._spec.setdefault('consumes', ['application/json']) # type: List[str]
|
||||
self._spec.setdefault('definitions', {})
|
||||
self._spec.setdefault('parameters', {})
|
||||
self._spec.setdefault('responses', {})
|
||||
|
||||
@property
|
||||
def produces(self):
|
||||
return self._spec['produces']
|
||||
|
||||
@property
|
||||
def consumes(self):
|
||||
return self._spec['consumes']
|
||||
|
||||
@property
|
||||
def definitions(self):
|
||||
return self._spec['definitions']
|
||||
|
||||
@property
|
||||
def parameter_definitions(self):
|
||||
return self._spec['parameters']
|
||||
|
||||
@property
|
||||
def response_definitions(self):
|
||||
return self._spec['responses']
|
||||
|
||||
@property
|
||||
def security_definitions(self):
|
||||
return self._spec.get('securityDefinitions', {})
|
||||
|
||||
@property
|
||||
def base_path(self):
|
||||
return canonical_base_path(self._spec.get('basePath', ''))
|
||||
|
||||
@base_path.setter
|
||||
def base_path(self, base_path):
|
||||
base_path = canonical_base_path(base_path)
|
||||
self._raw_spec['basePath'] = base_path
|
||||
self._spec['basePath'] = base_path
|
||||
|
||||
def _validate_spec(self):
|
||||
from openapi_spec_validator import validate_v2_spec as validate_spec
|
||||
try:
|
||||
validate_spec(self._raw_spec)
|
||||
except OpenAPIValidationError as e:
|
||||
raise InvalidSpecification.create_from(e)
|
||||
|
||||
|
||||
class OpenAPISpecification(Specification):
|
||||
yaml_name = 'openapi.yaml'
|
||||
operation_cls = OpenAPIOperation
|
||||
|
||||
def _set_defaults(self):
|
||||
self._spec.setdefault('components', {})
|
||||
|
||||
@property
|
||||
def security_definitions(self):
|
||||
return self._spec['components'].get('securitySchemes', {})
|
||||
|
||||
@property
|
||||
def components(self):
|
||||
return self._spec['components']
|
||||
|
||||
def _validate_spec(self):
|
||||
from openapi_spec_validator import validate_v3_spec as validate_spec
|
||||
try:
|
||||
validate_spec(self._raw_spec)
|
||||
except OpenAPIValidationError as e:
|
||||
raise InvalidSpecification.create_from(e)
|
||||
|
||||
@property
|
||||
def base_path(self):
|
||||
servers = self._spec.get('servers', [])
|
||||
try:
|
||||
# assume we're the first server in list
|
||||
server = copy.deepcopy(servers[0])
|
||||
server_vars = server.pop("variables", {})
|
||||
server['url'] = server['url'].format(
|
||||
**{k: v['default'] for k, v
|
||||
in six.iteritems(server_vars)}
|
||||
)
|
||||
base_path = urlsplit(server['url']).path
|
||||
except IndexError:
|
||||
base_path = ''
|
||||
return canonical_base_path(base_path)
|
||||
|
||||
@base_path.setter
|
||||
def base_path(self, base_path):
|
||||
base_path = canonical_base_path(base_path)
|
||||
user_servers = [{'url': base_path}]
|
||||
self._raw_spec['servers'] = user_servers
|
||||
self._spec['servers'] = user_servers
|
||||
|
||||
|
||||
@six.add_metaclass(AbstractAPIMeta)
|
||||
class AbstractAPI(object):
|
||||
"""
|
||||
@@ -41,8 +238,7 @@ class AbstractAPI(object):
|
||||
def __init__(self, specification, base_path=None, arguments=None,
|
||||
validate_responses=False, strict_validation=False, resolver=None,
|
||||
auth_all_paths=False, debug=False, resolver_error_handler=None,
|
||||
validator_map=None, pythonic_params=False, options=None, pass_context_arg_name=None,
|
||||
**old_style_options):
|
||||
validator_map=None, pythonic_params=False, pass_context_arg_name=None, options=None):
|
||||
"""
|
||||
:type specification: pathlib.Path | dict
|
||||
:type base_path: str | None
|
||||
@@ -65,63 +261,32 @@ class AbstractAPI(object):
|
||||
:param pass_context_arg_name: If not None URL request handling functions with an argument matching this name
|
||||
will be passed the framework's request context.
|
||||
:type pass_context_arg_name: str | None
|
||||
:param old_style_options: Old style options support for backward compatibility. Preference is
|
||||
what is defined in `options` parameter.
|
||||
"""
|
||||
self.debug = debug
|
||||
self.validator_map = validator_map
|
||||
self.resolver_error_handler = resolver_error_handler
|
||||
|
||||
self.options = ConnexionOptions(old_style_options)
|
||||
# options is added last to preserve the highest priority
|
||||
self.options = self.options.extend(options)
|
||||
|
||||
# TODO: Remove this in later versions (Current version is 1.1.9)
|
||||
if base_path is None and 'base_url' in old_style_options:
|
||||
base_path = old_style_options['base_url']
|
||||
logger.warning("Parameter base_url should be no longer used. Use base_path instead.")
|
||||
|
||||
logger.debug('Loading specification: %s', specification,
|
||||
extra={'swagger_yaml': specification,
|
||||
'base_path': base_path,
|
||||
'arguments': arguments,
|
||||
'swagger_ui': self.options.openapi_console_ui_available,
|
||||
'swagger_path': self.options.openapi_console_ui_from_dir,
|
||||
'swagger_url': self.options.openapi_console_ui_path,
|
||||
'auth_all_paths': auth_all_paths})
|
||||
|
||||
if isinstance(specification, dict):
|
||||
self.specification = specification
|
||||
else:
|
||||
specification_path = pathlib.Path(specification)
|
||||
self.specification = self.load_spec_from_file(arguments, specification_path)
|
||||
# Avoid validator having ability to modify specification
|
||||
self.specification = Specification.load(specification, arguments=arguments)
|
||||
|
||||
self.specification = compatibility_layer(self.specification)
|
||||
logger.debug('Read specification', extra={'spec': self.specification})
|
||||
|
||||
# Avoid validator having ability to modify specification
|
||||
spec = copy.deepcopy(self.specification)
|
||||
self._validate_spec(spec)
|
||||
self.options = ConnexionOptions(options, oas_version=self.specification.version)
|
||||
|
||||
logger.debug('Options Loaded',
|
||||
extra={'swagger_ui': self.options.openapi_console_ui_available,
|
||||
'swagger_path': self.options.openapi_console_ui_from_dir,
|
||||
'swagger_url': self.options.openapi_console_ui_path})
|
||||
|
||||
# https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#fixed-fields
|
||||
# If base_path is not on provided then we try to read it from the swagger.yaml or use / by default
|
||||
self._set_base_path(base_path)
|
||||
|
||||
# A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific
|
||||
# API calls.
|
||||
self.produces = self.specification.get('produces', list()) # type: List[str]
|
||||
|
||||
# A list of MIME types the APIs can consume. This is global to all APIs but can be overridden on specific
|
||||
# API calls.
|
||||
self.consumes = self.specification.get('consumes', ['application/json']) # type: List[str]
|
||||
|
||||
self.security = self.specification.get('security')
|
||||
self.security_definitions = self.specification.get('securityDefinitions', dict())
|
||||
logger.debug('Security Definitions: %s', self.security_definitions)
|
||||
|
||||
self.definitions = self.specification.get('definitions', {})
|
||||
self.parameter_definitions = self.specification.get('parameters', {})
|
||||
self.response_definitions = self.specification.get('responses', {})
|
||||
logger.debug('Security Definitions: %s', self.specification.security_definitions)
|
||||
|
||||
self.resolver = resolver or Resolver()
|
||||
|
||||
@@ -138,7 +303,7 @@ class AbstractAPI(object):
|
||||
self.pass_context_arg_name = pass_context_arg_name
|
||||
|
||||
if self.options.openapi_spec_available:
|
||||
self.add_swagger_json()
|
||||
self.add_openapi_json()
|
||||
|
||||
if self.options.openapi_console_ui_available:
|
||||
self.add_swagger_ui()
|
||||
@@ -146,23 +311,24 @@ class AbstractAPI(object):
|
||||
self.add_paths()
|
||||
|
||||
if auth_all_paths:
|
||||
self.add_auth_on_not_found(self.security, self.security_definitions)
|
||||
self.add_auth_on_not_found(
|
||||
self.specification.security,
|
||||
self.specification.security_definitions
|
||||
)
|
||||
|
||||
def _validate_spec(self, spec):
|
||||
validate_spec(spec)
|
||||
|
||||
def _set_base_path(self, base_path):
|
||||
# type: (AnyStr) -> None
|
||||
if base_path is None:
|
||||
self.base_path = canonical_base_path(self.specification.get('basePath', ''))
|
||||
def _set_base_path(self, base_path=None):
|
||||
if base_path is not None:
|
||||
# update spec to include user-provided base_path
|
||||
self.specification.base_path = base_path
|
||||
self.base_path = base_path
|
||||
else:
|
||||
self.base_path = canonical_base_path(base_path)
|
||||
self.specification['basePath'] = base_path
|
||||
self.base_path = self.specification.base_path
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_swagger_json(self):
|
||||
def add_openapi_json(self):
|
||||
"""
|
||||
Adds swagger json to {base_path}/swagger.json
|
||||
Adds openapi spec to {base_path}/openapi.json
|
||||
(or {base_path}/swagger.json for swagger2)
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -194,25 +360,37 @@ class AbstractAPI(object):
|
||||
:type path: str
|
||||
:type swagger_operation: dict
|
||||
"""
|
||||
operation = Operation(self,
|
||||
method=method,
|
||||
path=path,
|
||||
path_parameters=path_parameters,
|
||||
operation=swagger_operation,
|
||||
app_produces=self.produces,
|
||||
app_consumes=self.consumes,
|
||||
app_security=self.security,
|
||||
security_definitions=self.security_definitions,
|
||||
definitions=self.definitions,
|
||||
parameter_definitions=self.parameter_definitions,
|
||||
response_definitions=self.response_definitions,
|
||||
validate_responses=self.validate_responses,
|
||||
validator_map=self.validator_map,
|
||||
strict_validation=self.strict_validation,
|
||||
resolver=self.resolver,
|
||||
pythonic_params=self.pythonic_params,
|
||||
uri_parser_class=self.options.uri_parser_class,
|
||||
pass_context_arg_name=self.pass_context_arg_name)
|
||||
|
||||
shared_args = {
|
||||
"method": method,
|
||||
"path": path,
|
||||
"path_parameters": path_parameters,
|
||||
"operation": swagger_operation,
|
||||
"app_security": self.specification.security,
|
||||
"validate_responses": self.validate_responses,
|
||||
"validator_map": self.validator_map,
|
||||
"strict_validation": self.strict_validation,
|
||||
"resolver": self.resolver,
|
||||
"pythonic_params": self.pythonic_params,
|
||||
"uri_parser_class": self.options.uri_parser_class,
|
||||
"pass_context_arg_name": self.pass_context_arg_name
|
||||
}
|
||||
|
||||
# TODO refactor into AbstractOperation.from_spec(Specification, method, path)
|
||||
if self.specification.version < (3, 0, 0):
|
||||
operation = Swagger2Operation(self,
|
||||
app_produces=self.specification.produces,
|
||||
app_consumes=self.specification.consumes,
|
||||
security_definitions=self.specification.security_definitions,
|
||||
definitions=self.specification.definitions,
|
||||
parameter_definitions=self.specification.parameter_definitions,
|
||||
response_definitions=self.specification.response_definitions,
|
||||
**shared_args)
|
||||
else:
|
||||
operation = OpenAPIOperation(self,
|
||||
components=self.specification.components,
|
||||
**shared_args)
|
||||
|
||||
self._add_operation_internal(method, path, operation)
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -226,19 +404,11 @@ class AbstractAPI(object):
|
||||
"""
|
||||
Adds a handler for ResolverError for the given method and path.
|
||||
"""
|
||||
operation = self.resolver_error_handler(err,
|
||||
method=method,
|
||||
path=path,
|
||||
app_produces=self.produces,
|
||||
app_security=self.security,
|
||||
security_definitions=self.security_definitions,
|
||||
definitions=self.definitions,
|
||||
parameter_definitions=self.parameter_definitions,
|
||||
response_definitions=self.response_definitions,
|
||||
validate_responses=self.validate_responses,
|
||||
strict_validation=self.strict_validation,
|
||||
resolver=self.resolver,
|
||||
randomize_endpoint=RESOLVER_ERROR_ENDPOINT_RANDOM_DIGITS)
|
||||
operation = self.resolver_error_handler(
|
||||
err,
|
||||
security=self.specification.security,
|
||||
security_definitions=self.specification.security_definitions
|
||||
)
|
||||
self._add_operation_internal(method, path, operation)
|
||||
|
||||
def add_paths(self, paths=None):
|
||||
@@ -282,19 +452,6 @@ class AbstractAPI(object):
|
||||
logger.error(error_msg)
|
||||
six.reraise(*exc_info)
|
||||
|
||||
def load_spec_from_file(self, arguments, specification):
|
||||
arguments = arguments or {}
|
||||
|
||||
with specification.open(mode='rb') as swagger_yaml:
|
||||
contents = swagger_yaml.read()
|
||||
try:
|
||||
swagger_template = contents.decode()
|
||||
except UnicodeDecodeError:
|
||||
swagger_template = contents.decode('utf-8', 'replace')
|
||||
|
||||
swagger_string = jinja2.Template(swagger_template).render(**arguments)
|
||||
return yaml.safe_load(swagger_string) # type: dict
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_request(self, *args, **kwargs):
|
||||
@@ -337,24 +494,3 @@ def canonical_base_path(base_path):
|
||||
Make given "basePath" a canonical base URL which can be prepended to paths starting with "/".
|
||||
"""
|
||||
return base_path.rstrip('/')
|
||||
|
||||
|
||||
def compatibility_layer(spec):
|
||||
"""Make specs compatible with older versions of Connexion."""
|
||||
if not isinstance(spec, dict):
|
||||
return spec
|
||||
|
||||
# Make all response codes be string
|
||||
for path_name, methods_available in spec.get('paths', {}).items():
|
||||
for method_name, method_def in methods_available.items():
|
||||
if (method_name == 'parameters' or not isinstance(
|
||||
method_def, dict)):
|
||||
continue
|
||||
|
||||
response_definitions = {}
|
||||
for response_code, response_def in method_def.get(
|
||||
'responses', {}).items():
|
||||
response_definitions[str(response_code)] = response_def
|
||||
|
||||
method_def['responses'] = response_definitions
|
||||
return spec
|
||||
|
||||
@@ -64,23 +64,25 @@ class AioHttpApi(AbstractAPI):
|
||||
def normalize_string(string):
|
||||
return re.sub(r'[^a-zA-Z0-9]', '_', string.strip('/'))
|
||||
|
||||
def add_swagger_json(self):
|
||||
def add_openapi_json(self):
|
||||
"""
|
||||
Adds swagger json to {base_path}/swagger.json
|
||||
Adds openapi json to {base_path}/openapi.json
|
||||
(or {base_path}/swagger.json for swagger2)
|
||||
"""
|
||||
logger.debug('Adding swagger.json: %s/swagger.json', self.base_path)
|
||||
logger.debug('Adding spec json: %s/%s', self.base_path,
|
||||
self.options.openapi_spec_path)
|
||||
self.subapp.router.add_route(
|
||||
'GET',
|
||||
'/swagger.json',
|
||||
self._get_swagger_json
|
||||
self.options.openapi_spec_path,
|
||||
self._get_openapi_json
|
||||
)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_swagger_json(self, req):
|
||||
def _get_openapi_json(self, req):
|
||||
return web.Response(
|
||||
status=200,
|
||||
content_type='application/json',
|
||||
body=self.jsonifier.dumps(self.specification)
|
||||
body=self.jsonifier.dumps(self.specification.raw)
|
||||
)
|
||||
|
||||
def add_swagger_ui(self):
|
||||
@@ -109,10 +111,11 @@ class AioHttpApi(AbstractAPI):
|
||||
name='swagger_ui_static'
|
||||
)
|
||||
|
||||
@aiohttp_jinja2.template('index.html')
|
||||
@aiohttp_jinja2.template('index.j2')
|
||||
@asyncio.coroutine
|
||||
def _get_swagger_ui_home(self, req):
|
||||
return {'api_url': self.base_path}
|
||||
return {'openapi_spec_url': (self.base_path +
|
||||
self.options.openapi_spec_path)}
|
||||
|
||||
def add_auth_on_not_found(self, security, security_definitions):
|
||||
"""
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
import flask
|
||||
import six
|
||||
import werkzeug.exceptions
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from connexion.apis import flask_utils
|
||||
from connexion.apis.abstract import AbstractAPI
|
||||
@@ -26,15 +27,17 @@ class FlaskApi(AbstractAPI):
|
||||
self.blueprint = flask.Blueprint(endpoint, __name__, url_prefix=self.base_path,
|
||||
template_folder=str(self.options.openapi_console_ui_from_dir))
|
||||
|
||||
def add_swagger_json(self):
|
||||
def add_openapi_json(self):
|
||||
"""
|
||||
Adds swagger json to {base_path}/swagger.json
|
||||
Adds spec json to {base_path}/swagger.json
|
||||
or {base_path}/openapi.json (for oas3)
|
||||
"""
|
||||
logger.debug('Adding swagger.json: %s/swagger.json', self.base_path)
|
||||
endpoint_name = "{name}_swagger_json".format(name=self.blueprint.name)
|
||||
self.blueprint.add_url_rule('/swagger.json',
|
||||
logger.debug('Adding spec json: %s/%s', self.base_path,
|
||||
self.options.openapi_spec_path)
|
||||
endpoint_name = "{name}_openapi_json".format(name=self.blueprint.name)
|
||||
self.blueprint.add_url_rule(self.options.openapi_spec_path,
|
||||
endpoint_name,
|
||||
lambda: flask.jsonify(self.specification))
|
||||
lambda: flask.jsonify(self.specification.raw))
|
||||
|
||||
def add_swagger_ui(self):
|
||||
"""
|
||||
@@ -216,6 +219,8 @@ class FlaskApi(AbstractAPI):
|
||||
|
||||
:rtype: ConnexionRequest
|
||||
"""
|
||||
context_dict = {}
|
||||
setattr(flask._request_ctx_stack.top, 'connexion_context', context_dict)
|
||||
flask_request = flask.request
|
||||
request = ConnexionRequest(
|
||||
flask_request.url,
|
||||
@@ -227,7 +232,7 @@ class FlaskApi(AbstractAPI):
|
||||
json_getter=lambda: flask_request.get_json(silent=True),
|
||||
files=flask_request.files,
|
||||
path_params=params,
|
||||
context=FlaskRequestContextProxy()
|
||||
context=context_dict
|
||||
)
|
||||
logger.debug('Getting data and status code',
|
||||
extra={
|
||||
@@ -245,23 +250,11 @@ class FlaskApi(AbstractAPI):
|
||||
cls.jsonifier = Jsonifier(flask.json)
|
||||
|
||||
|
||||
class FlaskRequestContextProxy(object):
|
||||
""""Proxy assignments from `ConnexionRequest.context`
|
||||
to `flask.request` instance.
|
||||
"""
|
||||
def _get_context():
|
||||
return getattr(flask._request_ctx_stack.top, 'connexion_context')
|
||||
|
||||
def __init__(self):
|
||||
self.values = {}
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# type: (str, Any) -> None
|
||||
logger.debug('Setting "%s" attribute in flask.request', key)
|
||||
setattr(flask.request, key, value)
|
||||
self.values[key] = value
|
||||
|
||||
def items(self):
|
||||
# type: () -> list
|
||||
return self.values.items()
|
||||
context = LocalProxy(_get_context)
|
||||
|
||||
|
||||
class InternalHandlers(object):
|
||||
@@ -279,7 +272,10 @@ class InternalHandlers(object):
|
||||
|
||||
:return:
|
||||
"""
|
||||
return flask.render_template('index.html', api_url=self.base_path)
|
||||
return flask.render_template(
|
||||
'index.j2',
|
||||
openapi_spec_url=(self.base_path + self.options.openapi_spec_path)
|
||||
)
|
||||
|
||||
def console_ui_static_files(self, filename):
|
||||
"""
|
||||
|
||||
@@ -14,7 +14,7 @@ logger = logging.getLogger('connexion.app')
|
||||
class AbstractApp(object):
|
||||
def __init__(self, import_name, api_cls, port=None, specification_dir='',
|
||||
host=None, server=None, arguments=None, auth_all_paths=False, debug=False,
|
||||
validator_map=None, options=None, **old_style_options):
|
||||
resolver=None, options=None):
|
||||
"""
|
||||
:param import_name: the name of the application package
|
||||
:type import_name: str
|
||||
@@ -32,12 +32,12 @@ class AbstractApp(object):
|
||||
:type auth_all_paths: bool
|
||||
:param debug: include debugging information
|
||||
:type debug: bool
|
||||
:param validator_map: map of validators
|
||||
:type validator_map: dict
|
||||
:param resolver: Callable that maps operationID to a function
|
||||
"""
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.debug = debug
|
||||
self.resolver = resolver
|
||||
self.import_name = import_name
|
||||
self.arguments = arguments or {}
|
||||
self.api_cls = api_cls
|
||||
@@ -45,11 +45,8 @@ class AbstractApp(object):
|
||||
|
||||
# Options
|
||||
self.auth_all_paths = auth_all_paths
|
||||
self.validator_map = validator_map
|
||||
|
||||
self.options = ConnexionOptions(old_style_options)
|
||||
# options is added last to preserve the highest priority
|
||||
self.options = self.options.extend(options) # type: ConnexionOptions
|
||||
self.options = ConnexionOptions(options)
|
||||
|
||||
self.app = self.create_app()
|
||||
self.server = server
|
||||
@@ -89,8 +86,9 @@ class AbstractApp(object):
|
||||
|
||||
def add_api(self, specification, base_path=None, arguments=None,
|
||||
auth_all_paths=None, validate_responses=False,
|
||||
strict_validation=False, resolver=Resolver(), resolver_error=None,
|
||||
pythonic_params=False, options=None, pass_context_arg_name=None, **old_style_options):
|
||||
strict_validation=False, resolver=None, resolver_error=None,
|
||||
pythonic_params=False, pass_context_arg_name=None, options=None,
|
||||
validator_map=None):
|
||||
"""
|
||||
Adds an API to the application based on a swagger file or API dict
|
||||
|
||||
@@ -117,9 +115,8 @@ class AbstractApp(object):
|
||||
:type options: dict | None
|
||||
:param pass_context_arg_name: Name of argument in handler functions to pass request context to.
|
||||
:type pass_context_arg_name: str | None
|
||||
:param old_style_options: Old style options support for backward compatibility. Preference is
|
||||
what is defined in `options` parameter.
|
||||
:type old_style_options: dict
|
||||
:param validator_map: map of validators
|
||||
:type validator_map: dict
|
||||
:rtype: AbstractAPI
|
||||
"""
|
||||
# Turn the resolver_error code into a handler object
|
||||
@@ -128,6 +125,7 @@ class AbstractApp(object):
|
||||
if self.resolver_error is not None:
|
||||
resolver_error_handler = self._resolver_error_handler
|
||||
|
||||
resolver = resolver or self.resolver
|
||||
resolver = Resolver(resolver) if hasattr(resolver, '__call__') else resolver
|
||||
|
||||
auth_all_paths = auth_all_paths if auth_all_paths is not None else self.auth_all_paths
|
||||
@@ -140,12 +138,7 @@ class AbstractApp(object):
|
||||
else:
|
||||
specification = self.specification_dir / specification
|
||||
|
||||
# Old style options have higher priority compared to the already
|
||||
# defined options in the App class
|
||||
api_options = self.options.extend(old_style_options)
|
||||
|
||||
# locally defined options are added last to preserve highest priority
|
||||
api_options = api_options.extend(options)
|
||||
api_options = self.options.extend(options)
|
||||
|
||||
api = self.api_cls(specification,
|
||||
base_path=base_path,
|
||||
@@ -156,7 +149,7 @@ class AbstractApp(object):
|
||||
strict_validation=strict_validation,
|
||||
auth_all_paths=auth_all_paths,
|
||||
debug=self.debug,
|
||||
validator_map=self.validator_map,
|
||||
validator_map=validator_map,
|
||||
pythonic_params=pythonic_params,
|
||||
pass_context_arg_name=pass_context_arg_name,
|
||||
options=api_options.as_dict())
|
||||
@@ -164,10 +157,6 @@ class AbstractApp(object):
|
||||
|
||||
def _resolver_error_handler(self, *args, **kwargs):
|
||||
from connexion.handlers import ResolverErrorHandler
|
||||
kwargs['operation'] = {
|
||||
'operationId': 'connexion.handlers.ResolverErrorHandler',
|
||||
}
|
||||
kwargs.setdefault('app_consumes', ['application/json'])
|
||||
return ResolverErrorHandler(self.api_cls, self.resolver_error, *args, **kwargs)
|
||||
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||
|
||||
@@ -181,13 +181,18 @@ def run(spec_file,
|
||||
app_cls = connexion.utils.get_function_from_name(
|
||||
AVAILABLE_APPS[app_framework]
|
||||
)
|
||||
|
||||
options = {
|
||||
"swagger_json": not hide_spec,
|
||||
"swagger_path": console_ui_from or None,
|
||||
"swagger_ui": not hide_console_ui,
|
||||
"swagger_url": console_ui_url or None
|
||||
}
|
||||
|
||||
app = app_cls(__name__,
|
||||
swagger_json=not hide_spec,
|
||||
swagger_ui=not hide_console_ui,
|
||||
swagger_path=console_ui_from or None,
|
||||
swagger_url=console_ui_url or None,
|
||||
debug=debug,
|
||||
auth_all_paths=auth_all_paths,
|
||||
debug=debug)
|
||||
options=options)
|
||||
|
||||
app.add_api(spec_file_full_path,
|
||||
base_path=base_path,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import copy
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
@@ -7,8 +6,9 @@ import re
|
||||
import inflection
|
||||
import six
|
||||
|
||||
from ..http_facts import FORM_CONTENT_TYPES
|
||||
from ..lifecycle import ConnexionRequest # NOQA
|
||||
from ..utils import all_json, boolean, is_null, is_nullable
|
||||
from ..utils import all_json
|
||||
|
||||
try:
|
||||
import builtins
|
||||
@@ -24,14 +24,6 @@ try:
|
||||
except NameError: # pragma: no cover
|
||||
py_string = str # pragma: no cover
|
||||
|
||||
# https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#data-types
|
||||
TYPE_MAP = {'integer': int,
|
||||
'number': float,
|
||||
'string': py_string,
|
||||
'boolean': boolean,
|
||||
'array': list,
|
||||
'object': dict} # map of swagger types to python types
|
||||
|
||||
|
||||
def inspect_function_arguments(function): # pragma: no cover
|
||||
"""
|
||||
@@ -52,21 +44,6 @@ def inspect_function_arguments(function): # pragma: no cover
|
||||
return argspec.args, bool(argspec.keywords)
|
||||
|
||||
|
||||
def make_type(value, type):
|
||||
type_func = TYPE_MAP[type] # convert value to right type
|
||||
return type_func(value)
|
||||
|
||||
|
||||
def get_val_from_param(value, query_param):
|
||||
if is_nullable(query_param) and is_null(value):
|
||||
return None
|
||||
|
||||
if query_param["type"] == "array":
|
||||
return [make_type(v, query_param["items"]["type"]) for v in value]
|
||||
else:
|
||||
return make_type(value, query_param["type"])
|
||||
|
||||
|
||||
def snake_and_shadow(name):
|
||||
"""
|
||||
Converts the given name into Pythonic form. Firstly it converts CamelCase names to snake_case. Secondly it looks to
|
||||
@@ -81,17 +58,14 @@ def snake_and_shadow(name):
|
||||
return snake
|
||||
|
||||
|
||||
def parameter_to_arg(parameters, consumes, function, pythonic_params=False, pass_context_arg_name=None):
|
||||
def parameter_to_arg(operation, function, pythonic_params=False,
|
||||
pass_context_arg_name=None):
|
||||
"""
|
||||
Pass query and body parameters as keyword arguments to handler function.
|
||||
|
||||
See (https://github.com/zalando/connexion/issues/59)
|
||||
:param parameters: All the parameters of the handler functions
|
||||
:type parameters: dict|None
|
||||
:param consumes: The list of content types the operation consumes
|
||||
:type consumes: list
|
||||
:param function: The handler function for the REST endpoint.
|
||||
:type function: function|None
|
||||
:param operation: The operation being called
|
||||
:type operation: connexion.operations.AbstractOperation
|
||||
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended to
|
||||
any shadowed built-ins
|
||||
:type pythonic_params: bool
|
||||
@@ -99,25 +73,17 @@ def parameter_to_arg(parameters, consumes, function, pythonic_params=False, pass
|
||||
request context will be passed as that argument.
|
||||
:type pass_context_arg_name: str|None
|
||||
"""
|
||||
def sanitize_param(name):
|
||||
if name and pythonic_params:
|
||||
name = snake_and_shadow(name)
|
||||
consumes = operation.consumes
|
||||
|
||||
def sanitized(name):
|
||||
return name and re.sub('^[^a-zA-Z_]+', '', re.sub('[^0-9a-zA-Z_]', '', name))
|
||||
|
||||
body_parameters = [parameter for parameter in parameters if parameter['in'] == 'body'] or [{}]
|
||||
body_name = sanitize_param(body_parameters[0].get('name'))
|
||||
default_body = body_parameters[0].get('schema', {}).get('default')
|
||||
query_types = {sanitize_param(parameter['name']): parameter
|
||||
for parameter in parameters if parameter['in'] == 'query'} # type: dict[str, str]
|
||||
form_types = {sanitize_param(parameter['name']): parameter
|
||||
for parameter in parameters if parameter['in'] == 'formData'}
|
||||
path_types = {parameter['name']: parameter
|
||||
for parameter in parameters if parameter['in'] == 'path'}
|
||||
def pythonic(name):
|
||||
name = name and snake_and_shadow(name)
|
||||
return sanitized(name)
|
||||
|
||||
sanitize = pythonic if pythonic_params else sanitized
|
||||
arguments, has_kwargs = inspect_function_arguments(function)
|
||||
default_query_params = {sanitize_param(param['name']): param['default']
|
||||
for param in parameters if param['in'] == 'query' and 'default' in param}
|
||||
default_form_params = {sanitize_param(param['name']): param['default']
|
||||
for param in parameters if param['in'] == 'formData' and 'default' in param}
|
||||
|
||||
@functools.wraps(function)
|
||||
def wrapper(request):
|
||||
@@ -127,67 +93,20 @@ def parameter_to_arg(parameters, consumes, function, pythonic_params=False, pass
|
||||
|
||||
if all_json(consumes):
|
||||
request_body = request.json
|
||||
elif consumes[0] in FORM_CONTENT_TYPES:
|
||||
request_body = {sanitize(k): v for k, v in request.form.items()}
|
||||
else:
|
||||
request_body = request.body
|
||||
|
||||
if default_body and not request_body:
|
||||
request_body = default_body
|
||||
try:
|
||||
query = request.query.to_dict(flat=False)
|
||||
except AttributeError:
|
||||
query = dict(request.query.items())
|
||||
|
||||
# Parse path parameters
|
||||
path_params = request.path_params
|
||||
for key, value in path_params.items():
|
||||
if key in path_types:
|
||||
kwargs[key] = get_val_from_param(value, path_types[key])
|
||||
else: # Assume path params mechanism used for injection
|
||||
kwargs[key] = value
|
||||
|
||||
# Add body parameters
|
||||
if not has_kwargs and body_name not in arguments:
|
||||
logger.debug("Body parameter '%s' not in function arguments", body_name)
|
||||
elif body_name:
|
||||
logger.debug("Body parameter '%s' in function arguments", body_name)
|
||||
kwargs[body_name] = request_body
|
||||
|
||||
# Add query parameters
|
||||
query_arguments = copy.deepcopy(default_query_params)
|
||||
query_arguments.update(request.query)
|
||||
for key, value in query_arguments.items():
|
||||
key = sanitize_param(key)
|
||||
if not has_kwargs and key not in arguments:
|
||||
logger.debug("Query Parameter '%s' not in function arguments", key)
|
||||
else:
|
||||
logger.debug("Query Parameter '%s' in function arguments", key)
|
||||
try:
|
||||
query_param = query_types[key]
|
||||
except KeyError: # pragma: no cover
|
||||
logger.error("Function argument '{}' not defined in specification".format(key))
|
||||
else:
|
||||
logger.debug('%s is a %s', key, query_param)
|
||||
kwargs[key] = get_val_from_param(value, query_param)
|
||||
|
||||
# Add formData parameters
|
||||
form_arguments = copy.deepcopy(default_form_params)
|
||||
form_arguments.update({sanitize_param(k): v for k, v in request.form.items()})
|
||||
for key, value in form_arguments.items():
|
||||
if not has_kwargs and key not in arguments:
|
||||
logger.debug("FormData parameter '%s' not in function arguments", key)
|
||||
else:
|
||||
logger.debug("FormData parameter '%s' in function arguments", key)
|
||||
try:
|
||||
form_param = form_types[key]
|
||||
except KeyError: # pragma: no cover
|
||||
logger.error("Function argument '{}' not defined in specification".format(key))
|
||||
else:
|
||||
kwargs[key] = get_val_from_param(value, form_param)
|
||||
|
||||
# Add file parameters
|
||||
file_arguments = request.files
|
||||
for key, value in file_arguments.items():
|
||||
if not has_kwargs and key not in arguments:
|
||||
logger.debug("File parameter (formData) '%s' not in function arguments", key)
|
||||
else:
|
||||
logger.debug("File parameter (formData) '%s' in function arguments", key)
|
||||
kwargs[key] = value
|
||||
kwargs.update(
|
||||
operation.get_arguments(request.path_params, query, request_body,
|
||||
request.files, arguments, has_kwargs, sanitize)
|
||||
)
|
||||
|
||||
# optionally convert parameter variable names to un-shadowed, snake_case form
|
||||
if pythonic_params:
|
||||
|
||||
@@ -22,7 +22,7 @@ class BaseSerializer(BaseDecorator):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
return '<BaseSerializer: {}>'.format(self.mimetype)
|
||||
return '<BaseSerializer: {}>'.format(self.mimetype) # pragma: no cover
|
||||
|
||||
|
||||
class Produces(BaseSerializer):
|
||||
@@ -46,4 +46,4 @@ class Produces(BaseSerializer):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
return '<Produces: {}>'.format(self.mimetype)
|
||||
return '<Produces: {}>'.format(self.mimetype) # pragma: no cover
|
||||
|
||||
@@ -15,13 +15,17 @@ logger = logging.getLogger('connexion.decorators.response')
|
||||
|
||||
|
||||
class ResponseValidator(BaseDecorator):
|
||||
def __init__(self, operation, mimetype):
|
||||
def __init__(self, operation, mimetype, validator=None):
|
||||
"""
|
||||
:type operation: Operation
|
||||
:type mimetype: str
|
||||
:param validator: Validator class that should be used to validate passed data
|
||||
against API schema. Default is jsonschema.Draft4Validator.
|
||||
:type validator: jsonschema.IValidator
|
||||
"""
|
||||
self.operation = operation
|
||||
self.mimetype = mimetype
|
||||
self.validator = validator
|
||||
|
||||
def validate_response(self, data, status_code, headers, url):
|
||||
"""
|
||||
@@ -32,13 +36,15 @@ class ResponseValidator(BaseDecorator):
|
||||
:type headers: dict
|
||||
:rtype bool | None
|
||||
"""
|
||||
response_definitions = self.operation.operation["responses"]
|
||||
response_definition = response_definitions.get(str(status_code), response_definitions.get("default", {}))
|
||||
response_definition = self.operation.resolve_reference(response_definition)
|
||||
# check against returned header, fall back to expected mimetype
|
||||
content_type = headers.get("Content-Type", self.mimetype)
|
||||
content_type = content_type.rsplit(";", 1)[0] # remove things like utf8 metadata
|
||||
|
||||
if self.is_json_schema_compatible(response_definition):
|
||||
schema = response_definition.get("schema")
|
||||
v = ResponseBodyValidator(schema)
|
||||
response_definition = self.operation.response_definition(str(status_code), content_type)
|
||||
response_schema = self.operation.response_schema(str(status_code), content_type)
|
||||
|
||||
if self.is_json_schema_compatible(response_schema):
|
||||
v = ResponseBodyValidator(response_schema, validator=self.validator)
|
||||
try:
|
||||
data = self.operation.json_loads(data)
|
||||
v.validate_schema(data, url)
|
||||
@@ -57,7 +63,7 @@ class ResponseValidator(BaseDecorator):
|
||||
raise NonConformingResponseHeaders(message=msg)
|
||||
return True
|
||||
|
||||
def is_json_schema_compatible(self, response_definition):
|
||||
def is_json_schema_compatible(self, response_schema):
|
||||
"""
|
||||
Verify if the specified operation responses are JSON schema
|
||||
compatible.
|
||||
@@ -66,13 +72,12 @@ class ResponseValidator(BaseDecorator):
|
||||
type "application/json" or "text/plain" can be validated using
|
||||
json_schema package.
|
||||
|
||||
:type response_definition: dict
|
||||
:type response_schema: dict
|
||||
:rtype bool
|
||||
"""
|
||||
if not response_definition:
|
||||
if not response_schema:
|
||||
return False
|
||||
return ('schema' in response_definition and
|
||||
(all_json([self.mimetype]) or self.mimetype == 'text/plain'))
|
||||
return all_json([self.mimetype]) or self.mimetype == 'text/plain'
|
||||
|
||||
def __call__(self, function):
|
||||
"""
|
||||
@@ -110,4 +115,4 @@ class ResponseValidator(BaseDecorator):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
return '<ResponseValidator>'
|
||||
return '<ResponseValidator>' # pragma: no cover
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Authentication and authorization related decorators
|
||||
import base64
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
@@ -8,7 +9,8 @@ import requests
|
||||
|
||||
from connexion.utils import get_function_from_name
|
||||
|
||||
from ..exceptions import OAuthProblem, OAuthResponseProblem, OAuthScopeProblem
|
||||
from ..exceptions import (ConnexionException, OAuthProblem,
|
||||
OAuthResponseProblem, OAuthScopeProblem)
|
||||
|
||||
logger = logging.getLogger('connexion.api.security')
|
||||
|
||||
@@ -24,25 +26,65 @@ def get_tokeninfo_func(security_definition):
|
||||
:type security_definition: dict
|
||||
:rtype: function
|
||||
|
||||
>>> get_tokeninfo_url({'x-tokenInfoFunc': 'foo.bar>'})
|
||||
>>> get_tokeninfo_url({'x-tokenInfoFunc': 'foo.bar'})
|
||||
'<function foo.bar>'
|
||||
"""
|
||||
token_info_func = (security_definition.get("x-tokenInfoFunc") or
|
||||
os.environ.get('TOKENINFO_FUNC'))
|
||||
return get_function_from_name(token_info_func) if token_info_func else None
|
||||
if token_info_func:
|
||||
return get_function_from_name(token_info_func)
|
||||
|
||||
|
||||
def get_tokeninfo_url(security_definition):
|
||||
"""
|
||||
:type security_definition: dict
|
||||
:rtype: str
|
||||
|
||||
>>> get_tokeninfo_url({'x-tokenInfoUrl': 'foo'})
|
||||
'foo'
|
||||
"""
|
||||
token_info_url = (security_definition.get('x-tokenInfoUrl') or
|
||||
os.environ.get('TOKENINFO_URL'))
|
||||
return token_info_url
|
||||
if token_info_url:
|
||||
return functools.partial(get_tokeninfo_remote, token_info_url)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_scope_validate_func(security_definition):
|
||||
"""
|
||||
:type security_definition: dict
|
||||
:rtype: function
|
||||
|
||||
>>> get_scope_validate_func({'x-scopeValidateFunc': 'foo.bar'})
|
||||
'<function foo.bar>'
|
||||
"""
|
||||
func = (security_definition.get("x-scopeValidateFunc") or
|
||||
os.environ.get('SCOPEVALIDATE_FUNC'))
|
||||
if func:
|
||||
return get_function_from_name(func)
|
||||
return validate_scope
|
||||
|
||||
|
||||
def get_basicinfo_func(security_definition):
|
||||
"""
|
||||
:type security_definition: dict
|
||||
:rtype: function
|
||||
|
||||
>>> get_basicinfo_func({'x-basicInfoFunc': 'foo.bar'})
|
||||
'<function foo.bar>'
|
||||
"""
|
||||
func = (security_definition.get("x-basicInfoFunc") or
|
||||
os.environ.get('BASICINFO_FUNC'))
|
||||
if func:
|
||||
return get_function_from_name(func)
|
||||
return None
|
||||
|
||||
|
||||
def get_apikeyinfo_func(security_definition):
|
||||
"""
|
||||
:type security_definition: dict
|
||||
:rtype: function
|
||||
|
||||
>>> get_apikeyinfo_func({'x-apikeyInfoFunc': 'foo.bar'})
|
||||
'<function foo.bar>'
|
||||
"""
|
||||
func = (security_definition.get("x-apikeyInfoFunc") or
|
||||
os.environ.get('APIKEYINFO_FUNC'))
|
||||
if func:
|
||||
return get_function_from_name(func)
|
||||
return None
|
||||
|
||||
|
||||
def security_passthrough(function):
|
||||
@@ -53,104 +95,155 @@ def security_passthrough(function):
|
||||
return function
|
||||
|
||||
|
||||
def get_authorization_token(request):
|
||||
authorization = request.headers.get('Authorization') # type: str
|
||||
if not authorization:
|
||||
logger.info("... No auth provided. Aborting with 401.")
|
||||
raise OAuthProblem(description='No authorization token provided')
|
||||
else:
|
||||
try:
|
||||
_, token = authorization.split() # type: str, str
|
||||
except ValueError:
|
||||
raise OAuthProblem(description='Invalid authorization header')
|
||||
return token
|
||||
|
||||
|
||||
def validate_token_info(token_info, allowed_scopes):
|
||||
def security_deny(function):
|
||||
"""
|
||||
:param allowed_scopes:
|
||||
:param token_info: Dictionary containing the token_info
|
||||
:type token_info: dict
|
||||
:return: None
|
||||
"""
|
||||
scope = token_info.get('scope') or token_info.get('scopes')
|
||||
if isinstance(scope, list):
|
||||
user_scopes = set(scope)
|
||||
else:
|
||||
user_scopes = set(scope.split())
|
||||
logger.debug("... Scopes required: %s", allowed_scopes)
|
||||
logger.debug("... User scopes: %s", user_scopes)
|
||||
if not allowed_scopes <= user_scopes:
|
||||
logger.info(textwrap.dedent("""
|
||||
... User scopes (%s) do not match the scopes necessary to call endpoint (%s).
|
||||
Aborting with 403.""").replace('\n', ''),
|
||||
user_scopes, allowed_scopes)
|
||||
raise OAuthScopeProblem(
|
||||
description='Provided token doesn\'t have the required scope',
|
||||
required_scopes=allowed_scopes,
|
||||
token_scopes=user_scopes
|
||||
)
|
||||
logger.info("... Token authenticated.")
|
||||
|
||||
|
||||
def verify_oauth_local(token_info_func, allowed_scopes, function):
|
||||
"""
|
||||
Decorator to verify oauth locally
|
||||
|
||||
:param token_info_func: Function to get information about the token
|
||||
:type token_info_func: Function
|
||||
:param allowed_scopes: Set with scopes that are allowed to access the endpoint
|
||||
:type allowed_scopes: set
|
||||
:type function: types.FunctionType
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
def deny(*args, **kwargs):
|
||||
raise ConnexionException("Error in security definitions")
|
||||
return deny
|
||||
|
||||
@functools.wraps(function)
|
||||
def wrapper(request):
|
||||
logger.debug("%s Oauth local verification...", request.url)
|
||||
|
||||
token = get_authorization_token(request)
|
||||
def get_authorization_info(auth_funcs, request, required_scopes):
|
||||
for func in auth_funcs:
|
||||
token_info = func(request, required_scopes)
|
||||
if token_info is not None:
|
||||
return token_info
|
||||
|
||||
logger.info("... No auth provided. Aborting with 401.")
|
||||
raise OAuthProblem(description='No authorization token provided')
|
||||
|
||||
|
||||
def validate_scope(required_scopes, token_scopes):
|
||||
"""
|
||||
:param required_scopes: Scopes required to access operation
|
||||
:param token_scopes: Scopes granted by authorization server
|
||||
:rtype: bool
|
||||
"""
|
||||
required_scopes = set(required_scopes)
|
||||
if isinstance(token_scopes, list):
|
||||
token_scopes = set(token_scopes)
|
||||
else:
|
||||
token_scopes = set(token_scopes.split())
|
||||
logger.debug("... Scopes required: %s", required_scopes)
|
||||
logger.debug("... Token scopes: %s", token_scopes)
|
||||
if not required_scopes <= token_scopes:
|
||||
logger.info(textwrap.dedent("""
|
||||
... Token scopes (%s) do not match the scopes necessary to call endpoint (%s).
|
||||
Aborting with 403.""").replace('\n', ''),
|
||||
token_scopes, required_scopes)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def verify_oauth(token_info_func, scope_validate_func):
|
||||
def wrapper(request, required_scopes):
|
||||
authorization = request.headers.get('Authorization')
|
||||
if not authorization:
|
||||
return None
|
||||
|
||||
try:
|
||||
auth_type, token = authorization.split(None, 1)
|
||||
except ValueError:
|
||||
raise OAuthProblem(description='Invalid authorization header')
|
||||
|
||||
if auth_type.lower() != 'bearer':
|
||||
return None
|
||||
|
||||
token_info = token_info_func(token)
|
||||
if token_info is None:
|
||||
raise OAuthResponseProblem(
|
||||
description='Provided oauth token is not valid',
|
||||
token_response=token_info
|
||||
token_response=None
|
||||
)
|
||||
validate_token_info(token_info, allowed_scopes)
|
||||
request.context['user'] = token_info.get('uid')
|
||||
request.context['token_info'] = token_info
|
||||
return function(request)
|
||||
|
||||
# Fallback to 'scopes' for backward compability
|
||||
token_scopes = token_info.get('scope', token_info.get('scopes', ''))
|
||||
if not scope_validate_func(required_scopes, token_scopes):
|
||||
raise OAuthScopeProblem(
|
||||
description='Provided token doesn\'t have the required scope',
|
||||
required_scopes=required_scopes,
|
||||
token_scopes=token_scopes
|
||||
)
|
||||
|
||||
return token_info
|
||||
return wrapper
|
||||
|
||||
|
||||
def verify_oauth_remote(token_info_url, allowed_scopes, function):
|
||||
"""
|
||||
Decorator to verify oauth remotely using HTTP
|
||||
def verify_basic(basic_info_func):
|
||||
def wrapper(request, required_scopes):
|
||||
authorization = request.headers.get('Authorization')
|
||||
if not authorization:
|
||||
return None
|
||||
|
||||
:param token_info_url: Url to get information about the token
|
||||
:type token_info_url: str
|
||||
:param allowed_scopes: Set with scopes that are allowed to access the endpoint
|
||||
:type allowed_scopes: set
|
||||
:type function: types.FunctionType
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
try:
|
||||
auth_type, user_pass = authorization.split(None, 1)
|
||||
except ValueError:
|
||||
raise OAuthProblem(description='Invalid authorization header')
|
||||
|
||||
if auth_type.lower() != 'basic':
|
||||
return None
|
||||
|
||||
try:
|
||||
username, password = base64.b64decode(user_pass).decode('latin1').split(':', 1)
|
||||
except Exception:
|
||||
raise OAuthProblem(description='Invalid authorization header')
|
||||
|
||||
token_info = basic_info_func(username, password, required_scopes=required_scopes)
|
||||
if token_info is None:
|
||||
raise OAuthResponseProblem(
|
||||
description='Provided authorization is not valid',
|
||||
token_response=None
|
||||
)
|
||||
return token_info
|
||||
return wrapper
|
||||
|
||||
|
||||
def verify_apikey(apikey_info_func, loc, name):
|
||||
def wrapper(request, required_scopes):
|
||||
if loc == 'query':
|
||||
apikey = request.query.get(name)
|
||||
elif loc == 'header':
|
||||
apikey = request.headers.get(name)
|
||||
else:
|
||||
return None
|
||||
|
||||
if apikey is None:
|
||||
return None
|
||||
|
||||
token_info = apikey_info_func(apikey, required_scopes=required_scopes)
|
||||
if token_info is None:
|
||||
raise OAuthResponseProblem(
|
||||
description='Provided apikey is not valid',
|
||||
token_response=None
|
||||
)
|
||||
return token_info
|
||||
return wrapper
|
||||
|
||||
|
||||
def verify_security(auth_funcs, required_scopes, function):
|
||||
@functools.wraps(function)
|
||||
def wrapper(request):
|
||||
logger.debug("%s Oauth remote verification...", request.url)
|
||||
token = get_authorization_token(request)
|
||||
logger.debug("... Getting token from %s", token_info_url)
|
||||
token_request = session.get(token_info_url, headers={'Authorization': 'Bearer {}'.format(token)}, timeout=5)
|
||||
logger.debug("... Token info (%d): %s", token_request.status_code, token_request.text)
|
||||
if not token_request.ok:
|
||||
raise OAuthResponseProblem(
|
||||
description='Provided oauth token is not valid',
|
||||
token_response=token_request
|
||||
)
|
||||
token_info = get_authorization_info(auth_funcs, request, required_scopes)
|
||||
|
||||
token_info = token_request.json() # type: dict
|
||||
validate_token_info(token_info, allowed_scopes)
|
||||
request.context['user'] = token_info.get('uid')
|
||||
# Fallback to 'uid' for backward compability
|
||||
request.context['user'] = token_info.get('sub', token_info.get('uid'))
|
||||
request.context['token_info'] = token_info
|
||||
return function(request)
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_tokeninfo_remote(token_info_url, token):
|
||||
"""
|
||||
Retrieve oauth token_info remotely using HTTP
|
||||
:param token_info_url: Url to get information about the token
|
||||
:type token_info_url: str
|
||||
:param token: oauth token from authorization header
|
||||
:type token: str
|
||||
:rtype: dict
|
||||
"""
|
||||
token_request = session.get(token_info_url, headers={'Authorization': 'Bearer {}'.format(token)}, timeout=5)
|
||||
if not token_request.ok:
|
||||
return None
|
||||
return token_request.json()
|
||||
|
||||
@@ -9,12 +9,19 @@ from .decorator import BaseDecorator
|
||||
|
||||
logger = logging.getLogger('connexion.decorators.uri_parsing')
|
||||
|
||||
QUERY_STRING_DELIMITERS = {
|
||||
'spaceDelimited': ' ',
|
||||
'pipeDelimited': '|',
|
||||
'simple': ',',
|
||||
'form': ','
|
||||
}
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AbstractURIParser(BaseDecorator):
|
||||
parsable_parameters = ["query", "path"]
|
||||
|
||||
def __init__(self, param_defns):
|
||||
def __init__(self, param_defns, body_defn):
|
||||
"""
|
||||
a URI parser is initialized with parameter definitions.
|
||||
When called with a request object, it handles array types in the URI
|
||||
@@ -28,6 +35,8 @@ class AbstractURIParser(BaseDecorator):
|
||||
self._param_defns = {p["name"]: p
|
||||
for p in param_defns
|
||||
if p["in"] in self.parsable_parameters}
|
||||
self._body_schema = body_defn.get("schema", {})
|
||||
self._body_encoding = body_defn.get("encoding", {})
|
||||
|
||||
@abc.abstractproperty
|
||||
def param_defns(self):
|
||||
@@ -45,7 +54,13 @@ class AbstractURIParser(BaseDecorator):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
return "<{classname}>".format(classname=self.__class__.__name__)
|
||||
return "<{classname}>".format(
|
||||
classname=self.__class__.__name__) # pragma: no cover
|
||||
|
||||
@abc.abstractmethod
|
||||
def resolve_form(self, form_data):
|
||||
""" Resolve cases where form parameters are provided multiple times.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def _resolve_param_duplicates(self, values, param_defn):
|
||||
@@ -112,14 +127,73 @@ class AbstractURIParser(BaseDecorator):
|
||||
form = coerce_dict(request.form)
|
||||
|
||||
request.query = self.resolve_params(query, resolve_duplicates=True)
|
||||
request.form = self.resolve_params(form, resolve_duplicates=True)
|
||||
request.path_params = self.resolve_params(path_params)
|
||||
request.form = self.resolve_form(form)
|
||||
response = function(request)
|
||||
return response
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class OpenAPIURIParser(AbstractURIParser):
|
||||
|
||||
@property
|
||||
def param_defns(self):
|
||||
return self._param_defns
|
||||
|
||||
@property
|
||||
def form_defns(self):
|
||||
return {k: v for k, v in self._body_schema.get('properties', {}).items()}
|
||||
|
||||
@property
|
||||
def param_schemas(self):
|
||||
return {k: v.get('schema', {}) for k, v in self.param_defns.items()}
|
||||
|
||||
def resolve_form(self, form_data):
|
||||
if self._body_schema is None or self._body_schema.get('type') != 'object':
|
||||
return form_data
|
||||
for k in form_data:
|
||||
encoding = self._body_encoding.get(k, {"style": "form"})
|
||||
defn = self.form_defns.get(k, {})
|
||||
# TODO support more form encoding styles
|
||||
form_data[k] = \
|
||||
self._resolve_param_duplicates(form_data[k], encoding)
|
||||
if defn and defn["type"] == "array":
|
||||
form_data[k] = self._split(form_data[k], encoding)
|
||||
return form_data
|
||||
|
||||
@staticmethod
|
||||
def _resolve_param_duplicates(values, param_defn):
|
||||
""" Resolve cases where query parameters are provided multiple times.
|
||||
The default behavior is to use the first-defined value.
|
||||
For example, if the query string is '?a=1,2,3&a=4,5,6' the value of
|
||||
`a` would be "4,5,6".
|
||||
However, if 'explode' is 'True' then the duplicate values
|
||||
are concatenated together and `a` would be "1,2,3,4,5,6".
|
||||
"""
|
||||
try:
|
||||
style = param_defn['style']
|
||||
delimiter = QUERY_STRING_DELIMITERS.get(style, ',')
|
||||
is_form = (style == 'form')
|
||||
explode = param_defn.get('explode', is_form)
|
||||
if explode:
|
||||
return delimiter.join(values)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# default to last defined value
|
||||
return values[-1]
|
||||
|
||||
@staticmethod
|
||||
def _split(value, param_defn):
|
||||
try:
|
||||
style = param_defn['style']
|
||||
delimiter = QUERY_STRING_DELIMITERS.get(style, ',')
|
||||
return value.split(delimiter)
|
||||
except KeyError:
|
||||
return value.split(',')
|
||||
|
||||
|
||||
class Swagger2URIParser(AbstractURIParser):
|
||||
"""
|
||||
Adheres to the Swagger2 spec,
|
||||
@@ -135,6 +209,9 @@ class Swagger2URIParser(AbstractURIParser):
|
||||
def param_schemas(self):
|
||||
return self._param_defns # swagger2 conflates defn and schema
|
||||
|
||||
def resolve_form(self, form_data):
|
||||
return self.resolve_params(form_data, resolve_duplicates=True)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_param_duplicates(values, param_defn):
|
||||
""" Resolve cases where query parameters are provided multiple times.
|
||||
|
||||
@@ -5,11 +5,12 @@ import logging
|
||||
import sys
|
||||
|
||||
import six
|
||||
from jsonschema import (Draft4Validator, ValidationError,
|
||||
draft4_format_checker, validators)
|
||||
from jsonschema import Draft4Validator, ValidationError, draft4_format_checker
|
||||
from werkzeug import FileStorage
|
||||
|
||||
from ..exceptions import ExtraParameterProblem
|
||||
from ..http_facts import FORM_CONTENT_TYPES
|
||||
from ..json_schema import Draft4RequestValidator, Draft4ResponseValidator
|
||||
from ..problem import problem
|
||||
from ..utils import all_json, boolean, is_json_mimetype, is_null, is_nullable
|
||||
|
||||
@@ -22,11 +23,6 @@ TYPE_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def make_type(value, type_literal):
|
||||
type_func = TYPE_MAP.get(type_literal)
|
||||
return type_func(value)
|
||||
|
||||
|
||||
class TypeValidationError(Exception):
|
||||
def __init__(self, schema_type, parameter_type, parameter_name):
|
||||
"""
|
||||
@@ -46,14 +42,23 @@ class TypeValidationError(Exception):
|
||||
return msg.format(**vars(self))
|
||||
|
||||
|
||||
def validate_type(param, value, parameter_type, parameter_name=None):
|
||||
param_type = param.get('type')
|
||||
parameter_name = parameter_name if parameter_name else param['name']
|
||||
def coerce_type(param, value, parameter_type, parameter_name=None):
|
||||
|
||||
def make_type(value, type_literal):
|
||||
type_func = TYPE_MAP.get(type_literal)
|
||||
return type_func(value)
|
||||
|
||||
param_schema = param.get("schema", param)
|
||||
if is_nullable(param_schema) and is_null(value):
|
||||
return None
|
||||
|
||||
param_type = param_schema.get('type')
|
||||
parameter_name = parameter_name if parameter_name else param.get('name')
|
||||
if param_type == "array":
|
||||
converted_params = []
|
||||
for v in value:
|
||||
try:
|
||||
converted = make_type(v, param["items"]["type"])
|
||||
converted = make_type(v, param_schema["items"]["type"])
|
||||
except (ValueError, TypeError):
|
||||
converted = v
|
||||
converted_params.append(converted)
|
||||
@@ -74,40 +79,10 @@ def validate_parameter_list(request_params, spec_params):
|
||||
return request_params.difference(spec_params)
|
||||
|
||||
|
||||
def extend_with_nullable_support(validator_class):
|
||||
"""Add support for null values in body.
|
||||
|
||||
It adds property validator to given validator_class.
|
||||
|
||||
:param validator_class: validator to add nullable support
|
||||
:type validator_class: jsonschema.IValidator
|
||||
:return: new validator with added nullable support in properties
|
||||
:rtype: jsonschema.IValidator
|
||||
"""
|
||||
validate_properties = validator_class.VALIDATORS['properties']
|
||||
|
||||
def nullable_support(validator, properties, instance, schema):
|
||||
null_properties = {}
|
||||
for property_, subschema in six.iteritems(properties):
|
||||
if isinstance(instance, collections.Iterable) and \
|
||||
property_ in instance and \
|
||||
instance[property_] is None and \
|
||||
subschema.get('x-nullable') is True:
|
||||
# exclude from following validation
|
||||
null_properties[property_] = instance.pop(property_)
|
||||
for error in validate_properties(validator, properties, instance, schema):
|
||||
yield error
|
||||
# add null properties back
|
||||
if null_properties:
|
||||
instance.update(null_properties)
|
||||
return validators.extend(validator_class, {'properties': nullable_support})
|
||||
|
||||
|
||||
Draft4ValidatorSupportNullable = extend_with_nullable_support(Draft4Validator)
|
||||
|
||||
|
||||
class RequestBodyValidator(object):
|
||||
def __init__(self, schema, consumes, api, is_null_value_valid=False, validator=None):
|
||||
|
||||
def __init__(self, schema, consumes, api, is_null_value_valid=False, validator=None,
|
||||
strict_validation=False):
|
||||
"""
|
||||
:param schema: The schema of the request body
|
||||
:param consumes: The list of content types the operation consumes
|
||||
@@ -115,13 +90,21 @@ class RequestBodyValidator(object):
|
||||
:param validator: Validator class that should be used to validate passed data
|
||||
against API schema. Default is jsonschema.Draft4Validator.
|
||||
:type validator: jsonschema.IValidator
|
||||
:param strict_validation: Flag indicating if parameters not in spec are allowed
|
||||
"""
|
||||
self.consumes = consumes
|
||||
self.schema = schema
|
||||
self.has_default = schema.get('default', False)
|
||||
self.is_null_value_valid = is_null_value_valid
|
||||
validatorClass = validator or Draft4ValidatorSupportNullable
|
||||
validatorClass = validator or Draft4RequestValidator
|
||||
self.validator = validatorClass(schema, format_checker=draft4_format_checker)
|
||||
self.api = api
|
||||
self.strict_validation = strict_validation
|
||||
|
||||
def validate_formdata_parameter_list(self, request):
|
||||
request_params = request.form.keys()
|
||||
spec_params = self.schema.get('properties', {}).keys()
|
||||
return validate_parameter_list(request_params, spec_params)
|
||||
|
||||
def __call__(self, function):
|
||||
"""
|
||||
@@ -159,6 +142,25 @@ class RequestBodyValidator(object):
|
||||
error = self.validate_schema(data, request.url)
|
||||
if error and not self.has_default:
|
||||
return error
|
||||
elif self.consumes[0] in FORM_CONTENT_TYPES:
|
||||
data = dict(request.form.items()) or (request.body if len(request.body) > 0 else {})
|
||||
data.update(dict.fromkeys(request.files, '')) # validator expects string..
|
||||
logger.debug('%s validating schema...', request.url)
|
||||
|
||||
if self.strict_validation:
|
||||
formdata_errors = self.validate_formdata_parameter_list(request)
|
||||
if formdata_errors:
|
||||
raise ExtraParameterProblem(formdata_errors, [])
|
||||
|
||||
if data:
|
||||
props = self.schema.get("properties", {})
|
||||
for k, param_defn in props.items():
|
||||
if k in data:
|
||||
data[k] = coerce_type(param_defn, data[k], 'requestBody', k)
|
||||
|
||||
error = self.validate_schema(data, request.url)
|
||||
if error:
|
||||
return error
|
||||
|
||||
response = function(request)
|
||||
return response
|
||||
@@ -189,7 +191,7 @@ class ResponseBodyValidator(object):
|
||||
against API schema. Default is jsonschema.Draft4Validator.
|
||||
:type validator: jsonschema.IValidator
|
||||
"""
|
||||
ValidatorClass = validator or Draft4ValidatorSupportNullable
|
||||
ValidatorClass = validator or Draft4ResponseValidator
|
||||
self.validator = ValidatorClass(schema, format_checker=draft4_format_checker)
|
||||
|
||||
def validate_schema(self, data, url):
|
||||
@@ -209,6 +211,7 @@ class ParameterValidator(object):
|
||||
def __init__(self, parameters, api, strict_validation=False):
|
||||
"""
|
||||
:param parameters: List of request parameter dictionaries
|
||||
:param api: api that the validator is attached to
|
||||
:param strict_validation: Flag indicating if parameters not in spec are allowed
|
||||
"""
|
||||
self.parameters = collections.defaultdict(list)
|
||||
@@ -219,13 +222,13 @@ class ParameterValidator(object):
|
||||
self.strict_validation = strict_validation
|
||||
|
||||
@staticmethod
|
||||
def validate_parameter(parameter_type, value, param):
|
||||
def validate_parameter(parameter_type, value, param, param_name=None):
|
||||
if value is not None:
|
||||
if is_nullable(param) and is_null(value):
|
||||
return
|
||||
|
||||
try:
|
||||
converted_value = validate_type(param, value, parameter_type)
|
||||
converted_value = coerce_type(param, value, parameter_type, param_name)
|
||||
except TypeValidationError as e:
|
||||
return str(e)
|
||||
|
||||
@@ -284,11 +287,11 @@ class ParameterValidator(object):
|
||||
val = request.headers.get(param['name'])
|
||||
return self.validate_parameter('header', val, param)
|
||||
|
||||
def validate_formdata_parameter(self, param, request):
|
||||
if param.get('type') == 'file':
|
||||
val = request.files.get(param['name'])
|
||||
def validate_formdata_parameter(self, param_name, param, request):
|
||||
if param.get('type') == 'file' or param.get('format') == 'binary':
|
||||
val = request.files.get(param_name)
|
||||
else:
|
||||
val = request.form.get(param['name'])
|
||||
val = request.form.get(param_name)
|
||||
|
||||
return self.validate_parameter('formdata', val, param)
|
||||
|
||||
@@ -328,7 +331,7 @@ class ParameterValidator(object):
|
||||
return self.api.get_response(response)
|
||||
|
||||
for param in self.parameters.get('formData', []):
|
||||
error = self.validate_formdata_parameter(param, request)
|
||||
error = self.validate_formdata_parameter(param["name"], param, request)
|
||||
if error:
|
||||
response = problem(400, 'Bad Request', error)
|
||||
return self.api.get_response(response)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jsonschema.exceptions import ValidationError
|
||||
from werkzeug.exceptions import Forbidden, Unauthorized
|
||||
|
||||
from .problem import problem
|
||||
@@ -47,19 +48,8 @@ class ResolverError(LookupError):
|
||||
return '<ResolverError: {}>'.format(self.reason)
|
||||
|
||||
|
||||
class InvalidSpecification(ConnexionException):
|
||||
def __init__(self, reason='Unknown Reason'):
|
||||
"""
|
||||
:param reason: Reason why the specification is invalid
|
||||
:type reason: str
|
||||
"""
|
||||
self.reason = reason
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return '<InvalidSpecification: {}>'.format(self.reason)
|
||||
|
||||
def __repr__(self): # pragma: no cover
|
||||
return '<InvalidSpecification: {}>'.format(self.reason)
|
||||
class InvalidSpecification(ConnexionException, ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class NonConformingResponse(ConnexionException):
|
||||
@@ -102,7 +92,6 @@ class OAuthScopeProblem(Forbidden):
|
||||
def __init__(self, token_scopes, required_scopes, **kwargs):
|
||||
self.required_scopes = required_scopes
|
||||
self.token_scopes = token_scopes
|
||||
self.missing_scopes = required_scopes - token_scopes
|
||||
|
||||
super(OAuthScopeProblem, self).__init__(**kwargs)
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import logging
|
||||
|
||||
from .operation import Operation, SecureOperation
|
||||
from .operations.secure import SecureOperation
|
||||
from .problem import problem
|
||||
|
||||
logger = logging.getLogger('connexion.handlers')
|
||||
|
||||
RESOLVER_ERROR_ENDPOINT_RANDOM_DIGITS = 6
|
||||
|
||||
|
||||
class AuthErrorHandler(SecureOperation):
|
||||
"""
|
||||
@@ -25,7 +27,7 @@ class AuthErrorHandler(SecureOperation):
|
||||
:type security_definitions: dict
|
||||
"""
|
||||
self.exception = exception
|
||||
SecureOperation.__init__(self, api, security, security_definitions)
|
||||
super(AuthErrorHandler, self).__init__(api, security, security_definitions)
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
@@ -52,15 +54,15 @@ class AuthErrorHandler(SecureOperation):
|
||||
return self.api.get_response(response)
|
||||
|
||||
|
||||
class ResolverErrorHandler(Operation):
|
||||
class ResolverErrorHandler(SecureOperation):
|
||||
"""
|
||||
Handler for responding to ResolverError.
|
||||
"""
|
||||
|
||||
def __init__(self, api, status_code, exception, *args, **kwargs):
|
||||
def __init__(self, api, status_code, exception, security, security_definitions):
|
||||
self.status_code = status_code
|
||||
self.exception = exception
|
||||
Operation.__init__(self, api, *args, **kwargs)
|
||||
super(ResolverErrorHandler, self).__init__(api, security, security_definitions)
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
@@ -73,3 +75,14 @@ class ResolverErrorHandler(Operation):
|
||||
status=self.status_code
|
||||
)
|
||||
return self.api.get_response(response)
|
||||
|
||||
@property
|
||||
def operation_id(self):
|
||||
return "noop"
|
||||
|
||||
@property
|
||||
def randomize_endpoint(self):
|
||||
return RESOLVER_ERROR_ENDPOINT_RANDOM_DIGITS
|
||||
|
||||
def get_path_parameter_types(self):
|
||||
return []
|
||||
|
||||
4
connexion/http_facts.py
Normal file
@@ -0,0 +1,4 @@
|
||||
FORM_CONTENT_TYPES = [
|
||||
'application/x-www-form-urlencoded',
|
||||
'multipart/form-data'
|
||||
]
|
||||
109
connexion/json_schema.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import collections
|
||||
from copy import deepcopy
|
||||
|
||||
from jsonschema import Draft4Validator, RefResolver, _utils
|
||||
from jsonschema.exceptions import RefResolutionError, ValidationError # noqa
|
||||
from jsonschema.validators import extend
|
||||
from openapi_spec_validator.handlers import UrlHandler
|
||||
|
||||
from .utils import deep_get
|
||||
|
||||
default_handlers = {
|
||||
'http': UrlHandler('http'),
|
||||
'https': UrlHandler('https'),
|
||||
'file': UrlHandler('file'),
|
||||
}
|
||||
|
||||
|
||||
def resolve_refs(spec, store=None, handlers=None):
|
||||
"""
|
||||
Resolve JSON references like {"$ref": <some URI>} in a spec.
|
||||
Optionally takes a store, which is a mapping from reference URLs to a
|
||||
dereferenced objects. Prepopulating the store can avoid network calls.
|
||||
"""
|
||||
spec = deepcopy(spec)
|
||||
store = store or {}
|
||||
handlers = handlers or default_handlers
|
||||
resolver = RefResolver('', spec, store, handlers=handlers)
|
||||
|
||||
def _do_resolve(node):
|
||||
if isinstance(node, collections.Mapping) and '$ref' in node:
|
||||
path = node['$ref'][2:].split("/")
|
||||
try:
|
||||
# resolve known references
|
||||
node.update(deep_get(spec, path))
|
||||
del node['$ref']
|
||||
return node
|
||||
except KeyError:
|
||||
# resolve external references
|
||||
with resolver.resolving(node['$ref']) as resolved:
|
||||
return resolved
|
||||
elif isinstance(node, collections.Mapping):
|
||||
for k, v in node.items():
|
||||
node[k] = _do_resolve(v)
|
||||
elif isinstance(node, (list, tuple)):
|
||||
for i, _ in enumerate(node):
|
||||
node[i] = _do_resolve(node[i])
|
||||
return node
|
||||
|
||||
res = _do_resolve(spec)
|
||||
return res
|
||||
|
||||
|
||||
def validate_type(validator, types, instance, schema):
|
||||
if instance is None and (schema.get('x-nullable') is True or schema.get('nullable')):
|
||||
return
|
||||
|
||||
types = _utils.ensure_list(types)
|
||||
|
||||
if not any(validator.is_type(instance, type) for type in types):
|
||||
yield ValidationError(_utils.types_msg(instance, types))
|
||||
|
||||
|
||||
def validate_enum(validator, enums, instance, schema):
|
||||
if instance is None and (schema.get('x-nullable') is True or schema.get('nullable')):
|
||||
return
|
||||
|
||||
if instance not in enums:
|
||||
yield ValidationError("%r is not one of %r" % (instance, enums))
|
||||
|
||||
|
||||
def validate_required(validator, required, instance, schema):
|
||||
if not validator.is_type(instance, "object"):
|
||||
return
|
||||
|
||||
for prop in required:
|
||||
if prop not in instance:
|
||||
properties = schema.get('properties')
|
||||
if properties is not None:
|
||||
subschema = properties.get(prop)
|
||||
if subschema is not None:
|
||||
if 'readOnly' in validator.VALIDATORS and subschema.get('readOnly'):
|
||||
continue
|
||||
if 'writeOnly' in validator.VALIDATORS and subschema.get('writeOnly'):
|
||||
continue
|
||||
if 'x-writeOnly' in validator.VALIDATORS and subschema.get('x-writeOnly') is True:
|
||||
continue
|
||||
yield ValidationError("%r is a required property" % prop)
|
||||
|
||||
|
||||
def validate_readOnly(validator, ro, instance, schema):
|
||||
yield ValidationError("Property is read-only")
|
||||
|
||||
|
||||
def validate_writeOnly(validator, wo, instance, schema):
|
||||
yield ValidationError("Property is write-only")
|
||||
|
||||
|
||||
Draft4RequestValidator = extend(Draft4Validator, {
|
||||
'type': validate_type,
|
||||
'enum': validate_enum,
|
||||
'required': validate_required,
|
||||
'readOnly': validate_readOnly})
|
||||
|
||||
Draft4ResponseValidator = extend(Draft4Validator, {
|
||||
'type': validate_type,
|
||||
'enum': validate_enum,
|
||||
'required': validate_required,
|
||||
'writeOnly': validate_writeOnly,
|
||||
'x-writeOnly': validate_writeOnly})
|
||||
@@ -27,7 +27,7 @@ class MockResolver(Resolver):
|
||||
"""
|
||||
Mock operation resolver
|
||||
|
||||
:type operation: connexion.operation.Operation
|
||||
:type operation: connexion.operations.AbstractOperation
|
||||
"""
|
||||
operation_id = self.resolve_operation_id(operation)
|
||||
if not operation_id:
|
||||
@@ -51,33 +51,7 @@ class MockResolver(Resolver):
|
||||
return Resolution(func, operation_id)
|
||||
|
||||
def mock_operation(self, operation, *args, **kwargs):
|
||||
response_definitions = operation.operation["responses"]
|
||||
# simply use the first/lowest status code, this is probably 200 or 201
|
||||
status_code = sorted(response_definitions.keys())[0]
|
||||
response_definition = response_definitions.get(status_code, {})
|
||||
try:
|
||||
status_code = int(status_code)
|
||||
except ValueError:
|
||||
status_code = 200
|
||||
response_definition = operation.resolve_reference(response_definition)
|
||||
examples = response_definition.get('examples')
|
||||
if examples:
|
||||
return list(examples.values())[0], status_code
|
||||
else:
|
||||
# No response example, check for schema example
|
||||
response_schema = response_definition.get('schema', {})
|
||||
definitions = response_schema.get('definitions', {})
|
||||
schema_example = None
|
||||
ref = response_schema.get('$ref')
|
||||
if ref:
|
||||
# Referenced schema
|
||||
ref = ref[ref.rfind('/')+1:] or ''
|
||||
ref_schema = definitions.get(ref, {})
|
||||
schema_example = ref_schema.get('example')
|
||||
else:
|
||||
# Inline schema
|
||||
schema_example = response_schema.get('example')
|
||||
if schema_example:
|
||||
return schema_example, status_code
|
||||
else:
|
||||
return 'No example response was defined.', status_code
|
||||
resp, code = operation.example_response()
|
||||
if resp is not None:
|
||||
return resp, code
|
||||
return 'No example response was defined.', code
|
||||
|
||||
@@ -1,477 +0,0 @@
|
||||
import functools
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
|
||||
from .decorators.decorator import (BeginOfRequestLifecycleDecorator,
|
||||
EndOfRequestLifecycleDecorator)
|
||||
from .decorators.metrics import UWSGIMetricsCollector
|
||||
from .decorators.parameter import parameter_to_arg
|
||||
from .decorators.produces import BaseSerializer, Produces
|
||||
from .decorators.response import ResponseValidator
|
||||
from .decorators.security import (get_tokeninfo_func, get_tokeninfo_url,
|
||||
security_passthrough, verify_oauth_local,
|
||||
verify_oauth_remote)
|
||||
from .decorators.uri_parsing import AlwaysMultiURIParser
|
||||
from .decorators.validation import ParameterValidator, RequestBodyValidator
|
||||
from .exceptions import InvalidSpecification
|
||||
from .utils import all_json, is_nullable
|
||||
|
||||
logger = logging.getLogger('connexion.operation')
|
||||
|
||||
DEFAULT_MIMETYPE = 'application/json'
|
||||
|
||||
|
||||
VALIDATOR_MAP = {
|
||||
'parameter': ParameterValidator,
|
||||
'body': RequestBodyValidator,
|
||||
'response': ResponseValidator,
|
||||
}
|
||||
|
||||
|
||||
class SecureOperation(object):
|
||||
|
||||
def __init__(self, api, security, security_definitions):
|
||||
"""
|
||||
:param security: list of security rules the application uses by default
|
||||
:type security: list
|
||||
:param security_definitions: `Security Definitions Object
|
||||
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
|
||||
:type security_definitions: dict
|
||||
"""
|
||||
self.api = api
|
||||
self.security = security
|
||||
self.security_definitions = security_definitions
|
||||
|
||||
@property
|
||||
def security_decorator(self):
|
||||
"""
|
||||
Gets the security decorator for operation
|
||||
|
||||
From Swagger Specification:
|
||||
|
||||
**Security Definitions Object**
|
||||
|
||||
A declaration of the security schemes available to be used in the specification.
|
||||
|
||||
This does not enforce the security schemes on the operations and only serves to provide the relevant details
|
||||
for each scheme.
|
||||
|
||||
|
||||
**Security Requirement Object**
|
||||
|
||||
Lists the required security schemes to execute this operation. The object can have multiple security schemes
|
||||
declared in it which are all required (that is, there is a logical AND between the schemes).
|
||||
|
||||
The name used for each property **MUST** correspond to a security scheme declared in the Security Definitions.
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
logger.debug('... Security: %s', self.security, extra=vars(self))
|
||||
if self.security:
|
||||
if len(self.security) > 1:
|
||||
logger.debug("... More than one security requirement defined. **IGNORING SECURITY REQUIREMENTS**",
|
||||
extra=vars(self))
|
||||
return security_passthrough
|
||||
|
||||
security = self.security[0] # type: dict
|
||||
# the following line gets the first (and because of the previous condition only) scheme and scopes
|
||||
# from the operation's security requirements
|
||||
|
||||
scheme_name, scopes = next(iter(security.items())) # type: str, list
|
||||
security_definition = self.security_definitions[scheme_name]
|
||||
if security_definition['type'] == 'oauth2':
|
||||
token_info_url = get_tokeninfo_url(security_definition)
|
||||
token_info_func = get_tokeninfo_func(security_definition)
|
||||
scopes = set(scopes) # convert scopes to set because this is needed for verify_oauth_remote
|
||||
|
||||
if token_info_url and token_info_func:
|
||||
logger.warning("... Both x-tokenInfoUrl and x-tokenInfoFunc are defined, using x-tokenInfoFunc",
|
||||
extra=vars(self))
|
||||
if token_info_func:
|
||||
return functools.partial(verify_oauth_local, token_info_func, scopes)
|
||||
if token_info_url:
|
||||
return functools.partial(verify_oauth_remote, token_info_url, scopes)
|
||||
else:
|
||||
logger.warning("... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**",
|
||||
extra=vars(self))
|
||||
elif security_definition['type'] in ('apiKey', 'basic'):
|
||||
logger.debug(
|
||||
"... Security type '%s' not natively supported by Connexion; you should handle it yourself",
|
||||
security_definition['type'], extra=vars(self))
|
||||
|
||||
# if we don't know how to handle the security or it's not defined we will usa a passthrough decorator
|
||||
return security_passthrough
|
||||
|
||||
def get_mimetype(self):
|
||||
return DEFAULT_MIMETYPE
|
||||
|
||||
@property
|
||||
def _request_begin_lifecycle_decorator(self):
|
||||
"""
|
||||
Transforms the result of the operation handler in a internal
|
||||
representation (connexion.lifecycle.ConnexionRequest) to be
|
||||
used by internal Connexion decorators.
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
return BeginOfRequestLifecycleDecorator(self.api, self.get_mimetype())
|
||||
|
||||
@property
|
||||
def _request_end_lifecycle_decorator(self):
|
||||
"""
|
||||
Guarantees that instead of the internal representation of the
|
||||
operation handler response
|
||||
(connexion.lifecycle.ConnexionRequest) a framework specific
|
||||
object is returned.
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
return EndOfRequestLifecycleDecorator(self.api, self.get_mimetype())
|
||||
|
||||
|
||||
class Operation(SecureOperation):
|
||||
|
||||
"""
|
||||
A single API operation on a path.
|
||||
"""
|
||||
|
||||
def __init__(self, api, method, path, operation, resolver, app_produces, app_consumes,
|
||||
path_parameters=None, app_security=None, security_definitions=None,
|
||||
definitions=None, parameter_definitions=None, response_definitions=None,
|
||||
validate_responses=False, strict_validation=False, randomize_endpoint=None,
|
||||
validator_map=None, pythonic_params=False, uri_parser_class=None,
|
||||
pass_context_arg_name=None):
|
||||
"""
|
||||
This class uses the OperationID identify the module and function that will handle the operation
|
||||
|
||||
From Swagger Specification:
|
||||
|
||||
**OperationID**
|
||||
|
||||
A friendly name for the operation. The id MUST be unique among all operations described in the API.
|
||||
Tools and libraries MAY use the operation id to uniquely identify an operation.
|
||||
|
||||
:param method: HTTP method
|
||||
:type method: str
|
||||
:param path:
|
||||
:type path: str
|
||||
:param operation: swagger operation object
|
||||
:type operation: dict
|
||||
:param resolver: Callable that maps operationID to a function
|
||||
:param app_produces: list of content types the application can return by default
|
||||
:type app_produces: list
|
||||
:param app_consumes: list of content types the application consumes by default
|
||||
:type app_consumes: list
|
||||
:param validator_map: map of validators
|
||||
:type validator_map: dict
|
||||
:param path_parameters: Parameters defined in the path level
|
||||
:type path_parameters: list
|
||||
:param app_security: list of security rules the application uses by default
|
||||
:type app_security: list
|
||||
:param security_definitions: `Security Definitions Object
|
||||
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
|
||||
:type security_definitions: dict
|
||||
:param definitions: `Definitions Object
|
||||
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#definitionsObject>`_
|
||||
:type definitions: dict
|
||||
:param parameter_definitions: Global parameter definitions
|
||||
:type parameter_definitions: dict
|
||||
:param response_definitions: Global response definitions
|
||||
:type response_definitions: dict
|
||||
:param validator_map: Custom validators for the types "parameter", "body" and "response".
|
||||
:type validator_map: dict
|
||||
:param validate_responses: True enables validation. Validation errors generate HTTP 500 responses.
|
||||
:type validate_responses: bool
|
||||
:param strict_validation: True enables validation on invalid request parameters
|
||||
:type strict_validation: bool
|
||||
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended
|
||||
to any shadowed built-ins
|
||||
:type pythonic_params: bool
|
||||
:param uri_parser_class: A URI parser class that inherits from AbstractURIParser
|
||||
:type uri_parser_class: AbstractURIParser
|
||||
:param pass_context_arg_name: If not None will try to inject the request context to the function using this
|
||||
name.
|
||||
:type pass_context_arg_name: str|None
|
||||
"""
|
||||
|
||||
self.api = api
|
||||
self.method = method
|
||||
self.path = path
|
||||
self.validator_map = dict(VALIDATOR_MAP)
|
||||
self.validator_map.update(validator_map or {})
|
||||
self.security_definitions = security_definitions or {}
|
||||
self.definitions = definitions or {}
|
||||
self.parameter_definitions = parameter_definitions or {}
|
||||
self.response_definitions = response_definitions or {}
|
||||
self.definitions_map = {
|
||||
'definitions': self.definitions,
|
||||
'parameters': self.parameter_definitions,
|
||||
'responses': self.response_definitions
|
||||
}
|
||||
self.validate_responses = validate_responses
|
||||
self.strict_validation = strict_validation
|
||||
self.operation = operation
|
||||
self.randomize_endpoint = randomize_endpoint
|
||||
self.pythonic_params = pythonic_params
|
||||
self.uri_parser_class = uri_parser_class or AlwaysMultiURIParser
|
||||
self.pass_context_arg_name = pass_context_arg_name
|
||||
|
||||
# todo support definition references
|
||||
# todo support references to application level parameters
|
||||
self.parameters = list(self.resolve_parameters(operation.get('parameters', [])))
|
||||
if path_parameters:
|
||||
self.parameters += list(self.resolve_parameters(path_parameters))
|
||||
|
||||
self.security = operation.get('security', app_security)
|
||||
self.produces = operation.get('produces', app_produces)
|
||||
self.consumes = operation.get('consumes', app_consumes)
|
||||
|
||||
resolution = resolver.resolve(self)
|
||||
self.operation_id = resolution.operation_id
|
||||
self.__undecorated_function = resolution.function
|
||||
|
||||
def resolve_reference(self, schema):
|
||||
schema = deepcopy(schema) # avoid changing the original schema
|
||||
self.check_references(schema)
|
||||
|
||||
# find the object we need to resolve/update if this is not a proper SchemaObject
|
||||
# e.g a response or parameter object
|
||||
for obj in schema, schema.get('items'):
|
||||
reference = obj and obj.get('$ref') # type: str
|
||||
if reference:
|
||||
break
|
||||
if reference:
|
||||
definition = deepcopy(self._retrieve_reference(reference))
|
||||
# Update schema
|
||||
obj.update(definition)
|
||||
del obj['$ref']
|
||||
|
||||
# if there is a schema object on this param or response, then we just
|
||||
# need to include the defs and it can be validated by jsonschema
|
||||
if 'schema' in schema:
|
||||
schema['schema']['definitions'] = self.definitions
|
||||
return schema
|
||||
|
||||
return schema
|
||||
|
||||
def check_references(self, schema):
|
||||
"""
|
||||
Searches the keys and values of a schema object for json references.
|
||||
If it finds one, it attempts to locate it and will thrown an exception
|
||||
if the reference can't be found in the definitions dictionary.
|
||||
|
||||
:param schema: The schema object to check
|
||||
:type schema: dict
|
||||
:raises InvalidSpecification: raised when a reference isn't found
|
||||
"""
|
||||
|
||||
stack = [schema]
|
||||
visited = set()
|
||||
while stack:
|
||||
schema = stack.pop()
|
||||
for k, v in schema.items():
|
||||
if k == "$ref":
|
||||
if v in visited:
|
||||
continue
|
||||
visited.add(v)
|
||||
stack.append(self._retrieve_reference(v))
|
||||
elif isinstance(v, (list, tuple)):
|
||||
continue
|
||||
elif hasattr(v, "items"):
|
||||
stack.append(v)
|
||||
|
||||
def _retrieve_reference(self, reference):
|
||||
if not reference.startswith('#/'):
|
||||
raise InvalidSpecification(
|
||||
"{method} {path} '$ref' needs to start with '#/'".format(**vars(self)))
|
||||
path = reference.split('/')
|
||||
definition_type = path[1]
|
||||
try:
|
||||
definitions = self.definitions_map[definition_type]
|
||||
except KeyError:
|
||||
ref_possible = ', '.join(self.definitions_map.keys())
|
||||
raise InvalidSpecification(
|
||||
"{method} {path} $ref \"{reference}\" needs to point to one of: "
|
||||
"{ref_possible}".format(
|
||||
method=self.method,
|
||||
path=self.path,
|
||||
reference=reference,
|
||||
ref_possible=ref_possible
|
||||
))
|
||||
definition_name = path[-1]
|
||||
try:
|
||||
# Get sub definition
|
||||
definition = deepcopy(definitions[definition_name])
|
||||
except KeyError:
|
||||
raise InvalidSpecification(
|
||||
"{method} {path} Definition '{definition_name}' not found".format(
|
||||
definition_name=definition_name, method=self.method, path=self.path))
|
||||
|
||||
return definition
|
||||
|
||||
def get_mimetype(self):
|
||||
"""
|
||||
If the endpoint has no 'produces' then the default is
|
||||
'application/json'.
|
||||
|
||||
:rtype str
|
||||
"""
|
||||
if all_json(self.produces):
|
||||
try:
|
||||
return self.produces[0]
|
||||
except IndexError:
|
||||
return DEFAULT_MIMETYPE
|
||||
elif len(self.produces) == 1:
|
||||
return self.produces[0]
|
||||
else:
|
||||
return DEFAULT_MIMETYPE
|
||||
|
||||
def resolve_parameters(self, parameters):
|
||||
for param in parameters:
|
||||
param = self.resolve_reference(param)
|
||||
yield param
|
||||
|
||||
def get_path_parameter_types(self):
|
||||
return {p['name']: 'path' if p.get('type') == 'string' and p.get('format') == 'path' else p.get('type')
|
||||
for p in self.parameters if p['in'] == 'path'}
|
||||
|
||||
@property
|
||||
def body_schema(self):
|
||||
"""
|
||||
The body schema definition for this operation.
|
||||
"""
|
||||
return self.body_definition.get('schema')
|
||||
|
||||
@property
|
||||
def body_definition(self):
|
||||
"""
|
||||
The body complete definition for this operation.
|
||||
|
||||
**There can be one "body" parameter at most.**
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
body_parameters = [parameter for parameter in self.parameters if parameter['in'] == 'body']
|
||||
if len(body_parameters) > 1:
|
||||
raise InvalidSpecification(
|
||||
"{method} {path} There can be one 'body' parameter at most".format(**vars(self)))
|
||||
|
||||
return body_parameters[0] if body_parameters else {}
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
"""
|
||||
Operation function with decorators
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
|
||||
function = parameter_to_arg(
|
||||
self.parameters, self.consumes, self.__undecorated_function, self.pythonic_params,
|
||||
self.pass_context_arg_name)
|
||||
function = self._request_begin_lifecycle_decorator(function)
|
||||
|
||||
if self.validate_responses:
|
||||
logger.debug('... Response validation enabled.')
|
||||
response_decorator = self.__response_validation_decorator
|
||||
logger.debug('... Adding response decorator (%r)', response_decorator)
|
||||
function = response_decorator(function)
|
||||
|
||||
produces_decorator = self.__content_type_decorator
|
||||
logger.debug('... Adding produces decorator (%r)', produces_decorator)
|
||||
function = produces_decorator(function)
|
||||
|
||||
for validation_decorator in self.__validation_decorators:
|
||||
function = validation_decorator(function)
|
||||
|
||||
uri_parsing_decorator = self.__uri_parsing_decorator
|
||||
logger.debug('... Adding uri parsing decorator (%r)', uri_parsing_decorator)
|
||||
function = uri_parsing_decorator(function)
|
||||
|
||||
# NOTE: the security decorator should be applied last to check auth before anything else :-)
|
||||
security_decorator = self.security_decorator
|
||||
logger.debug('... Adding security decorator (%r)', security_decorator)
|
||||
function = security_decorator(function)
|
||||
|
||||
if UWSGIMetricsCollector.is_available(): # pragma: no cover
|
||||
decorator = UWSGIMetricsCollector(self.path, self.method)
|
||||
function = decorator(function)
|
||||
|
||||
function = self._request_end_lifecycle_decorator(function)
|
||||
|
||||
return function
|
||||
|
||||
@property
|
||||
def __uri_parsing_decorator(self):
|
||||
"""
|
||||
Get uri parsing decorator
|
||||
|
||||
This decorator handles query and path parameter deduplication and
|
||||
array types.
|
||||
"""
|
||||
return self.uri_parser_class(self.parameters)
|
||||
|
||||
@property
|
||||
def __content_type_decorator(self):
|
||||
"""
|
||||
Get produces decorator.
|
||||
|
||||
If the operation mimetype format is json then the function return value is jsonified
|
||||
|
||||
From Swagger Specification:
|
||||
|
||||
**Produces**
|
||||
|
||||
A list of MIME types the operation can produce. This overrides the produces definition at the Swagger Object.
|
||||
An empty value MAY be used to clear the global definition.
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
|
||||
logger.debug('... Produces: %s', self.produces, extra=vars(self))
|
||||
|
||||
mimetype = self.get_mimetype()
|
||||
if all_json(self.produces): # endpoint will return json
|
||||
logger.debug('... Produces json', extra=vars(self))
|
||||
# TODO: Refactor this.
|
||||
return lambda f: f
|
||||
|
||||
elif len(self.produces) == 1:
|
||||
logger.debug('... Produces %s', mimetype, extra=vars(self))
|
||||
decorator = Produces(mimetype)
|
||||
return decorator
|
||||
|
||||
else:
|
||||
return BaseSerializer()
|
||||
|
||||
@property
|
||||
def __validation_decorators(self):
|
||||
"""
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
ParameterValidator = self.validator_map['parameter']
|
||||
RequestBodyValidator = self.validator_map['body']
|
||||
if self.parameters:
|
||||
yield ParameterValidator(self.parameters,
|
||||
self.api,
|
||||
strict_validation=self.strict_validation)
|
||||
if self.body_schema:
|
||||
yield RequestBodyValidator(self.body_schema, self.consumes, self.api,
|
||||
is_nullable(self.body_definition))
|
||||
|
||||
@property
|
||||
def __response_validation_decorator(self):
|
||||
"""
|
||||
Get a decorator for validating the generated Response.
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
ResponseValidator = self.validator_map['response']
|
||||
return ResponseValidator(self, self.get_mimetype())
|
||||
|
||||
def json_loads(self, data):
|
||||
"""
|
||||
A wrapper for calling the API specific JSON loader.
|
||||
|
||||
:param data: The JSON data in textual form.
|
||||
:type data: bytes
|
||||
"""
|
||||
return self.api.json_loads(data)
|
||||
4
connexion/operations/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .abstract import AbstractOperation # noqa
|
||||
from .openapi import OpenAPIOperation # noqa
|
||||
from .swagger2 import Swagger2Operation # noqa
|
||||
from .secure import SecureOperation # noqa
|
||||
444
connexion/operations/abstract.py
Normal file
@@ -0,0 +1,444 @@
|
||||
import abc
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
from connexion.operations.secure import SecureOperation
|
||||
|
||||
from ..decorators.metrics import UWSGIMetricsCollector
|
||||
from ..decorators.parameter import parameter_to_arg
|
||||
from ..decorators.produces import BaseSerializer, Produces
|
||||
from ..decorators.response import ResponseValidator
|
||||
from ..decorators.validation import ParameterValidator, RequestBodyValidator
|
||||
from ..utils import all_json, is_nullable
|
||||
|
||||
logger = logging.getLogger('connexion.operations.abstract')
|
||||
|
||||
DEFAULT_MIMETYPE = 'application/json'
|
||||
|
||||
VALIDATOR_MAP = {
|
||||
'parameter': ParameterValidator,
|
||||
'body': RequestBodyValidator,
|
||||
'response': ResponseValidator,
|
||||
}
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AbstractOperation(SecureOperation):
|
||||
|
||||
"""
|
||||
An API routes requests to an Operation by a (path, method) pair.
|
||||
The operation uses a resolver to resolve its handler function.
|
||||
We use the provided spec to do a bunch of heavy lifting before
|
||||
(and after) we call security_schemes handler.
|
||||
The registered handler function ends up looking something like:
|
||||
|
||||
@secure_endpoint
|
||||
@validate_inputs
|
||||
@deserialize_function_inputs
|
||||
@serialize_function_outputs
|
||||
@validate_outputs
|
||||
def user_provided_handler_function(important, stuff):
|
||||
if important:
|
||||
serious_business(stuff)
|
||||
"""
|
||||
def __init__(self, api, method, path, operation, resolver,
|
||||
app_security=None, security_schemes=None,
|
||||
validate_responses=False, strict_validation=False,
|
||||
randomize_endpoint=None, validator_map=None,
|
||||
pythonic_params=False, uri_parser_class=None,
|
||||
pass_context_arg_name=None):
|
||||
"""
|
||||
:param api: api that this operation is attached to
|
||||
:type api: apis.AbstractAPI
|
||||
:param method: HTTP method
|
||||
:type method: str
|
||||
:param path:
|
||||
:type path: str
|
||||
:param operation: swagger operation object
|
||||
:type operation: dict
|
||||
:param resolver: Callable that maps operationID to a function
|
||||
:param app_produces: list of content types the application can return by default
|
||||
:param app_security: list of security rules the application uses by default
|
||||
:type app_security: list
|
||||
:param security_schemes: `Security Definitions Object
|
||||
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
|
||||
:type security_schemes: dict
|
||||
:param validate_responses: True enables validation. Validation errors generate HTTP 500 responses.
|
||||
:type validate_responses: bool
|
||||
:param strict_validation: True enables validation on invalid request parameters
|
||||
:type strict_validation: bool
|
||||
:param randomize_endpoint: number of random characters to append to operation name
|
||||
:type randomize_endpoint: integer
|
||||
:param validator_map: Custom validators for the types "parameter", "body" and "response".
|
||||
:type validator_map: dict
|
||||
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended
|
||||
to any shadowed built-ins
|
||||
:type pythonic_params: bool
|
||||
:param uri_parser_class: class to use for uri parseing
|
||||
:type uri_parser_class: AbstractURIParser
|
||||
:param pass_context_arg_name: If not None will try to inject the request context to the function using this
|
||||
name.
|
||||
:type pass_context_arg_name: str|None
|
||||
"""
|
||||
self._api = api
|
||||
self._method = method
|
||||
self._path = path
|
||||
self._operation = operation
|
||||
self._resolver = resolver
|
||||
self._security = app_security
|
||||
self._security_schemes = security_schemes
|
||||
self._validate_responses = validate_responses
|
||||
self._strict_validation = strict_validation
|
||||
self._pythonic_params = pythonic_params
|
||||
self._uri_parser_class = uri_parser_class
|
||||
self._pass_context_arg_name = pass_context_arg_name
|
||||
self._randomize_endpoint = randomize_endpoint
|
||||
|
||||
self._operation_id = self._operation.get("operationId")
|
||||
self._resolution = resolver.resolve(self)
|
||||
self._operation_id = self._resolution.operation_id
|
||||
|
||||
self._validator_map = dict(VALIDATOR_MAP)
|
||||
self._validator_map.update(validator_map or {})
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
"""
|
||||
The HTTP method for this operation (ex. GET, POST)
|
||||
"""
|
||||
return self._method
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
The path of the operation, relative to the API base path
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def validator_map(self):
|
||||
"""
|
||||
Validators to use for parameter, body, and response validation
|
||||
"""
|
||||
return self._validator_map
|
||||
|
||||
@property
|
||||
def operation_id(self):
|
||||
"""
|
||||
The operation id used to indentify the operation internally to the app
|
||||
"""
|
||||
return self._operation_id
|
||||
|
||||
@property
|
||||
def randomize_endpoint(self):
|
||||
"""
|
||||
number of random digits to generate and append to the operation_id.
|
||||
"""
|
||||
return self._randomize_endpoint
|
||||
|
||||
@property
|
||||
def router_controller(self):
|
||||
"""
|
||||
The router controller to use (python module where handler functions live)
|
||||
"""
|
||||
return self._router_controller
|
||||
|
||||
@property
|
||||
def strict_validation(self):
|
||||
"""
|
||||
If True, validate all requests against the spec
|
||||
"""
|
||||
return self._strict_validation
|
||||
|
||||
@property
|
||||
def pythonic_params(self):
|
||||
"""
|
||||
If True, convert CamelCase into pythonic_variable_names
|
||||
"""
|
||||
return self._pythonic_params
|
||||
|
||||
@property
|
||||
def validate_responses(self):
|
||||
"""
|
||||
If True, check the response against the response schema, and return an
|
||||
error if the response does not validate.
|
||||
"""
|
||||
return self._validate_responses
|
||||
|
||||
@staticmethod
|
||||
def _get_file_arguments(files, arguments, has_kwargs=False):
|
||||
return {k: v for k, v in files.items() if not has_kwargs and k in arguments}
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_val_from_param(self, value, query_defn):
|
||||
"""
|
||||
Convert input parameters into the correct type
|
||||
"""
|
||||
|
||||
def _query_args_helper(self, query_defns, query_arguments,
|
||||
function_arguments, has_kwargs, sanitize):
|
||||
res = {}
|
||||
for key, value in query_arguments.items():
|
||||
key = sanitize(key)
|
||||
if not has_kwargs and key not in function_arguments:
|
||||
logger.debug("Query Parameter '%s' not in function arguments", key)
|
||||
else:
|
||||
logger.debug("Query Parameter '%s' in function arguments", key)
|
||||
try:
|
||||
query_defn = query_defns[key]
|
||||
except KeyError: # pragma: no cover
|
||||
logger.error("Function argument '{}' not defined in specification".format(key))
|
||||
else:
|
||||
logger.debug('%s is a %s', key, query_defn)
|
||||
res[key] = self._get_val_from_param(value, query_defn)
|
||||
return res
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_query_arguments(self, query, arguments, has_kwargs, sanitize):
|
||||
"""
|
||||
extract handler function arguments from the query parameters
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_body_argument(self, body, arguments, has_kwargs, sanitize):
|
||||
"""
|
||||
extract handler function arguments from the request body
|
||||
"""
|
||||
|
||||
def _get_path_arguments(self, path_params, sanitize):
|
||||
"""
|
||||
extract handler function arguments from path parameters
|
||||
"""
|
||||
kwargs = {}
|
||||
path_defns = {p["name"]: p for p in self.parameters if p["in"] == "path"}
|
||||
for key, value in path_params.items():
|
||||
key = sanitize(key)
|
||||
if key in path_defns:
|
||||
kwargs[key] = self._get_val_from_param(value, path_defns[key])
|
||||
else: # Assume path params mechanism used for injection
|
||||
kwargs[key] = value
|
||||
return kwargs
|
||||
|
||||
@abc.abstractproperty
|
||||
def parameters(self):
|
||||
"""
|
||||
Returns the parameters for this operation
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def responses(self):
|
||||
"""
|
||||
Returns the responses for this operation
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def produces(self):
|
||||
"""
|
||||
Content-Types that the operation produces
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def consumes(self):
|
||||
"""
|
||||
Content-Types that the operation consumes
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def body_schema(self):
|
||||
"""
|
||||
The body schema definition for this operation.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def body_definition(self):
|
||||
"""
|
||||
The body definition for this operation.
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
def get_arguments(self, path_params, query_params, body, files, arguments,
|
||||
has_kwargs, sanitize):
|
||||
"""
|
||||
get arguments for handler function
|
||||
"""
|
||||
ret = {}
|
||||
ret.update(self._get_path_arguments(path_params, sanitize))
|
||||
ret.update(self._get_query_arguments(query_params, arguments,
|
||||
has_kwargs, sanitize))
|
||||
ret.update(self._get_body_argument(body, arguments,
|
||||
has_kwargs, sanitize))
|
||||
ret.update(self._get_file_arguments(files, arguments, has_kwargs))
|
||||
return ret
|
||||
|
||||
def response_definition(self, status_code=None,
|
||||
content_type=None):
|
||||
"""
|
||||
response definition for this endpoint
|
||||
"""
|
||||
content_type = content_type or self.get_mimetype()
|
||||
response_definition = self.responses.get(
|
||||
str(status_code),
|
||||
self.responses.get("default", {})
|
||||
)
|
||||
return response_definition
|
||||
|
||||
@abc.abstractmethod
|
||||
def response_schema(self, status_code=None, content_type=None):
|
||||
"""
|
||||
response schema for this endpoint
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def example_response(self, status_code=None, content_type=None):
|
||||
"""
|
||||
Returns an example from the spec
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_path_parameter_types(self):
|
||||
"""
|
||||
Returns the types for parameters in the path
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def with_definitions(self, schema):
|
||||
"""
|
||||
Returns the given schema, but with the definitions from the spec
|
||||
attached. This allows any remaining references to be resolved by a
|
||||
validator (for example).
|
||||
"""
|
||||
|
||||
def get_mimetype(self):
|
||||
"""
|
||||
If the endpoint has no 'produces' then the default is
|
||||
'application/json'.
|
||||
|
||||
:rtype str
|
||||
"""
|
||||
if all_json(self.produces):
|
||||
try:
|
||||
return self.produces[0]
|
||||
except IndexError:
|
||||
return DEFAULT_MIMETYPE
|
||||
elif len(self.produces) == 1:
|
||||
return self.produces[0]
|
||||
else:
|
||||
return DEFAULT_MIMETYPE
|
||||
|
||||
@property
|
||||
def _uri_parsing_decorator(self):
|
||||
"""
|
||||
Returns a decorator that parses request data and handles things like
|
||||
array types, and duplicate parameter definitions.
|
||||
"""
|
||||
return self._uri_parser_class(self.parameters, self.body_definition)
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
"""
|
||||
Operation function with decorators
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
function = parameter_to_arg(
|
||||
self, self._resolution.function, self.pythonic_params,
|
||||
self._pass_context_arg_name
|
||||
)
|
||||
function = self._request_begin_lifecycle_decorator(function)
|
||||
|
||||
if self.validate_responses:
|
||||
logger.debug('... Response validation enabled.')
|
||||
response_decorator = self.__response_validation_decorator
|
||||
logger.debug('... Adding response decorator (%r)', response_decorator)
|
||||
function = response_decorator(function)
|
||||
|
||||
produces_decorator = self.__content_type_decorator
|
||||
logger.debug('... Adding produces decorator (%r)', produces_decorator)
|
||||
function = produces_decorator(function)
|
||||
|
||||
for validation_decorator in self.__validation_decorators:
|
||||
function = validation_decorator(function)
|
||||
|
||||
uri_parsing_decorator = self._uri_parsing_decorator
|
||||
function = uri_parsing_decorator(function)
|
||||
|
||||
# NOTE: the security decorator should be applied last to check auth before anything else :-)
|
||||
security_decorator = self.security_decorator
|
||||
logger.debug('... Adding security decorator (%r)', security_decorator)
|
||||
function = security_decorator(function)
|
||||
|
||||
if UWSGIMetricsCollector.is_available(): # pragma: no cover
|
||||
decorator = UWSGIMetricsCollector(self.path, self.method)
|
||||
function = decorator(function)
|
||||
|
||||
function = self._request_end_lifecycle_decorator(function)
|
||||
|
||||
return function
|
||||
|
||||
@property
|
||||
def __content_type_decorator(self):
|
||||
"""
|
||||
Get produces decorator.
|
||||
|
||||
If the operation mimetype format is json then the function return value is jsonified
|
||||
|
||||
From Swagger Specification:
|
||||
|
||||
**Produces**
|
||||
|
||||
A list of MIME types the operation can produce. This overrides the produces definition at the Swagger Object.
|
||||
An empty value MAY be used to clear the global definition.
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
|
||||
logger.debug('... Produces: %s', self.produces, extra=vars(self))
|
||||
|
||||
mimetype = self.get_mimetype()
|
||||
if all_json(self.produces): # endpoint will return json
|
||||
logger.debug('... Produces json', extra=vars(self))
|
||||
# TODO: Refactor this.
|
||||
return lambda f: f
|
||||
|
||||
elif len(self.produces) == 1:
|
||||
logger.debug('... Produces %s', mimetype, extra=vars(self))
|
||||
decorator = Produces(mimetype)
|
||||
return decorator
|
||||
|
||||
else:
|
||||
return BaseSerializer()
|
||||
|
||||
@property
|
||||
def __validation_decorators(self):
|
||||
"""
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
ParameterValidator = self.validator_map['parameter']
|
||||
RequestBodyValidator = self.validator_map['body']
|
||||
if self.parameters:
|
||||
yield ParameterValidator(self.parameters,
|
||||
self.api,
|
||||
strict_validation=self.strict_validation)
|
||||
if self.body_schema:
|
||||
yield RequestBodyValidator(self.body_schema, self.consumes, self.api,
|
||||
is_nullable(self.body_definition),
|
||||
strict_validation=self.strict_validation)
|
||||
|
||||
@property
|
||||
def __response_validation_decorator(self):
|
||||
"""
|
||||
Get a decorator for validating the generated Response.
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
ResponseValidator = self.validator_map['response']
|
||||
return ResponseValidator(self, self.get_mimetype())
|
||||
|
||||
def json_loads(self, data):
|
||||
"""
|
||||
A wrapper for calling the API specific JSON loader.
|
||||
|
||||
:param data: The JSON data in textual form.
|
||||
:type data: bytes
|
||||
"""
|
||||
return self.api.json_loads(data)
|
||||
295
connexion/operations/openapi.py
Normal file
@@ -0,0 +1,295 @@
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
|
||||
from connexion.operations.abstract import AbstractOperation
|
||||
|
||||
from ..decorators.uri_parsing import OpenAPIURIParser
|
||||
from ..utils import deep_get, is_null, is_nullable, make_type
|
||||
|
||||
logger = logging.getLogger("connexion.operations.openapi3")
|
||||
|
||||
|
||||
class OpenAPIOperation(AbstractOperation):
|
||||
|
||||
"""
|
||||
A single API operation on a path.
|
||||
"""
|
||||
|
||||
def __init__(self, api, method, path, operation, resolver, path_parameters=None,
|
||||
app_security=None, components=None, validate_responses=False,
|
||||
strict_validation=False, randomize_endpoint=None, validator_map=None,
|
||||
pythonic_params=False, uri_parser_class=None, pass_context_arg_name=None):
|
||||
"""
|
||||
This class uses the OperationID identify the module and function that will handle the operation
|
||||
|
||||
From Swagger Specification:
|
||||
|
||||
**OperationID**
|
||||
|
||||
A friendly name for the operation. The id MUST be unique among all operations described in the API.
|
||||
Tools and libraries MAY use the operation id to uniquely identify an operation.
|
||||
|
||||
:param method: HTTP method
|
||||
:type method: str
|
||||
:param path:
|
||||
:type path: str
|
||||
:param operation: swagger operation object
|
||||
:type operation: dict
|
||||
:param resolver: Callable that maps operationID to a function
|
||||
:param path_parameters: Parameters defined in the path level
|
||||
:type path_parameters: list
|
||||
:param app_security: list of security rules the application uses by default
|
||||
:type app_security: list
|
||||
:param components: `Components Object
|
||||
<https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#componentsObject>`_
|
||||
:type components: dict
|
||||
:param validate_responses: True enables validation. Validation errors generate HTTP 500 responses.
|
||||
:type validate_responses: bool
|
||||
:param strict_validation: True enables validation on invalid request parameters
|
||||
:type strict_validation: bool
|
||||
:param randomize_endpoint: number of random characters to append to operation name
|
||||
:type randomize_endpoint: integer
|
||||
:param validator_map: Custom validators for the types "parameter", "body" and "response".
|
||||
:type validator_map: dict
|
||||
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended
|
||||
to any shadowed built-ins
|
||||
:type pythonic_params: bool
|
||||
:param uri_parser_class: class to use for uri parseing
|
||||
:type uri_parser_class: AbstractURIParser
|
||||
:param pass_context_arg_name: If not None will try to inject the request context to the function using this
|
||||
name.
|
||||
:type pass_context_arg_name: str|None
|
||||
"""
|
||||
self.components = components or {}
|
||||
|
||||
def component_get(oas3_name):
|
||||
return self.components.get(oas3_name, {})
|
||||
|
||||
# operation overrides globals
|
||||
security_schemes = component_get('securitySchemes')
|
||||
app_security = operation.get('security', app_security)
|
||||
uri_parser_class = uri_parser_class or OpenAPIURIParser
|
||||
|
||||
self._router_controller = operation.get('x-openapi-router-controller')
|
||||
|
||||
super(OpenAPIOperation, self).__init__(
|
||||
api=api,
|
||||
method=method,
|
||||
path=path,
|
||||
operation=operation,
|
||||
resolver=resolver,
|
||||
app_security=app_security,
|
||||
security_schemes=security_schemes,
|
||||
validate_responses=validate_responses,
|
||||
strict_validation=strict_validation,
|
||||
randomize_endpoint=randomize_endpoint,
|
||||
validator_map=validator_map,
|
||||
pythonic_params=pythonic_params,
|
||||
uri_parser_class=uri_parser_class,
|
||||
pass_context_arg_name=pass_context_arg_name
|
||||
)
|
||||
|
||||
self._definitions_map = {
|
||||
'components': {
|
||||
'schemas': component_get('schemas'),
|
||||
'examples': component_get('examples'),
|
||||
'requestBodies': component_get('requestBodies'),
|
||||
'parameters': component_get('parameters'),
|
||||
'securitySchemes': component_get('securitySchemes'),
|
||||
'responses': component_get('responses'),
|
||||
'headers': component_get('headers'),
|
||||
}
|
||||
}
|
||||
|
||||
self._request_body = operation.get('requestBody')
|
||||
|
||||
self._parameters = operation.get('parameters', [])
|
||||
if path_parameters:
|
||||
self._parameters += path_parameters
|
||||
|
||||
self._responses = operation.get('responses', {})
|
||||
|
||||
# TODO figure out how to support multiple mimetypes
|
||||
# NOTE we currently just combine all of the possible mimetypes,
|
||||
# but we need to refactor to support mimetypes by response code
|
||||
response_codes = operation.get('responses', {})
|
||||
response_content_types = []
|
||||
for _, defn in response_codes.items():
|
||||
response_content_types += defn.get('content', {}).keys()
|
||||
self._produces = response_content_types or ['application/json']
|
||||
|
||||
request_content = operation.get('requestBody', {}).get('content', {})
|
||||
self._consumes = list(request_content.keys()) or ['application/json']
|
||||
|
||||
logger.debug('consumes: %s' % self.consumes)
|
||||
logger.debug('produces: %s' % self.produces)
|
||||
|
||||
@property
|
||||
def request_body(self):
|
||||
return self._request_body
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
return self._parameters
|
||||
|
||||
@property
|
||||
def responses(self):
|
||||
return self._responses
|
||||
|
||||
@property
|
||||
def consumes(self):
|
||||
return self._consumes
|
||||
|
||||
@property
|
||||
def produces(self):
|
||||
return self._produces
|
||||
|
||||
@property
|
||||
def _spec_definitions(self):
|
||||
return self._definitions_map
|
||||
|
||||
def with_definitions(self, schema):
|
||||
if self.components:
|
||||
schema['schema']['components'] = self.components
|
||||
return schema
|
||||
|
||||
def response_schema(self, status_code=None, content_type=None):
|
||||
response_definition = self.response_definition(
|
||||
status_code, content_type
|
||||
)
|
||||
content_definition = response_definition.get("content", response_definition)
|
||||
content_definition = content_definition.get(content_type, content_definition)
|
||||
if "schema" in content_definition:
|
||||
return self.with_definitions(content_definition).get("schema", {})
|
||||
return {}
|
||||
|
||||
def example_response(self, status_code=None, content_type=None):
|
||||
"""
|
||||
Returns example response from spec
|
||||
"""
|
||||
# simply use the first/lowest status code, this is probably 200 or 201
|
||||
status_code = status_code or sorted(self._responses.keys())[0]
|
||||
|
||||
content_type = content_type or self.get_mimetype()
|
||||
examples_path = [str(status_code), 'content', content_type, 'examples']
|
||||
example_path = [str(status_code), 'content', content_type, 'example']
|
||||
schema_example_path = [
|
||||
str(status_code), 'content', content_type, 'schema', 'example'
|
||||
]
|
||||
|
||||
try:
|
||||
status_code = int(status_code)
|
||||
except ValueError:
|
||||
status_code = 200
|
||||
try:
|
||||
# TODO also use example header?
|
||||
return (
|
||||
list(deep_get(self._responses, examples_path).values())[0],
|
||||
status_code
|
||||
)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
try:
|
||||
return (deep_get(self._responses, example_path), status_code)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
return (deep_get(self._responses, schema_example_path),
|
||||
status_code)
|
||||
except KeyError:
|
||||
return (None, status_code)
|
||||
|
||||
def get_path_parameter_types(self):
|
||||
types = {}
|
||||
path_parameters = (p for p in self.parameters if p["in"] == "path")
|
||||
for path_defn in path_parameters:
|
||||
path_schema = path_defn["schema"]
|
||||
if path_schema.get('type') == 'string' and path_schema.get('format') == 'path':
|
||||
# path is special case for type 'string'
|
||||
path_type = 'path'
|
||||
else:
|
||||
path_type = path_schema.get('type')
|
||||
types[path_defn['name']] = path_type
|
||||
return types
|
||||
|
||||
@property
|
||||
def body_schema(self):
|
||||
"""
|
||||
The body schema definition for this operation.
|
||||
"""
|
||||
return self.body_definition.get('schema', {})
|
||||
|
||||
@property
|
||||
def body_definition(self):
|
||||
"""
|
||||
The body complete definition for this operation.
|
||||
|
||||
**There can be one "body" parameter at most.**
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
if self._request_body:
|
||||
if len(self.consumes) > 1:
|
||||
logger.warning(
|
||||
'this operation accepts multiple content types, using %s',
|
||||
self.consumes[0])
|
||||
res = self._request_body.get('content', {}).get(self.consumes[0], {})
|
||||
return self.with_definitions(res)
|
||||
return {}
|
||||
|
||||
def _get_body_argument(self, body, arguments, has_kwargs, sanitize):
|
||||
|
||||
x_body_name = self.body_schema.get('x-body-name', 'body')
|
||||
if is_nullable(self.body_schema) and is_null(body):
|
||||
return {x_body_name: None}
|
||||
|
||||
default_body = self.body_schema.get('default', {})
|
||||
body_props = {sanitize(k): {"schema": v} for k, v
|
||||
in self.body_schema.get("properties", {}).items()}
|
||||
|
||||
body = body or default_body
|
||||
|
||||
if self.body_schema.get("type") != "object":
|
||||
if x_body_name in arguments or has_kwargs:
|
||||
return {x_body_name: body}
|
||||
return {}
|
||||
|
||||
body_arg = deepcopy(default_body)
|
||||
body_arg.update(body or {})
|
||||
|
||||
res = {}
|
||||
if body_props:
|
||||
for key, value in body_arg.items():
|
||||
key = sanitize(key)
|
||||
try:
|
||||
prop_defn = body_props[key]
|
||||
res[key] = self._get_val_from_param(value, prop_defn)
|
||||
except KeyError: # pragma: no cover
|
||||
logger.error("Body property '{}' not defined in body schema".format(key))
|
||||
if x_body_name in arguments or has_kwargs:
|
||||
return {x_body_name: res}
|
||||
return {}
|
||||
|
||||
def _get_query_arguments(self, query, arguments, has_kwargs, sanitize):
|
||||
query_defns = {sanitize(p["name"]): p
|
||||
for p in self.parameters
|
||||
if p["in"] == "query"}
|
||||
default_query_params = {k: v["schema"]['default']
|
||||
for k, v in query_defns.items()
|
||||
if 'default' in v["schema"]}
|
||||
|
||||
query_arguments = deepcopy(default_query_params)
|
||||
query_arguments.update(query)
|
||||
return self._query_args_helper(query_defns, query_arguments,
|
||||
arguments, has_kwargs, sanitize)
|
||||
|
||||
def _get_val_from_param(self, value, query_defn):
|
||||
query_schema = query_defn["schema"]
|
||||
|
||||
if is_nullable(query_schema) and is_null(value):
|
||||
return None
|
||||
|
||||
if query_schema["type"] == "array":
|
||||
return [make_type(part, query_schema["items"]["type"]) for part in value]
|
||||
else:
|
||||
return make_type(value, query_schema["type"])
|
||||
160
connexion/operations/secure.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from ..decorators.decorator import (BeginOfRequestLifecycleDecorator,
|
||||
EndOfRequestLifecycleDecorator)
|
||||
from ..decorators.security import (get_apikeyinfo_func, get_basicinfo_func,
|
||||
get_scope_validate_func, get_tokeninfo_func,
|
||||
security_deny, security_passthrough,
|
||||
verify_apikey, verify_basic, verify_oauth,
|
||||
verify_security)
|
||||
|
||||
logger = logging.getLogger("connexion.operations.secure")
|
||||
|
||||
DEFAULT_MIMETYPE = 'application/json'
|
||||
|
||||
|
||||
class SecureOperation(object):
|
||||
|
||||
def __init__(self, api, security, security_schemes):
|
||||
"""
|
||||
:param security: list of security rules the application uses by default
|
||||
:type security: list
|
||||
:param security_definitions: `Security Definitions Object
|
||||
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
|
||||
:type security_definitions: dict
|
||||
"""
|
||||
self._api = api
|
||||
self._security = security
|
||||
self._security_schemes = security_schemes
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
return self._api
|
||||
|
||||
@property
|
||||
def security(self):
|
||||
return self._security
|
||||
|
||||
@property
|
||||
def security_schemes(self):
|
||||
return self._security_schemes
|
||||
|
||||
@property
|
||||
def security_decorator(self):
|
||||
"""
|
||||
Gets the security decorator for operation
|
||||
|
||||
From Swagger Specification:
|
||||
|
||||
**Security Definitions Object**
|
||||
|
||||
A declaration of the security schemes available to be used in the specification.
|
||||
|
||||
This does not enforce the security schemes on the operations and only serves to provide the relevant details
|
||||
for each scheme.
|
||||
|
||||
|
||||
**Operation Object -> security**
|
||||
|
||||
A declaration of which security schemes are applied for this operation. The list of values describes alternative
|
||||
security schemes that can be used (that is, there is a logical OR between the security requirements).
|
||||
This definition overrides any declared top-level security. To remove a top-level security declaration,
|
||||
an empty array can be used.
|
||||
|
||||
|
||||
**Security Requirement Object**
|
||||
|
||||
Lists the required security schemes to execute this operation. The object can have multiple security schemes
|
||||
declared in it which are all required (that is, there is a logical AND between the schemes).
|
||||
|
||||
The name used for each property **MUST** correspond to a security scheme declared in the Security Definitions.
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
logger.debug('... Security: %s', self.security, extra=vars(self))
|
||||
if not self.security:
|
||||
return security_passthrough
|
||||
|
||||
auth_funcs = []
|
||||
required_scopes = None
|
||||
for security_req in self.security:
|
||||
if not security_req:
|
||||
continue
|
||||
elif len(security_req) > 1:
|
||||
logger.warning("... More than one security scheme in security requirement defined. "
|
||||
"**DENYING ALL REQUESTS**", extra=vars(self))
|
||||
return security_deny
|
||||
|
||||
scheme_name, scopes = next(iter(security_req.items()))
|
||||
security_scheme = self.security_schemes[scheme_name]
|
||||
|
||||
if security_scheme['type'] == 'oauth2':
|
||||
required_scopes = scopes
|
||||
token_info_func = get_tokeninfo_func(security_scheme)
|
||||
scope_validate_func = get_scope_validate_func(security_scheme)
|
||||
if not token_info_func:
|
||||
logger.warning("... x-tokenInfoFunc missing", extra=vars(self))
|
||||
continue
|
||||
|
||||
auth_funcs.append(verify_oauth(token_info_func, scope_validate_func))
|
||||
|
||||
# Swagger 2.0
|
||||
elif security_scheme['type'] == 'basic':
|
||||
basic_info_func = get_basicinfo_func(security_scheme)
|
||||
if not basic_info_func:
|
||||
logger.warning("... x-basicInfoFunc missing", extra=vars(self))
|
||||
continue
|
||||
|
||||
auth_funcs.append(verify_basic(basic_info_func))
|
||||
|
||||
# OpenAPI 3.0.0
|
||||
elif security_scheme['type'] == 'http':
|
||||
scheme = security_scheme['scheme'].lower()
|
||||
if scheme == 'basic':
|
||||
basic_info_func = get_basicinfo_func(security_scheme)
|
||||
if not basic_info_func:
|
||||
logger.warning("... x-basicInfoFunc missing", extra=vars(self))
|
||||
continue
|
||||
|
||||
auth_funcs.append(verify_basic(basic_info_func))
|
||||
else:
|
||||
logger.warning("... Unsupported http authorization scheme %s" % scheme, extra=vars(self))
|
||||
|
||||
elif security_scheme['type'] == 'apiKey':
|
||||
apikey_info_func = get_apikeyinfo_func(security_scheme)
|
||||
if not apikey_info_func:
|
||||
logger.warning("... x-apikeyInfoFunc missing", extra=vars(self))
|
||||
continue
|
||||
|
||||
auth_funcs.append(verify_apikey(apikey_info_func, security_scheme['in'], security_scheme['name']))
|
||||
|
||||
else:
|
||||
logger.warning("... Unsupported security scheme type %s" % security_scheme['type'], extra=vars(self))
|
||||
|
||||
return functools.partial(verify_security, auth_funcs, required_scopes)
|
||||
|
||||
def get_mimetype(self):
|
||||
return DEFAULT_MIMETYPE
|
||||
|
||||
@property
|
||||
def _request_begin_lifecycle_decorator(self):
|
||||
"""
|
||||
Transforms the result of the operation handler in a internal
|
||||
representation (connexion.lifecycle.ConnexionRequest) to be
|
||||
used by internal Connexion decorators.
|
||||
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
return BeginOfRequestLifecycleDecorator(self.api, self.get_mimetype())
|
||||
|
||||
@property
|
||||
def _request_end_lifecycle_decorator(self):
|
||||
"""
|
||||
Guarantees that instead of the internal representation of the
|
||||
operation handler response
|
||||
(connexion.lifecycle.ConnexionRequest) a framework specific
|
||||
object is returned.
|
||||
:rtype: types.FunctionType
|
||||
"""
|
||||
return EndOfRequestLifecycleDecorator(self.api, self.get_mimetype())
|
||||
270
connexion/operations/swagger2.py
Normal file
@@ -0,0 +1,270 @@
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
|
||||
from connexion.operations.abstract import AbstractOperation
|
||||
|
||||
from ..decorators.uri_parsing import Swagger2URIParser
|
||||
from ..exceptions import InvalidSpecification
|
||||
from ..utils import deep_get, is_null, is_nullable, make_type
|
||||
|
||||
logger = logging.getLogger("connexion.operations.swagger2")
|
||||
|
||||
|
||||
class Swagger2Operation(AbstractOperation):
|
||||
|
||||
"""
|
||||
Exposes a Swagger 2.0 operation under the AbstractOperation interface.
|
||||
The primary purpose of this class is to provide the `function()` method
|
||||
to the API. A Swagger2Operation is plugged into the API with the provided
|
||||
(path, method) pair. It resolves the handler function for this operation
|
||||
with the provided resolver, and wraps the handler function with multiple
|
||||
decorators that provide security, validation, serialization,
|
||||
and deserialization.
|
||||
"""
|
||||
|
||||
def __init__(self, api, method, path, operation, resolver, app_produces, app_consumes,
|
||||
path_parameters=None, app_security=None, security_definitions=None,
|
||||
definitions=None, parameter_definitions=None,
|
||||
response_definitions=None, validate_responses=False, strict_validation=False,
|
||||
randomize_endpoint=None, validator_map=None, pythonic_params=False,
|
||||
uri_parser_class=None, pass_context_arg_name=None):
|
||||
"""
|
||||
:param api: api that this operation is attached to
|
||||
:type api: apis.AbstractAPI
|
||||
:param method: HTTP method
|
||||
:type method: str
|
||||
:param path: relative path to this operation
|
||||
:type path: str
|
||||
:param operation: swagger operation object
|
||||
:type operation: dict
|
||||
:param resolver: Callable that maps operationID to a function
|
||||
:type resolver: resolver.Resolver
|
||||
:param app_produces: list of content types the application can return by default
|
||||
:type app_produces: list
|
||||
:param app_consumes: list of content types the application consumes by default
|
||||
:type app_consumes: list
|
||||
:param path_parameters: Parameters defined in the path level
|
||||
:type path_parameters: list
|
||||
:param app_security: list of security rules the application uses by default
|
||||
:type app_security: list
|
||||
:param security_definitions: `Security Definitions Object
|
||||
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
|
||||
:type security_definitions: dict
|
||||
:param definitions: `Definitions Object
|
||||
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#definitionsObject>`_
|
||||
:type definitions: dict
|
||||
:param parameter_definitions: Global parameter definitions
|
||||
:type parameter_definitions: dict
|
||||
:param response_definitions: Global response definitions
|
||||
:type response_definitions: dict
|
||||
:param validate_responses: True enables validation. Validation errors generate HTTP 500 responses.
|
||||
:type validate_responses: bool
|
||||
:param strict_validation: True enables validation on invalid request parameters
|
||||
:type strict_validation: bool
|
||||
:param randomize_endpoint: number of random characters to append to operation name
|
||||
:type randomize_endpoint: integer
|
||||
:param validator_map: Custom validators for the types "parameter", "body" and "response".
|
||||
:type validator_map: dict
|
||||
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended
|
||||
to any shadowed built-ins
|
||||
:type pythonic_params: bool
|
||||
:param uri_parser_class: class to use for uri parseing
|
||||
:type uri_parser_class: AbstractURIParser
|
||||
:param pass_context_arg_name: If not None will try to inject the request context to the function using this
|
||||
name.
|
||||
:type pass_context_arg_name: str|None
|
||||
"""
|
||||
app_security = operation.get('security', app_security)
|
||||
uri_parser_class = uri_parser_class or Swagger2URIParser
|
||||
|
||||
self._router_controller = operation.get('x-swagger-router-controller')
|
||||
|
||||
super(Swagger2Operation, self).__init__(
|
||||
api=api,
|
||||
method=method,
|
||||
path=path,
|
||||
operation=operation,
|
||||
resolver=resolver,
|
||||
app_security=app_security,
|
||||
security_schemes=security_definitions,
|
||||
validate_responses=validate_responses,
|
||||
strict_validation=strict_validation,
|
||||
randomize_endpoint=randomize_endpoint,
|
||||
validator_map=validator_map,
|
||||
pythonic_params=pythonic_params,
|
||||
uri_parser_class=uri_parser_class,
|
||||
pass_context_arg_name=pass_context_arg_name
|
||||
)
|
||||
|
||||
self._produces = operation.get('produces', app_produces)
|
||||
self._consumes = operation.get('consumes', app_consumes)
|
||||
|
||||
self.definitions = definitions or {}
|
||||
|
||||
self.definitions_map = {
|
||||
'definitions': self.definitions,
|
||||
'parameters': parameter_definitions,
|
||||
'responses': response_definitions
|
||||
}
|
||||
|
||||
self._parameters = operation.get('parameters', [])
|
||||
if path_parameters:
|
||||
self._parameters += path_parameters
|
||||
|
||||
self._responses = operation.get('responses', {})
|
||||
logger.debug(self._responses)
|
||||
|
||||
logger.debug('consumes: %s', self.consumes)
|
||||
logger.debug('produces: %s', self.produces)
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
return self._parameters
|
||||
|
||||
@property
|
||||
def responses(self):
|
||||
return self._responses
|
||||
|
||||
@property
|
||||
def consumes(self):
|
||||
return self._consumes
|
||||
|
||||
@property
|
||||
def produces(self):
|
||||
return self._produces
|
||||
|
||||
def get_path_parameter_types(self):
|
||||
types = {}
|
||||
path_parameters = (p for p in self.parameters if p["in"] == "path")
|
||||
for path_defn in path_parameters:
|
||||
if path_defn.get('type') == 'string' and path_defn.get('format') == 'path':
|
||||
# path is special case for type 'string'
|
||||
path_type = 'path'
|
||||
else:
|
||||
path_type = path_defn.get('type')
|
||||
types[path_defn['name']] = path_type
|
||||
return types
|
||||
|
||||
def with_definitions(self, schema):
|
||||
if "schema" in schema:
|
||||
schema['schema']['definitions'] = self.definitions
|
||||
return schema
|
||||
|
||||
def response_schema(self, status_code=None, content_type=None):
|
||||
response_definition = self.response_definition(
|
||||
status_code, content_type
|
||||
)
|
||||
return self.with_definitions(response_definition.get("schema", {}))
|
||||
|
||||
def example_response(self, status_code=None, *args, **kwargs):
|
||||
"""
|
||||
Returns example response from spec
|
||||
"""
|
||||
# simply use the first/lowest status code, this is probably 200 or 201
|
||||
status_code = status_code or sorted(self._responses.keys())[0]
|
||||
examples_path = [str(status_code), 'examples']
|
||||
schema_example_path = [str(status_code), 'schema', 'example']
|
||||
try:
|
||||
status_code = int(status_code)
|
||||
except ValueError:
|
||||
status_code = 200
|
||||
try:
|
||||
return (
|
||||
list(deep_get(self._responses, examples_path).values())[0],
|
||||
status_code
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
return (deep_get(self._responses, schema_example_path),
|
||||
status_code)
|
||||
except KeyError:
|
||||
return (None, status_code)
|
||||
|
||||
@property
|
||||
def body_schema(self):
|
||||
"""
|
||||
The body schema definition for this operation.
|
||||
"""
|
||||
return self.with_definitions(self.body_definition).get('schema', {})
|
||||
|
||||
@property
|
||||
def body_definition(self):
|
||||
"""
|
||||
The body complete definition for this operation.
|
||||
|
||||
**There can be one "body" parameter at most.**
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
body_parameters = [p for p in self.parameters if p['in'] == 'body']
|
||||
if len(body_parameters) > 1:
|
||||
raise InvalidSpecification(
|
||||
"{method} {path} There can be one 'body' parameter at most".format(
|
||||
method=self.method,
|
||||
path=self.path))
|
||||
return body_parameters[0] if body_parameters else {}
|
||||
|
||||
def _get_query_arguments(self, query, arguments, has_kwargs, sanitize):
|
||||
query_defns = {sanitize(p["name"]): p
|
||||
for p in self.parameters
|
||||
if p["in"] == "query"}
|
||||
default_query_params = {k: v['default']
|
||||
for k, v in query_defns.items()
|
||||
if 'default' in v}
|
||||
query_arguments = deepcopy(default_query_params)
|
||||
query_arguments.update(query)
|
||||
return self._query_args_helper(query_defns, query_arguments,
|
||||
arguments, has_kwargs, sanitize)
|
||||
|
||||
def _get_body_argument(self, body, arguments, has_kwargs, sanitize):
|
||||
kwargs = {}
|
||||
body_parameters = [p for p in self.parameters if p['in'] == 'body'] or [{}]
|
||||
default_body = body_parameters[0].get('schema', {}).get('default')
|
||||
body_name = sanitize(body_parameters[0].get('name'))
|
||||
|
||||
body = body or default_body
|
||||
|
||||
form_defns = {sanitize(p['name']): p
|
||||
for p in self.parameters
|
||||
if p['in'] == 'formData'}
|
||||
|
||||
default_form_params = {k: v['default']
|
||||
for k, v in form_defns.items()
|
||||
if 'default' in v}
|
||||
|
||||
# Add body parameters
|
||||
if body_name:
|
||||
if not has_kwargs and body_name not in arguments:
|
||||
logger.debug("Body parameter '%s' not in function arguments", body_name)
|
||||
else:
|
||||
logger.debug("Body parameter '%s' in function arguments", body_name)
|
||||
kwargs[body_name] = body
|
||||
|
||||
# Add formData parameters
|
||||
form_arguments = deepcopy(default_form_params)
|
||||
if form_defns and body:
|
||||
form_arguments.update(body)
|
||||
for key, value in form_arguments.items():
|
||||
if not has_kwargs and key not in arguments:
|
||||
logger.debug("FormData parameter '%s' not in function arguments", key)
|
||||
else:
|
||||
logger.debug("FormData parameter '%s' in function arguments", key)
|
||||
try:
|
||||
form_defn = form_defns[key]
|
||||
except KeyError: # pragma: no cover
|
||||
logger.error("Function argument '{}' not defined in specification".format(key))
|
||||
else:
|
||||
kwargs[key] = self._get_val_from_param(value, form_defn)
|
||||
return kwargs
|
||||
|
||||
def _get_val_from_param(self, value, query_defn):
|
||||
if is_nullable(query_defn) and is_null(value):
|
||||
return None
|
||||
|
||||
query_schema = query_defn
|
||||
|
||||
if query_schema["type"] == "array":
|
||||
return [make_type(part, query_defn["items"]["type"]) for part in value]
|
||||
else:
|
||||
return make_type(value, query_defn["type"])
|
||||
@@ -1,13 +1,34 @@
|
||||
import logging
|
||||
import pathlib
|
||||
from typing import Optional # NOQA
|
||||
|
||||
try:
|
||||
from swagger_ui_bundle import (swagger_ui_2_path,
|
||||
swagger_ui_3_path)
|
||||
except ImportError:
|
||||
swagger_ui_2_path = swagger_ui_3_path = None
|
||||
|
||||
MODULE_PATH = pathlib.Path(__file__).absolute().parent
|
||||
INTERNAL_CONSOLE_UI_PATH = MODULE_PATH / 'vendor' / 'swagger-ui'
|
||||
NO_UI_MSG = """The swagger_ui directory could not be found.
|
||||
Please install connexion with extra install: pip install connexion[swagger-ui]
|
||||
or provide the path to your local installation by passing swagger_path=<your path>
|
||||
"""
|
||||
|
||||
logger = logging.getLogger("connexion.options")
|
||||
|
||||
|
||||
class ConnexionOptions(object):
|
||||
def __init__(self, options=None):
|
||||
|
||||
def __init__(self, options=None, oas_version=(2,)):
|
||||
self._options = {}
|
||||
self.oas_version = oas_version
|
||||
if self.oas_version >= (3, 0, 0):
|
||||
self.openapi_spec_name = '/openapi.json'
|
||||
self.swagger_ui_local_path = swagger_ui_3_path
|
||||
else:
|
||||
self.openapi_spec_name = '/swagger.json'
|
||||
self.swagger_ui_local_path = swagger_ui_2_path
|
||||
|
||||
if options:
|
||||
self._options.update(filter_values(options))
|
||||
|
||||
@@ -22,7 +43,7 @@ class ConnexionOptions(object):
|
||||
|
||||
options = dict(self._options)
|
||||
options.update(filter_values(new_values))
|
||||
return ConnexionOptions(options)
|
||||
return ConnexionOptions(options, self.oas_version)
|
||||
|
||||
def as_dict(self):
|
||||
return self._options
|
||||
@@ -32,26 +53,43 @@ class ConnexionOptions(object):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Whether to make available the OpenAPI Specification under
|
||||
`openapi_console_ui_path`/swagger.json path.
|
||||
`openapi_spec_path`.
|
||||
|
||||
Default: True
|
||||
"""
|
||||
# NOTE: Under OpenAPI v3 this should change to "/openapi.json"
|
||||
return self._options.get('swagger_json', True)
|
||||
deprecated_option = self._options.get('swagger_json', True)
|
||||
serve_spec = self._options.get('serve_spec', deprecated_option)
|
||||
if 'swagger_json' in self._options:
|
||||
deprecation_warning = ("The 'swagger_json' option is deprecated. "
|
||||
"Please use 'serve_spec' instead")
|
||||
logger.warning(deprecation_warning)
|
||||
return serve_spec
|
||||
|
||||
@property
|
||||
def openapi_console_ui_available(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Whether to make the OpenAPI Console UI available under the path
|
||||
defined in `openapi_console_ui_path` option. Note that if enabled,
|
||||
this overrides the `openapi_spec_available` option since the specification
|
||||
is required to be available via a HTTP endpoint to display the console UI.
|
||||
defined in `openapi_console_ui_path` option.
|
||||
|
||||
Default: True
|
||||
"""
|
||||
if (self._options.get('swagger_ui', True) and
|
||||
self.openapi_console_ui_from_dir is None):
|
||||
logger.warning(NO_UI_MSG)
|
||||
return False
|
||||
return self._options.get('swagger_ui', True)
|
||||
|
||||
@property
|
||||
def openapi_spec_path(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Path to mount the OpenAPI Console UI and make it accessible via a browser.
|
||||
|
||||
Default: /openapi.json for openapi3, otherwise /swagger.json
|
||||
"""
|
||||
return self._options.get('openapi_spec_path', self.openapi_spec_name)
|
||||
|
||||
@property
|
||||
def openapi_console_ui_path(self):
|
||||
# type: () -> str
|
||||
@@ -71,11 +109,11 @@ class ConnexionOptions(object):
|
||||
|
||||
Default: Connexion's vendored version of the OpenAPI Console UI.
|
||||
"""
|
||||
return self._options.get('swagger_path', INTERNAL_CONSOLE_UI_PATH)
|
||||
return self._options.get('swagger_path', self.swagger_ui_local_path)
|
||||
|
||||
@property
|
||||
def uri_parser_class(self):
|
||||
# type: () -> str
|
||||
# type: () -> AbstractURIParser
|
||||
"""
|
||||
The class to use for parsing URIs into path and query parameters.
|
||||
Default: None
|
||||
|
||||
@@ -34,7 +34,7 @@ class Resolver(object):
|
||||
"""
|
||||
Default operation resolver
|
||||
|
||||
:type operation: connexion.operation.Operation
|
||||
:type operation: connexion.operations.AbstractOperation
|
||||
"""
|
||||
operation_id = self.resolve_operation_id(operation)
|
||||
return Resolution(self.resolve_function_from_operation_id(operation_id), operation_id)
|
||||
@@ -43,14 +43,13 @@ class Resolver(object):
|
||||
"""
|
||||
Default operationId resolver
|
||||
|
||||
:type operation: connexion.operation.Operation
|
||||
:type operation: connexion.operations.AbstractOperation
|
||||
"""
|
||||
spec = operation.operation
|
||||
operation_id = spec.get('operationId', '')
|
||||
x_router_controller = spec.get('x-swagger-router-controller')
|
||||
if x_router_controller is None:
|
||||
operation_id = operation.operation_id
|
||||
router_controller = operation.router_controller
|
||||
if operation.router_controller is None:
|
||||
return operation_id
|
||||
return '{}.{}'.format(x_router_controller, operation_id)
|
||||
return '{}.{}'.format(router_controller, operation_id)
|
||||
|
||||
def resolve_function_from_operation_id(self, operation_id):
|
||||
"""
|
||||
@@ -85,9 +84,9 @@ class RestyResolver(Resolver):
|
||||
"""
|
||||
Resolves the operationId using REST semantics unless explicitly configured in the spec
|
||||
|
||||
:type operation: connexion.operation.Operation
|
||||
:type operation: connexion.operations.AbstractOperation
|
||||
"""
|
||||
if operation.operation.get('operationId'):
|
||||
if operation.operation_id:
|
||||
return Resolver.resolve_operation_id(self, operation)
|
||||
|
||||
return self.resolve_operation_id_using_rest_semantics(operation)
|
||||
@@ -96,14 +95,14 @@ class RestyResolver(Resolver):
|
||||
"""
|
||||
Resolves the operationId using REST semantics
|
||||
|
||||
:type operation: connexion.operation.Operation
|
||||
:type operation: connexion.operations.AbstractOperation
|
||||
"""
|
||||
path_match = re.search(
|
||||
'^/?(?P<resource_name>([\w\-](?<!/))*)(?P<trailing_slash>/*)(?P<extended_path>.*)$', operation.path
|
||||
r'^/?(?P<resource_name>([\w\-](?<!/))*)(?P<trailing_slash>/*)(?P<extended_path>.*)$', operation.path
|
||||
)
|
||||
|
||||
def get_controller_name():
|
||||
x_router_controller = operation.operation.get('x-swagger-router-controller')
|
||||
x_router_controller = operation.router_controller
|
||||
|
||||
name = self.default_module_name
|
||||
resource_name = path_match.group('resource_name')
|
||||
|
||||
@@ -3,6 +3,48 @@ import importlib
|
||||
|
||||
import six
|
||||
|
||||
# Python 2/3 compatibility:
|
||||
try:
|
||||
py_string = unicode
|
||||
except NameError: # pragma: no cover
|
||||
py_string = str # pragma: no cover
|
||||
|
||||
|
||||
def boolean(s):
|
||||
'''
|
||||
Convert JSON/Swagger boolean value to Python, raise ValueError otherwise
|
||||
|
||||
>>> boolean('true')
|
||||
True
|
||||
|
||||
>>> boolean('false')
|
||||
False
|
||||
'''
|
||||
if isinstance(s, bool):
|
||||
return s
|
||||
elif not hasattr(s, 'lower'):
|
||||
raise ValueError('Invalid boolean value')
|
||||
elif s.lower() == 'true':
|
||||
return True
|
||||
elif s.lower() == 'false':
|
||||
return False
|
||||
else:
|
||||
raise ValueError('Invalid boolean value')
|
||||
|
||||
|
||||
# https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#data-types
|
||||
TYPE_MAP = {'integer': int,
|
||||
'number': float,
|
||||
'string': py_string,
|
||||
'boolean': boolean,
|
||||
'array': list,
|
||||
'object': dict} # map of swagger types to python types
|
||||
|
||||
|
||||
def make_type(value, _type):
|
||||
type_func = TYPE_MAP[_type] # convert value to right type
|
||||
return type_func(value)
|
||||
|
||||
|
||||
def deep_getattr(obj, attr):
|
||||
"""
|
||||
@@ -13,6 +55,15 @@ def deep_getattr(obj, attr):
|
||||
return functools.reduce(getattr, attr.split('.'), obj)
|
||||
|
||||
|
||||
def deep_get(obj, keys):
|
||||
"""
|
||||
Recurses through a nested object get a leaf value.
|
||||
"""
|
||||
if not keys:
|
||||
return obj
|
||||
return deep_get(obj[keys[0]], keys[1:])
|
||||
|
||||
|
||||
def get_function_from_name(function_name):
|
||||
"""
|
||||
Tries to get function by fully qualified name (e.g. "mymodule.myobj.myfunc")
|
||||
@@ -85,30 +136,11 @@ def all_json(mimetypes):
|
||||
return all(is_json_mimetype(mimetype) for mimetype in mimetypes)
|
||||
|
||||
|
||||
def boolean(s):
|
||||
'''
|
||||
Convert JSON/Swagger boolean value to Python, raise ValueError otherwise
|
||||
|
||||
>>> boolean('true')
|
||||
True
|
||||
|
||||
>>> boolean('false')
|
||||
False
|
||||
'''
|
||||
if isinstance(s, bool):
|
||||
return s
|
||||
elif not hasattr(s, 'lower'):
|
||||
raise ValueError('Invalid boolean value')
|
||||
elif s.lower() == 'true':
|
||||
return True
|
||||
elif s.lower() == 'false':
|
||||
return False
|
||||
else:
|
||||
raise ValueError('Invalid boolean value')
|
||||
|
||||
|
||||
def is_nullable(param_def):
|
||||
return param_def.get('x-nullable', False)
|
||||
return (
|
||||
param_def.get('schema', param_def).get('nullable', False) or
|
||||
param_def.get('x-nullable', False) # swagger2
|
||||
)
|
||||
|
||||
|
||||
def is_null(value):
|
||||
@@ -140,7 +172,7 @@ class Jsonifier(object):
|
||||
|
||||
try:
|
||||
return self.json.loads(data)
|
||||
except Exception as error:
|
||||
except Exception:
|
||||
if isinstance(data, six.string_types):
|
||||
return data
|
||||
|
||||
|
||||
1367
connexion/vendor/swagger-ui/css/print.css
vendored
125
connexion/vendor/swagger-ui/css/reset.css
vendored
@@ -1,125 +0,0 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
b,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
1494
connexion/vendor/swagger-ui/css/screen.css
vendored
250
connexion/vendor/swagger-ui/css/style.css
vendored
@@ -1,250 +0,0 @@
|
||||
.swagger-section #header a#logo {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
background: transparent url(../images/logo.png) no-repeat left center;
|
||||
padding: 20px 0 20px 40px;
|
||||
}
|
||||
#text-head {
|
||||
font-size: 80px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: #ffffff;
|
||||
float: right;
|
||||
margin-right: 20%;
|
||||
}
|
||||
.navbar-fixed-top .navbar-nav {
|
||||
height: auto;
|
||||
}
|
||||
.navbar-fixed-top .navbar-brand {
|
||||
height: auto;
|
||||
}
|
||||
.navbar-header {
|
||||
height: auto;
|
||||
}
|
||||
.navbar-inverse {
|
||||
background-color: #000;
|
||||
border-color: #000;
|
||||
}
|
||||
#navbar-brand {
|
||||
margin-left: 20%;
|
||||
}
|
||||
.navtext {
|
||||
font-size: 10px;
|
||||
}
|
||||
.h1,
|
||||
h1 {
|
||||
font-size: 60px;
|
||||
}
|
||||
.navbar-default .navbar-header .navbar-brand {
|
||||
color: #a2dfee;
|
||||
}
|
||||
/* tag titles */
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a {
|
||||
color: #393939;
|
||||
font-family: 'Arvo', serif;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover {
|
||||
color: black;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 {
|
||||
color: #525252;
|
||||
padding-left: 0px;
|
||||
display: block;
|
||||
clear: none;
|
||||
float: left;
|
||||
font-family: 'Arvo', serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
.navbar-default .navbar-collapse,
|
||||
.navbar-default .navbar-form {
|
||||
border-color: #0A0A0A;
|
||||
}
|
||||
.container1 {
|
||||
width: 1500px;
|
||||
margin: auto;
|
||||
margin-top: 0;
|
||||
background-image: url('../images/shield.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: -40px -20px;
|
||||
margin-bottom: 210px;
|
||||
}
|
||||
.container-inner {
|
||||
width: 1200px;
|
||||
margin: auto;
|
||||
background-color: rgba(223, 227, 228, 0.75);
|
||||
padding-bottom: 40px;
|
||||
padding-top: 40px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
.header-content {
|
||||
padding: 0;
|
||||
width: 1000px;
|
||||
}
|
||||
.title1 {
|
||||
font-size: 80px;
|
||||
font-family: 'Vollkorn', serif;
|
||||
color: #404040;
|
||||
text-align: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
#icon {
|
||||
margin-top: -18px;
|
||||
}
|
||||
.subtext {
|
||||
font-size: 25px;
|
||||
font-style: italic;
|
||||
color: #08b;
|
||||
text-align: right;
|
||||
padding-right: 250px;
|
||||
}
|
||||
.bg-primary {
|
||||
background-color: #00468b;
|
||||
}
|
||||
.navbar-default .nav > li > a,
|
||||
.navbar-default .nav > li > a:focus {
|
||||
color: #08b;
|
||||
}
|
||||
.navbar-default .nav > li > a,
|
||||
.navbar-default .nav > li > a:hover {
|
||||
color: #08b;
|
||||
}
|
||||
.navbar-default .nav > li > a,
|
||||
.navbar-default .nav > li > a:focus:hover {
|
||||
color: #08b;
|
||||
}
|
||||
.text-faded {
|
||||
font-size: 25px;
|
||||
font-family: 'Vollkorn', serif;
|
||||
}
|
||||
.section-heading {
|
||||
font-family: 'Vollkorn', serif;
|
||||
font-size: 45px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
hr {
|
||||
border-color: #00468b;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.description {
|
||||
margin-top: 20px;
|
||||
padding-bottom: 200px;
|
||||
}
|
||||
.description li {
|
||||
font-family: 'Vollkorn', serif;
|
||||
font-size: 25px;
|
||||
color: #525252;
|
||||
margin-left: 28%;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.gap {
|
||||
margin-top: 200px;
|
||||
}
|
||||
.troubleshootingtext {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
padding-left: 30%;
|
||||
}
|
||||
.troubleshootingtext li {
|
||||
list-style-type: circle;
|
||||
font-size: 25px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
.block.response_body.json:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.backdrop {
|
||||
color: blue;
|
||||
}
|
||||
#myModal {
|
||||
height: 100%;
|
||||
}
|
||||
.modal-backdrop {
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
}
|
||||
.curl {
|
||||
padding: 10px;
|
||||
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
||||
font-size: 0.9em;
|
||||
max-height: 400px;
|
||||
margin-top: 5px;
|
||||
overflow-y: auto;
|
||||
background-color: #fcf6db;
|
||||
border: 1px solid #e5e0c6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.curl_title {
|
||||
font-size: 1.1em;
|
||||
margin: 0;
|
||||
padding: 15px 0 5px;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.footer {
|
||||
display: none;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap h2 {
|
||||
padding: 0;
|
||||
}
|
||||
h2 {
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.markdown p {
|
||||
font-size: 15px;
|
||||
font-family: 'Arvo', serif;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .code {
|
||||
font-size: 15px;
|
||||
font-family: 'Arvo', serif;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap b {
|
||||
font-family: 'Arvo', serif;
|
||||
}
|
||||
#signin:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dropdown-menu {
|
||||
padding: 15px;
|
||||
}
|
||||
.navbar-right .dropdown-menu {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
#signinbutton {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #08b;
|
||||
}
|
||||
.navbar-default .nav > li .details {
|
||||
color: #000000;
|
||||
text-transform: none;
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-style: italic;
|
||||
line-height: 20px;
|
||||
top: -2px;
|
||||
}
|
||||
.navbar-default .nav > li .details:hover {
|
||||
color: black;
|
||||
}
|
||||
#signout {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #08b;
|
||||
}
|
||||
14
connexion/vendor/swagger-ui/css/typography.css
vendored
@@ -1,14 +0,0 @@
|
||||
/* Google Font's Droid Sans */
|
||||
@font-face {
|
||||
font-family: 'Droid Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf'), format('truetype');
|
||||
}
|
||||
/* Google Font's Droid Sans Bold */
|
||||
@font-face {
|
||||
font-family: 'Droid Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf'), format('truetype');
|
||||
}
|
||||
BIN
connexion/vendor/swagger-ui/fonts/DroidSans-Bold.ttf
vendored
BIN
connexion/vendor/swagger-ui/fonts/DroidSans.ttf
vendored
BIN
connexion/vendor/swagger-ui/images/collapse.gif
vendored
|
Before Width: | Height: | Size: 69 B |
BIN
connexion/vendor/swagger-ui/images/expand.gif
vendored
|
Before Width: | Height: | Size: 73 B |
|
Before Width: | Height: | Size: 5.0 KiB |
BIN
connexion/vendor/swagger-ui/images/favicon-16x16.png
vendored
|
Before Width: | Height: | Size: 445 B |
BIN
connexion/vendor/swagger-ui/images/favicon-32x32.png
vendored
|
Before Width: | Height: | Size: 1.1 KiB |
BIN
connexion/vendor/swagger-ui/images/favicon.ico
vendored
|
Before Width: | Height: | Size: 5.3 KiB |
BIN
connexion/vendor/swagger-ui/images/logo_small.png
vendored
|
Before Width: | Height: | Size: 455 B |
BIN
connexion/vendor/swagger-ui/images/pet_store_api.png
vendored
|
Before Width: | Height: | Size: 631 B |
BIN
connexion/vendor/swagger-ui/images/throbber.gif
vendored
|
Before Width: | Height: | Size: 9.0 KiB |
BIN
connexion/vendor/swagger-ui/images/wordnik_api.png
vendored
|
Before Width: | Height: | Size: 670 B |
108
connexion/vendor/swagger-ui/index.html
vendored
@@ -1,108 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" />
|
||||
<link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/print.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
|
||||
<script src='lib/object-assign-pollyfill.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
|
||||
<script src='lib/handlebars-4.0.5.js' type='text/javascript'></script>
|
||||
<script src='lib/lodash.min.js' type='text/javascript'></script>
|
||||
<script src='lib/backbone-min.js' type='text/javascript'></script>
|
||||
<script src='swagger-ui.js' type='text/javascript'></script>
|
||||
<script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
|
||||
<script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>
|
||||
<script src='lib/jsoneditor.min.js' type='text/javascript'></script>
|
||||
<script src='lib/marked.js' type='text/javascript'></script>
|
||||
<script src='lib/swagger-oauth.js' type='text/javascript'></script>
|
||||
|
||||
<!-- Some basic translations -->
|
||||
<!-- <script src='lang/translator.js' type='text/javascript'></script> -->
|
||||
<!-- <script src='lang/ru.js' type='text/javascript'></script> -->
|
||||
<!-- <script src='lang/en.js' type='text/javascript'></script> -->
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var url = window.location.search.match(/url=([^&]+)/);
|
||||
if (url && url.length > 1) {
|
||||
url = decodeURIComponent(url[1]);
|
||||
} else {
|
||||
url = "{{ api_url }}/swagger.json";
|
||||
}
|
||||
|
||||
hljs.configure({
|
||||
highlightSizeThreshold: 5000
|
||||
});
|
||||
|
||||
// Pre load translate...
|
||||
if(window.SwaggerTranslator) {
|
||||
window.SwaggerTranslator.translate();
|
||||
}
|
||||
window.swaggerUi = new SwaggerUi({
|
||||
url: url,
|
||||
// https://github.com/zalando/connexion/issues/110
|
||||
validatorUrl: null,
|
||||
dom_id: "swagger-ui-container",
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
|
||||
onComplete: function(swaggerApi, swaggerUi){
|
||||
if(typeof initOAuth == "function") {
|
||||
initOAuth({
|
||||
clientId: "your-client-id",
|
||||
clientSecret: "your-client-secret-if-required",
|
||||
realm: "your-realms",
|
||||
appName: "your-app-name",
|
||||
scopeSeparator: " ",
|
||||
additionalQueryStringParams: {}
|
||||
});
|
||||
}
|
||||
|
||||
if(window.SwaggerTranslator) {
|
||||
window.SwaggerTranslator.translate();
|
||||
}
|
||||
},
|
||||
onFailure: function(data) {
|
||||
log("Unable to Load SwaggerUI");
|
||||
},
|
||||
docExpansion: "none",
|
||||
jsonEditor: false,
|
||||
defaultModelRendering: 'schema',
|
||||
showRequestHeaders: false
|
||||
});
|
||||
|
||||
window.swaggerUi.load();
|
||||
|
||||
function log() {
|
||||
if ('console' in window) {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="swagger-section">
|
||||
<div id='header'>
|
||||
<div class="swagger-ui-wrap">
|
||||
<a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="images/logo_small.png" /><span class="logo__title">swagger</span></a>
|
||||
<form id='api_selector'>
|
||||
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
|
||||
<div id='auth_container'></div>
|
||||
<div class='input'><a id="explore" class="header__btn" href="#" data-sw-translate>Explore</a></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate> </div>
|
||||
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
|
||||
</body>
|
||||
</html>
|
||||
20
connexion/vendor/swagger-ui/index.html.patch
vendored
@@ -1,20 +0,0 @@
|
||||
--- index.html 2016-09-26 21:25:42.000000000 +0200
|
||||
+++ index.html.patched 2016-10-04 14:18:52.110478183 +0200
|
||||
@@ -37,7 +37,7 @@
|
||||
if (url && url.length > 1) {
|
||||
url = decodeURIComponent(url[1]);
|
||||
} else {
|
||||
- url = "http://petstore.swagger.io/v2/swagger.json";
|
||||
+ url = "{{ api_url }}/swagger.json";
|
||||
}
|
||||
|
||||
hljs.configure({
|
||||
@@ -50,6 +50,8 @@
|
||||
}
|
||||
window.swaggerUi = new SwaggerUi({
|
||||
url: url,
|
||||
+ // https://github.com/zalando/connexion/issues/110
|
||||
+ validatorUrl: null,
|
||||
dom_id: "swagger-ui-container",
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
|
||||
onComplete: function(swaggerApi, swaggerUi){
|
||||
53
connexion/vendor/swagger-ui/lang/ca.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Advertència: Obsolet",
|
||||
"Implementation Notes":"Notes d'implementació",
|
||||
"Response Class":"Classe de la Resposta",
|
||||
"Status":"Estatus",
|
||||
"Parameters":"Paràmetres",
|
||||
"Parameter":"Paràmetre",
|
||||
"Value":"Valor",
|
||||
"Description":"Descripció",
|
||||
"Parameter Type":"Tipus del Paràmetre",
|
||||
"Data Type":"Tipus de la Dada",
|
||||
"Response Messages":"Missatges de la Resposta",
|
||||
"HTTP Status Code":"Codi d'Estatus HTTP",
|
||||
"Reason":"Raó",
|
||||
"Response Model":"Model de la Resposta",
|
||||
"Request URL":"URL de la Sol·licitud",
|
||||
"Response Body":"Cos de la Resposta",
|
||||
"Response Code":"Codi de la Resposta",
|
||||
"Response Headers":"Capçaleres de la Resposta",
|
||||
"Hide Response":"Amagar Resposta",
|
||||
"Try it out!":"Prova-ho!",
|
||||
"Show/Hide":"Mostrar/Amagar",
|
||||
"List Operations":"Llista Operacions",
|
||||
"Expand Operations":"Expandir Operacions",
|
||||
"Raw":"Cru",
|
||||
"can't parse JSON. Raw result":"no puc analitzar el JSON. Resultat cru",
|
||||
"Example Value":"Valor d'Exemple",
|
||||
"Model Schema":"Esquema del Model",
|
||||
"Model":"Model",
|
||||
"apply":"aplicar",
|
||||
"Username":"Nom d'usuari",
|
||||
"Password":"Contrasenya",
|
||||
"Terms of service":"Termes del servei",
|
||||
"Created by":"Creat per",
|
||||
"See more at":"Veure més en",
|
||||
"Contact the developer":"Contactar amb el desenvolupador",
|
||||
"api version":"versió de la api",
|
||||
"Response Content Type":"Tipus de Contingut de la Resposta",
|
||||
"fetching resource":"recollint recurs",
|
||||
"fetching resource list":"recollins llista de recursos",
|
||||
"Explore":"Explorant",
|
||||
"Show Swagger Petstore Example Apis":"Mostrar API d'Exemple Swagger Petstore",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"No es pot llegir del servidor. Potser no teniu la configuració de control d'accés apropiada.",
|
||||
"Please specify the protocol for":"Si us plau, especifiqueu el protocol per a",
|
||||
"Can't read swagger JSON from":"No es pot llegir el JSON de swagger des de",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Finalitzada la càrrega del recurs informatiu. Renderitzant Swagger UI",
|
||||
"Unable to read api":"No es pot llegir l'api",
|
||||
"from path":"des de la ruta",
|
||||
"server returned":"el servidor ha retornat"
|
||||
});
|
||||
56
connexion/vendor/swagger-ui/lang/en.js
vendored
@@ -1,56 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Warning: Deprecated",
|
||||
"Implementation Notes":"Implementation Notes",
|
||||
"Response Class":"Response Class",
|
||||
"Status":"Status",
|
||||
"Parameters":"Parameters",
|
||||
"Parameter":"Parameter",
|
||||
"Value":"Value",
|
||||
"Description":"Description",
|
||||
"Parameter Type":"Parameter Type",
|
||||
"Data Type":"Data Type",
|
||||
"Response Messages":"Response Messages",
|
||||
"HTTP Status Code":"HTTP Status Code",
|
||||
"Reason":"Reason",
|
||||
"Response Model":"Response Model",
|
||||
"Request URL":"Request URL",
|
||||
"Response Body":"Response Body",
|
||||
"Response Code":"Response Code",
|
||||
"Response Headers":"Response Headers",
|
||||
"Hide Response":"Hide Response",
|
||||
"Headers":"Headers",
|
||||
"Try it out!":"Try it out!",
|
||||
"Show/Hide":"Show/Hide",
|
||||
"List Operations":"List Operations",
|
||||
"Expand Operations":"Expand Operations",
|
||||
"Raw":"Raw",
|
||||
"can't parse JSON. Raw result":"can't parse JSON. Raw result",
|
||||
"Example Value":"Example Value",
|
||||
"Model Schema":"Model Schema",
|
||||
"Model":"Model",
|
||||
"Click to set as parameter value":"Click to set as parameter value",
|
||||
"apply":"apply",
|
||||
"Username":"Username",
|
||||
"Password":"Password",
|
||||
"Terms of service":"Terms of service",
|
||||
"Created by":"Created by",
|
||||
"See more at":"See more at",
|
||||
"Contact the developer":"Contact the developer",
|
||||
"api version":"api version",
|
||||
"Response Content Type":"Response Content Type",
|
||||
"Parameter content type:":"Parameter content type:",
|
||||
"fetching resource":"fetching resource",
|
||||
"fetching resource list":"fetching resource list",
|
||||
"Explore":"Explore",
|
||||
"Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Can't read from server. It may not have the appropriate access-control-origin settings.",
|
||||
"Please specify the protocol for":"Please specify the protocol for",
|
||||
"Can't read swagger JSON from":"Can't read swagger JSON from",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Finished Loading Resource Information. Rendering Swagger UI",
|
||||
"Unable to read api":"Unable to read api",
|
||||
"from path":"from path",
|
||||
"server returned":"server returned"
|
||||
});
|
||||
53
connexion/vendor/swagger-ui/lang/es.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Advertencia: Obsoleto",
|
||||
"Implementation Notes":"Notas de implementación",
|
||||
"Response Class":"Clase de la Respuesta",
|
||||
"Status":"Status",
|
||||
"Parameters":"Parámetros",
|
||||
"Parameter":"Parámetro",
|
||||
"Value":"Valor",
|
||||
"Description":"Descripción",
|
||||
"Parameter Type":"Tipo del Parámetro",
|
||||
"Data Type":"Tipo del Dato",
|
||||
"Response Messages":"Mensajes de la Respuesta",
|
||||
"HTTP Status Code":"Código de Status HTTP",
|
||||
"Reason":"Razón",
|
||||
"Response Model":"Modelo de la Respuesta",
|
||||
"Request URL":"URL de la Solicitud",
|
||||
"Response Body":"Cuerpo de la Respuesta",
|
||||
"Response Code":"Código de la Respuesta",
|
||||
"Response Headers":"Encabezados de la Respuesta",
|
||||
"Hide Response":"Ocultar Respuesta",
|
||||
"Try it out!":"Pruébalo!",
|
||||
"Show/Hide":"Mostrar/Ocultar",
|
||||
"List Operations":"Listar Operaciones",
|
||||
"Expand Operations":"Expandir Operaciones",
|
||||
"Raw":"Crudo",
|
||||
"can't parse JSON. Raw result":"no puede parsear el JSON. Resultado crudo",
|
||||
"Example Value":"Valor de Ejemplo",
|
||||
"Model Schema":"Esquema del Modelo",
|
||||
"Model":"Modelo",
|
||||
"apply":"aplicar",
|
||||
"Username":"Nombre de usuario",
|
||||
"Password":"Contraseña",
|
||||
"Terms of service":"Términos de Servicio",
|
||||
"Created by":"Creado por",
|
||||
"See more at":"Ver más en",
|
||||
"Contact the developer":"Contactar al desarrollador",
|
||||
"api version":"versión de la api",
|
||||
"Response Content Type":"Tipo de Contenido (Content Type) de la Respuesta",
|
||||
"fetching resource":"buscando recurso",
|
||||
"fetching resource list":"buscando lista del recurso",
|
||||
"Explore":"Explorar",
|
||||
"Show Swagger Petstore Example Apis":"Mostrar Api Ejemplo de Swagger Petstore",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.",
|
||||
"Please specify the protocol for":"Por favor, especificar el protocola para",
|
||||
"Can't read swagger JSON from":"No se puede leer el JSON de swagger desde",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Finalizada la carga del recurso de Información. Mostrando Swagger UI",
|
||||
"Unable to read api":"No se puede leer la api",
|
||||
"from path":"desde ruta",
|
||||
"server returned":"el servidor retornó"
|
||||
});
|
||||
54
connexion/vendor/swagger-ui/lang/fr.js
vendored
@@ -1,54 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Avertissement : Obsolète",
|
||||
"Implementation Notes":"Notes d'implémentation",
|
||||
"Response Class":"Classe de la réponse",
|
||||
"Status":"Statut",
|
||||
"Parameters":"Paramètres",
|
||||
"Parameter":"Paramètre",
|
||||
"Value":"Valeur",
|
||||
"Description":"Description",
|
||||
"Parameter Type":"Type du paramètre",
|
||||
"Data Type":"Type de données",
|
||||
"Response Messages":"Messages de la réponse",
|
||||
"HTTP Status Code":"Code de statut HTTP",
|
||||
"Reason":"Raison",
|
||||
"Response Model":"Modèle de réponse",
|
||||
"Request URL":"URL appelée",
|
||||
"Response Body":"Corps de la réponse",
|
||||
"Response Code":"Code de la réponse",
|
||||
"Response Headers":"En-têtes de la réponse",
|
||||
"Hide Response":"Cacher la réponse",
|
||||
"Headers":"En-têtes",
|
||||
"Try it out!":"Testez !",
|
||||
"Show/Hide":"Afficher/Masquer",
|
||||
"List Operations":"Liste des opérations",
|
||||
"Expand Operations":"Développer les opérations",
|
||||
"Raw":"Brut",
|
||||
"can't parse JSON. Raw result":"impossible de décoder le JSON. Résultat brut",
|
||||
"Example Value":"Exemple la valeur",
|
||||
"Model Schema":"Définition du modèle",
|
||||
"Model":"Modèle",
|
||||
"apply":"appliquer",
|
||||
"Username":"Nom d'utilisateur",
|
||||
"Password":"Mot de passe",
|
||||
"Terms of service":"Conditions de service",
|
||||
"Created by":"Créé par",
|
||||
"See more at":"Voir plus sur",
|
||||
"Contact the developer":"Contacter le développeur",
|
||||
"api version":"version de l'api",
|
||||
"Response Content Type":"Content Type de la réponse",
|
||||
"fetching resource":"récupération de la ressource",
|
||||
"fetching resource list":"récupération de la liste de ressources",
|
||||
"Explore":"Explorer",
|
||||
"Show Swagger Petstore Example Apis":"Montrer les Apis de l'exemple Petstore de Swagger",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.",
|
||||
"Please specify the protocol for":"Veuillez spécifier un protocole pour",
|
||||
"Can't read swagger JSON from":"Impossible de lire le JSON swagger à partir de",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Chargement des informations terminé. Affichage de Swagger UI",
|
||||
"Unable to read api":"Impossible de lire l'api",
|
||||
"from path":"à partir du chemin",
|
||||
"server returned":"réponse du serveur"
|
||||
});
|
||||
56
connexion/vendor/swagger-ui/lang/geo.js
vendored
@@ -1,56 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"ყურადღება: აღარ გამოიყენება",
|
||||
"Implementation Notes":"იმპლემენტაციის აღწერა",
|
||||
"Response Class":"რესპონს კლასი",
|
||||
"Status":"სტატუსი",
|
||||
"Parameters":"პარამეტრები",
|
||||
"Parameter":"პარამეტრი",
|
||||
"Value":"მნიშვნელობა",
|
||||
"Description":"აღწერა",
|
||||
"Parameter Type":"პარამეტრის ტიპი",
|
||||
"Data Type":"მონაცემის ტიპი",
|
||||
"Response Messages":"პასუხი",
|
||||
"HTTP Status Code":"HTTP სტატუსი",
|
||||
"Reason":"მიზეზი",
|
||||
"Response Model":"რესპონს მოდელი",
|
||||
"Request URL":"მოთხოვნის URL",
|
||||
"Response Body":"პასუხის სხეული",
|
||||
"Response Code":"პასუხის კოდი",
|
||||
"Response Headers":"პასუხის ჰედერები",
|
||||
"Hide Response":"დამალე პასუხი",
|
||||
"Headers":"ჰედერები",
|
||||
"Try it out!":"ცადე !",
|
||||
"Show/Hide":"გამოჩენა/დამალვა",
|
||||
"List Operations":"ოპერაციების სია",
|
||||
"Expand Operations":"ოპერაციები ვრცლად",
|
||||
"Raw":"ნედლი",
|
||||
"can't parse JSON. Raw result":"JSON-ის დამუშავება ვერ მოხერხდა. ნედლი პასუხი",
|
||||
"Example Value":"მაგალითი",
|
||||
"Model Schema":"მოდელის სტრუქტურა",
|
||||
"Model":"მოდელი",
|
||||
"Click to set as parameter value":"პარამეტრისთვის მნიშვნელობის მისანიჭებლად, დააკლიკე",
|
||||
"apply":"გამოყენება",
|
||||
"Username":"მოხმარებელი",
|
||||
"Password":"პაროლი",
|
||||
"Terms of service":"მომსახურების პირობები",
|
||||
"Created by":"შექმნა",
|
||||
"See more at":"ნახე ვრცლად",
|
||||
"Contact the developer":"დაუკავშირდი დეველოპერს",
|
||||
"api version":"api ვერსია",
|
||||
"Response Content Type":"პასუხის კონტენტის ტიპი",
|
||||
"Parameter content type:":"პარამეტრის კონტენტის ტიპი:",
|
||||
"fetching resource":"რესურსების მიღება",
|
||||
"fetching resource list":"რესურსების სიის მიღება",
|
||||
"Explore":"ნახვა",
|
||||
"Show Swagger Petstore Example Apis":"ნახე Swagger Petstore სამაგალითო Api",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"სერვერთან დაკავშირება ვერ ხერხდება. შეამოწმეთ access-control-origin.",
|
||||
"Please specify the protocol for":"მიუთითეთ პროტოკოლი",
|
||||
"Can't read swagger JSON from":"swagger JSON წაკითხვა ვერ მოხერხდა",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"რესურსების ჩატვირთვა სრულდება. Swagger UI რენდერდება",
|
||||
"Unable to read api":"api წაკითხვა ვერ მოხერხდა",
|
||||
"from path":"მისამართიდან",
|
||||
"server returned":"სერვერმა დააბრუნა"
|
||||
});
|
||||
52
connexion/vendor/swagger-ui/lang/it.js
vendored
@@ -1,52 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Attenzione: Deprecato",
|
||||
"Implementation Notes":"Note di implementazione",
|
||||
"Response Class":"Classe della risposta",
|
||||
"Status":"Stato",
|
||||
"Parameters":"Parametri",
|
||||
"Parameter":"Parametro",
|
||||
"Value":"Valore",
|
||||
"Description":"Descrizione",
|
||||
"Parameter Type":"Tipo di parametro",
|
||||
"Data Type":"Tipo di dato",
|
||||
"Response Messages":"Messaggi della risposta",
|
||||
"HTTP Status Code":"Codice stato HTTP",
|
||||
"Reason":"Motivo",
|
||||
"Response Model":"Modello di risposta",
|
||||
"Request URL":"URL della richiesta",
|
||||
"Response Body":"Corpo della risposta",
|
||||
"Response Code":"Oggetto della risposta",
|
||||
"Response Headers":"Intestazioni della risposta",
|
||||
"Hide Response":"Nascondi risposta",
|
||||
"Try it out!":"Provalo!",
|
||||
"Show/Hide":"Mostra/Nascondi",
|
||||
"List Operations":"Mostra operazioni",
|
||||
"Expand Operations":"Espandi operazioni",
|
||||
"Raw":"Grezzo (raw)",
|
||||
"can't parse JSON. Raw result":"non è possibile parsare il JSON. Risultato grezzo (raw).",
|
||||
"Model Schema":"Schema del modello",
|
||||
"Model":"Modello",
|
||||
"apply":"applica",
|
||||
"Username":"Nome utente",
|
||||
"Password":"Password",
|
||||
"Terms of service":"Condizioni del servizio",
|
||||
"Created by":"Creato da",
|
||||
"See more at":"Informazioni aggiuntive:",
|
||||
"Contact the developer":"Contatta lo sviluppatore",
|
||||
"api version":"versione api",
|
||||
"Response Content Type":"Tipo di contenuto (content type) della risposta",
|
||||
"fetching resource":"recuperando la risorsa",
|
||||
"fetching resource list":"recuperando lista risorse",
|
||||
"Explore":"Esplora",
|
||||
"Show Swagger Petstore Example Apis":"Mostra le api di esempio di Swagger Petstore",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.",
|
||||
"Please specify the protocol for":"Si prega di specificare il protocollo per",
|
||||
"Can't read swagger JSON from":"Impossibile leggere JSON swagger da:",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Lettura informazioni risorse termianta. Swagger UI viene mostrata",
|
||||
"Unable to read api":"Impossibile leggere la api",
|
||||
"from path":"da cartella",
|
||||
"server returned":"il server ha restituito"
|
||||
});
|
||||
53
connexion/vendor/swagger-ui/lang/ja.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"警告: 廃止予定",
|
||||
"Implementation Notes":"実装メモ",
|
||||
"Response Class":"レスポンスクラス",
|
||||
"Status":"ステータス",
|
||||
"Parameters":"パラメータ群",
|
||||
"Parameter":"パラメータ",
|
||||
"Value":"値",
|
||||
"Description":"説明",
|
||||
"Parameter Type":"パラメータタイプ",
|
||||
"Data Type":"データタイプ",
|
||||
"Response Messages":"レスポンスメッセージ",
|
||||
"HTTP Status Code":"HTTPステータスコード",
|
||||
"Reason":"理由",
|
||||
"Response Model":"レスポンスモデル",
|
||||
"Request URL":"リクエストURL",
|
||||
"Response Body":"レスポンスボディ",
|
||||
"Response Code":"レスポンスコード",
|
||||
"Response Headers":"レスポンスヘッダ",
|
||||
"Hide Response":"レスポンスを隠す",
|
||||
"Headers":"ヘッダ",
|
||||
"Try it out!":"実際に実行!",
|
||||
"Show/Hide":"表示/非表示",
|
||||
"List Operations":"操作一覧",
|
||||
"Expand Operations":"操作の展開",
|
||||
"Raw":"Raw",
|
||||
"can't parse JSON. Raw result":"JSONへ解釈できません. 未加工の結果",
|
||||
"Model Schema":"モデルスキーマ",
|
||||
"Model":"モデル",
|
||||
"apply":"実行",
|
||||
"Username":"ユーザ名",
|
||||
"Password":"パスワード",
|
||||
"Terms of service":"サービス利用規約",
|
||||
"Created by":"Created by",
|
||||
"See more at":"See more at",
|
||||
"Contact the developer":"開発者に連絡",
|
||||
"api version":"APIバージョン",
|
||||
"Response Content Type":"レスポンス コンテンツタイプ",
|
||||
"fetching resource":"リソースの取得",
|
||||
"fetching resource list":"リソース一覧の取得",
|
||||
"Explore":"Explore",
|
||||
"Show Swagger Petstore Example Apis":"SwaggerペットストアAPIの表示",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"サーバから読み込めません. 適切なaccess-control-origin設定を持っていない可能性があります.",
|
||||
"Please specify the protocol for":"プロトコルを指定してください",
|
||||
"Can't read swagger JSON from":"次からswagger JSONを読み込めません",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"リソース情報の読み込みが完了しました. Swagger UIを描画しています",
|
||||
"Unable to read api":"APIを読み込めません",
|
||||
"from path":"次のパスから",
|
||||
"server returned":"サーバからの返答"
|
||||
});
|
||||
53
connexion/vendor/swagger-ui/lang/ko-kr.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"경고:폐기예정됨",
|
||||
"Implementation Notes":"구현 노트",
|
||||
"Response Class":"응답 클래스",
|
||||
"Status":"상태",
|
||||
"Parameters":"매개변수들",
|
||||
"Parameter":"매개변수",
|
||||
"Value":"값",
|
||||
"Description":"설명",
|
||||
"Parameter Type":"매개변수 타입",
|
||||
"Data Type":"데이터 타입",
|
||||
"Response Messages":"응답 메세지",
|
||||
"HTTP Status Code":"HTTP 상태 코드",
|
||||
"Reason":"원인",
|
||||
"Response Model":"응답 모델",
|
||||
"Request URL":"요청 URL",
|
||||
"Response Body":"응답 본문",
|
||||
"Response Code":"응답 코드",
|
||||
"Response Headers":"응답 헤더",
|
||||
"Hide Response":"응답 숨기기",
|
||||
"Headers":"헤더",
|
||||
"Try it out!":"써보기!",
|
||||
"Show/Hide":"보이기/숨기기",
|
||||
"List Operations":"목록 작업",
|
||||
"Expand Operations":"전개 작업",
|
||||
"Raw":"원본",
|
||||
"can't parse JSON. Raw result":"JSON을 파싱할수 없음. 원본결과:",
|
||||
"Model Schema":"모델 스키마",
|
||||
"Model":"모델",
|
||||
"apply":"적용",
|
||||
"Username":"사용자 이름",
|
||||
"Password":"암호",
|
||||
"Terms of service":"이용약관",
|
||||
"Created by":"작성자",
|
||||
"See more at":"추가정보:",
|
||||
"Contact the developer":"개발자에게 문의",
|
||||
"api version":"api버전",
|
||||
"Response Content Type":"응답Content Type",
|
||||
"fetching resource":"리소스 가져오기",
|
||||
"fetching resource list":"리소스 목록 가져오기",
|
||||
"Explore":"탐색",
|
||||
"Show Swagger Petstore Example Apis":"Swagger Petstore 예제 보기",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"서버로부터 읽어들일수 없습니다. access-control-origin 설정이 올바르지 않을수 있습니다.",
|
||||
"Please specify the protocol for":"다음을 위한 프로토콜을 정하세요",
|
||||
"Can't read swagger JSON from":"swagger JSON 을 다음으로 부터 읽을수 없습니다",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"리소스 정보 불러오기 완료. Swagger UI 랜더링",
|
||||
"Unable to read api":"api를 읽을 수 없습니다.",
|
||||
"from path":"다음 경로로 부터",
|
||||
"server returned":"서버 응답함."
|
||||
});
|
||||
53
connexion/vendor/swagger-ui/lang/pl.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Uwaga: Wycofane",
|
||||
"Implementation Notes":"Uwagi Implementacji",
|
||||
"Response Class":"Klasa Odpowiedzi",
|
||||
"Status":"Status",
|
||||
"Parameters":"Parametry",
|
||||
"Parameter":"Parametr",
|
||||
"Value":"Wartość",
|
||||
"Description":"Opis",
|
||||
"Parameter Type":"Typ Parametru",
|
||||
"Data Type":"Typ Danych",
|
||||
"Response Messages":"Wiadomości Odpowiedzi",
|
||||
"HTTP Status Code":"Kod Statusu HTTP",
|
||||
"Reason":"Przyczyna",
|
||||
"Response Model":"Model Odpowiedzi",
|
||||
"Request URL":"URL Wywołania",
|
||||
"Response Body":"Treść Odpowiedzi",
|
||||
"Response Code":"Kod Odpowiedzi",
|
||||
"Response Headers":"Nagłówki Odpowiedzi",
|
||||
"Hide Response":"Ukryj Odpowiedź",
|
||||
"Headers":"Nagłówki",
|
||||
"Try it out!":"Wypróbuj!",
|
||||
"Show/Hide":"Pokaż/Ukryj",
|
||||
"List Operations":"Lista Operacji",
|
||||
"Expand Operations":"Rozwiń Operacje",
|
||||
"Raw":"Nieprzetworzone",
|
||||
"can't parse JSON. Raw result":"nie można przetworzyć pliku JSON. Nieprzetworzone dane",
|
||||
"Model Schema":"Schemat Modelu",
|
||||
"Model":"Model",
|
||||
"apply":"użyj",
|
||||
"Username":"Nazwa użytkownika",
|
||||
"Password":"Hasło",
|
||||
"Terms of service":"Warunki używania",
|
||||
"Created by":"Utworzone przez",
|
||||
"See more at":"Zobacz więcej na",
|
||||
"Contact the developer":"Kontakt z deweloperem",
|
||||
"api version":"wersja api",
|
||||
"Response Content Type":"Typ Zasobu Odpowiedzi",
|
||||
"fetching resource":"ładowanie zasobu",
|
||||
"fetching resource list":"ładowanie listy zasobów",
|
||||
"Explore":"Eksploruj",
|
||||
"Show Swagger Petstore Example Apis":"Pokaż Przykładowe Api Swagger Petstore",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Brak połączenia z serwerem. Może on nie mieć odpowiednich ustawień access-control-origin.",
|
||||
"Please specify the protocol for":"Proszę podać protokół dla",
|
||||
"Can't read swagger JSON from":"Nie można odczytać swagger JSON z",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Ukończono Ładowanie Informacji o Zasobie. Renderowanie Swagger UI",
|
||||
"Unable to read api":"Nie można odczytać api",
|
||||
"from path":"ze ścieżki",
|
||||
"server returned":"serwer zwrócił"
|
||||
});
|
||||
53
connexion/vendor/swagger-ui/lang/pt.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Aviso: Depreciado",
|
||||
"Implementation Notes":"Notas de Implementação",
|
||||
"Response Class":"Classe de resposta",
|
||||
"Status":"Status",
|
||||
"Parameters":"Parâmetros",
|
||||
"Parameter":"Parâmetro",
|
||||
"Value":"Valor",
|
||||
"Description":"Descrição",
|
||||
"Parameter Type":"Tipo de parâmetro",
|
||||
"Data Type":"Tipo de dados",
|
||||
"Response Messages":"Mensagens de resposta",
|
||||
"HTTP Status Code":"Código de status HTTP",
|
||||
"Reason":"Razão",
|
||||
"Response Model":"Modelo resposta",
|
||||
"Request URL":"URL requisição",
|
||||
"Response Body":"Corpo da resposta",
|
||||
"Response Code":"Código da resposta",
|
||||
"Response Headers":"Cabeçalho da resposta",
|
||||
"Headers":"Cabeçalhos",
|
||||
"Hide Response":"Esconder resposta",
|
||||
"Try it out!":"Tente agora!",
|
||||
"Show/Hide":"Mostrar/Esconder",
|
||||
"List Operations":"Listar operações",
|
||||
"Expand Operations":"Expandir operações",
|
||||
"Raw":"Cru",
|
||||
"can't parse JSON. Raw result":"Falha ao analisar JSON. Resulto cru",
|
||||
"Model Schema":"Modelo esquema",
|
||||
"Model":"Modelo",
|
||||
"apply":"Aplicar",
|
||||
"Username":"Usuário",
|
||||
"Password":"Senha",
|
||||
"Terms of service":"Termos do serviço",
|
||||
"Created by":"Criado por",
|
||||
"See more at":"Veja mais em",
|
||||
"Contact the developer":"Contate o desenvolvedor",
|
||||
"api version":"Versão api",
|
||||
"Response Content Type":"Tipo de conteúdo da resposta",
|
||||
"fetching resource":"busca recurso",
|
||||
"fetching resource list":"buscando lista de recursos",
|
||||
"Explore":"Explorar",
|
||||
"Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Não é possível ler do servidor. Pode não ter as apropriadas configurações access-control-origin",
|
||||
"Please specify the protocol for":"Por favor especifique o protocolo",
|
||||
"Can't read swagger JSON from":"Não é possível ler o JSON Swagger de",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Carregar informação de recurso finalizada. Renderizando Swagger UI",
|
||||
"Unable to read api":"Não foi possível ler api",
|
||||
"from path":"do caminho",
|
||||
"server returned":"servidor retornou"
|
||||
});
|
||||
56
connexion/vendor/swagger-ui/lang/ru.js
vendored
@@ -1,56 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Предупреждение: Устарело",
|
||||
"Implementation Notes":"Заметки",
|
||||
"Response Class":"Пример ответа",
|
||||
"Status":"Статус",
|
||||
"Parameters":"Параметры",
|
||||
"Parameter":"Параметр",
|
||||
"Value":"Значение",
|
||||
"Description":"Описание",
|
||||
"Parameter Type":"Тип параметра",
|
||||
"Data Type":"Тип данных",
|
||||
"HTTP Status Code":"HTTP код",
|
||||
"Reason":"Причина",
|
||||
"Response Model":"Структура ответа",
|
||||
"Request URL":"URL запроса",
|
||||
"Response Body":"Тело ответа",
|
||||
"Response Code":"HTTP код ответа",
|
||||
"Response Headers":"Заголовки ответа",
|
||||
"Hide Response":"Спрятать ответ",
|
||||
"Headers":"Заголовки",
|
||||
"Response Messages":"Что может прийти в ответ",
|
||||
"Try it out!":"Попробовать!",
|
||||
"Show/Hide":"Показать/Скрыть",
|
||||
"List Operations":"Операции кратко",
|
||||
"Expand Operations":"Операции подробно",
|
||||
"Raw":"В сыром виде",
|
||||
"can't parse JSON. Raw result":"Не удается распарсить ответ:",
|
||||
"Example Value":"Пример",
|
||||
"Model Schema":"Структура",
|
||||
"Model":"Описание",
|
||||
"Click to set as parameter value":"Нажмите, чтобы испльзовать в качестве значения параметра",
|
||||
"apply":"применить",
|
||||
"Username":"Имя пользователя",
|
||||
"Password":"Пароль",
|
||||
"Terms of service":"Условия использования",
|
||||
"Created by":"Разработано",
|
||||
"See more at":"Еще тут",
|
||||
"Contact the developer":"Связаться с разработчиком",
|
||||
"api version":"Версия API",
|
||||
"Response Content Type":"Content Type ответа",
|
||||
"Parameter content type:":"Content Type параметра:",
|
||||
"fetching resource":"Получение ресурса",
|
||||
"fetching resource list":"Получение ресурсов",
|
||||
"Explore":"Показать",
|
||||
"Show Swagger Petstore Example Apis":"Показать примеры АПИ",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Не удается получить ответ от сервера. Возможно, проблема с настройками доступа",
|
||||
"Please specify the protocol for":"Пожалуйста, укажите протокол для",
|
||||
"Can't read swagger JSON from":"Не получается прочитать swagger json из",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Загрузка информации о ресурсах завершена. Рендерим",
|
||||
"Unable to read api":"Не удалось прочитать api",
|
||||
"from path":"по адресу",
|
||||
"server returned":"сервер сказал"
|
||||
});
|
||||
53
connexion/vendor/swagger-ui/lang/tr.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"Uyarı: Deprecated",
|
||||
"Implementation Notes":"Gerçekleştirim Notları",
|
||||
"Response Class":"Dönen Sınıf",
|
||||
"Status":"Statü",
|
||||
"Parameters":"Parametreler",
|
||||
"Parameter":"Parametre",
|
||||
"Value":"Değer",
|
||||
"Description":"Açıklama",
|
||||
"Parameter Type":"Parametre Tipi",
|
||||
"Data Type":"Veri Tipi",
|
||||
"Response Messages":"Dönüş Mesajı",
|
||||
"HTTP Status Code":"HTTP Statü Kodu",
|
||||
"Reason":"Gerekçe",
|
||||
"Response Model":"Dönüş Modeli",
|
||||
"Request URL":"İstek URL",
|
||||
"Response Body":"Dönüş İçeriği",
|
||||
"Response Code":"Dönüş Kodu",
|
||||
"Response Headers":"Dönüş Üst Bilgileri",
|
||||
"Hide Response":"Dönüşü Gizle",
|
||||
"Headers":"Üst Bilgiler",
|
||||
"Try it out!":"Dene!",
|
||||
"Show/Hide":"Göster/Gizle",
|
||||
"List Operations":"Operasyonları Listele",
|
||||
"Expand Operations":"Operasyonları Aç",
|
||||
"Raw":"Ham",
|
||||
"can't parse JSON. Raw result":"JSON çözümlenemiyor. Ham sonuç",
|
||||
"Model Schema":"Model Şema",
|
||||
"Model":"Model",
|
||||
"apply":"uygula",
|
||||
"Username":"Kullanıcı Adı",
|
||||
"Password":"Parola",
|
||||
"Terms of service":"Servis şartları",
|
||||
"Created by":"Oluşturan",
|
||||
"See more at":"Daha fazlası için",
|
||||
"Contact the developer":"Geliştirici ile İletişime Geçin",
|
||||
"api version":"api versiyon",
|
||||
"Response Content Type":"Dönüş İçerik Tipi",
|
||||
"fetching resource":"kaynak getiriliyor",
|
||||
"fetching resource list":"kaynak listesi getiriliyor",
|
||||
"Explore":"Keşfet",
|
||||
"Show Swagger Petstore Example Apis":"Swagger Petstore Örnek Api'yi Gör",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Sunucudan okuma yapılamıyor. Sunucu access-control-origin ayarlarınızı kontrol edin.",
|
||||
"Please specify the protocol for":"Lütfen istenen adres için protokol belirtiniz",
|
||||
"Can't read swagger JSON from":"Swagger JSON bu kaynaktan okunamıyor",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"Kaynak baglantısı tamamlandı. Swagger UI gösterime hazırlanıyor",
|
||||
"Unable to read api":"api okunamadı",
|
||||
"from path":"yoldan",
|
||||
"server returned":"sunucuya dönüldü"
|
||||
});
|
||||
39
connexion/vendor/swagger-ui/lang/translator.js
vendored
@@ -1,39 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Translator for documentation pages.
|
||||
*
|
||||
* To enable translation you should include one of language-files in your index.html
|
||||
* after <script src='lang/translator.js' type='text/javascript'></script>.
|
||||
* For example - <script src='lang/ru.js' type='text/javascript'></script>
|
||||
*
|
||||
* If you wish to translate some new texts you should do two things:
|
||||
* 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too.
|
||||
* 2. Mark that text it templates this way <anyHtmlTag data-sw-translate>New Phrase</anyHtmlTag> or <anyHtmlTag data-sw-translate value='New Phrase'/>.
|
||||
* The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate.
|
||||
*
|
||||
*/
|
||||
window.SwaggerTranslator = {
|
||||
|
||||
_words:[],
|
||||
|
||||
translate: function(sel) {
|
||||
var $this = this;
|
||||
sel = sel || '[data-sw-translate]';
|
||||
|
||||
$(sel).each(function() {
|
||||
$(this).html($this._tryTranslate($(this).html()));
|
||||
|
||||
$(this).val($this._tryTranslate($(this).val()));
|
||||
$(this).attr('title', $this._tryTranslate($(this).attr('title')));
|
||||
});
|
||||
},
|
||||
|
||||
_tryTranslate: function(word) {
|
||||
return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word;
|
||||
},
|
||||
|
||||
learn: function(wordsMap) {
|
||||
this._words = wordsMap;
|
||||
}
|
||||
};
|
||||
53
connexion/vendor/swagger-ui/lang/zh-cn.js
vendored
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint quotmark: double */
|
||||
window.SwaggerTranslator.learn({
|
||||
"Warning: Deprecated":"警告:已过时",
|
||||
"Implementation Notes":"实现备注",
|
||||
"Response Class":"响应类",
|
||||
"Status":"状态",
|
||||
"Parameters":"参数",
|
||||
"Parameter":"参数",
|
||||
"Value":"值",
|
||||
"Description":"描述",
|
||||
"Parameter Type":"参数类型",
|
||||
"Data Type":"数据类型",
|
||||
"Response Messages":"响应消息",
|
||||
"HTTP Status Code":"HTTP状态码",
|
||||
"Reason":"原因",
|
||||
"Response Model":"响应模型",
|
||||
"Request URL":"请求URL",
|
||||
"Response Body":"响应体",
|
||||
"Response Code":"响应码",
|
||||
"Response Headers":"响应头",
|
||||
"Hide Response":"隐藏响应",
|
||||
"Headers":"头",
|
||||
"Try it out!":"试一下!",
|
||||
"Show/Hide":"显示/隐藏",
|
||||
"List Operations":"显示操作",
|
||||
"Expand Operations":"展开操作",
|
||||
"Raw":"原始",
|
||||
"can't parse JSON. Raw result":"无法解析JSON. 原始结果",
|
||||
"Model Schema":"模型架构",
|
||||
"Model":"模型",
|
||||
"apply":"应用",
|
||||
"Username":"用户名",
|
||||
"Password":"密码",
|
||||
"Terms of service":"服务条款",
|
||||
"Created by":"创建者",
|
||||
"See more at":"查看更多:",
|
||||
"Contact the developer":"联系开发者",
|
||||
"api version":"api版本",
|
||||
"Response Content Type":"响应Content Type",
|
||||
"fetching resource":"正在获取资源",
|
||||
"fetching resource list":"正在获取资源列表",
|
||||
"Explore":"浏览",
|
||||
"Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis",
|
||||
"Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。",
|
||||
"Please specify the protocol for":"请指定协议:",
|
||||
"Can't read swagger JSON from":"无法读取swagger JSON于",
|
||||
"Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI",
|
||||
"Unable to read api":"无法读取api",
|
||||
"from path":"从路径",
|
||||
"server returned":"服务器返回"
|
||||
});
|
||||
15
connexion/vendor/swagger-ui/lib/backbone-min.js
vendored
2065
connexion/vendor/swagger-ui/lib/es5-shim.js
vendored
4608
connexion/vendor/swagger-ui/lib/handlebars-4.0.5.js
vendored
@@ -1,34 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
var configure, highlightBlock;
|
||||
|
||||
configure = hljs.configure;
|
||||
// "extending" hljs.configure method
|
||||
hljs.configure = function _configure (options) {
|
||||
var size = options.highlightSizeThreshold;
|
||||
|
||||
// added highlightSizeThreshold option to set maximum size
|
||||
// of processed string. Set to null if not a number
|
||||
hljs.highlightSizeThreshold = size === +size ? size : null;
|
||||
|
||||
configure.call(this, options);
|
||||
};
|
||||
|
||||
highlightBlock = hljs.highlightBlock;
|
||||
|
||||
// "extending" hljs.highlightBlock method
|
||||
hljs.highlightBlock = function _highlightBlock (el) {
|
||||
var innerHTML = el.innerHTML;
|
||||
var size = hljs.highlightSizeThreshold;
|
||||
|
||||
// check if highlightSizeThreshold is not set or element innerHTML
|
||||
// is less than set option highlightSizeThreshold
|
||||
if (size == null || size > innerHTML.length) {
|
||||
// proceed with hljs.highlightBlock
|
||||
highlightBlock.call(hljs, el);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
|
||||
* http://benalman.com/projects/jquery-bbq-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
|
||||
/*
|
||||
* jQuery hashchange event - v1.2 - 2/11/2010
|
||||
* http://benalman.com/projects/jquery-hashchange-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);
|
||||
@@ -1 +0,0 @@
|
||||
(function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery);
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
jQuery Wiggle
|
||||
Author: WonderGroup, Jordan Thomas
|
||||
URL: http://labs.wondergroup.com/demos/mini-ui/index.html
|
||||
License: MIT (http://en.wikipedia.org/wiki/MIT_License)
|
||||
*/
|
||||
jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('<div class="wiggle-wrap"></div>').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);}
|
||||
if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});};
|
||||
102
connexion/vendor/swagger-ui/lib/lodash.min.js
vendored
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* lodash 3.10.1 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
|
||||
* Build: `lodash compat -o ./lodash.js`
|
||||
*/
|
||||
;(function(){function n(n,t){if(n!==t){var r=null===n,e=n===w,u=n===n,o=null===t,i=t===w,f=t===t;if(n>t&&!o||!u||r&&!i&&f||e&&f)return 1;if(n<t&&!r||!f||o&&!e&&u||i&&u)return-1}return 0}function t(n,t,r){for(var e=n.length,u=r?e:-1;r?u--:++u<e;)if(t(n[u],u,n))return u;return-1}function r(n,t,r){if(t!==t)return p(n,r);r-=1;for(var e=n.length;++r<e;)if(n[r]===t)return r;return-1}function e(n){return typeof n=="function"||false}function u(n){return null==n?"":n+""}function o(n,t){for(var r=-1,e=n.length;++r<e&&-1<t.indexOf(n.charAt(r)););
|
||||
return r}function i(n,t){for(var r=n.length;r--&&-1<t.indexOf(n.charAt(r)););return r}function f(t,r){return n(t.a,r.a)||t.b-r.b}function a(n){return Nn[n]}function c(n){return Tn[n]}function l(n,t,r){return t?n=Bn[n]:r&&(n=Dn[n]),"\\"+n}function s(n){return"\\"+Dn[n]}function p(n,t,r){var e=n.length;for(t+=r?0:-1;r?t--:++t<e;){var u=n[t];if(u!==u)return t}return-1}function h(n){return!!n&&typeof n=="object"}function _(n){return 160>=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n);
|
||||
}function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;)n[r]===t&&(n[r]=P,o[++u]=r);return o}function g(n){for(var t=-1,r=n.length;++t<r&&_(n.charCodeAt(t)););return t}function y(n){for(var t=n.length;t--&&_(n.charCodeAt(t)););return t}function d(n){return Pn[n]}function m(_){function Nn(n){if(h(n)&&!(Wo(n)||n instanceof zn)){if(n instanceof Pn)return n;if(eu.call(n,"__chain__")&&eu.call(n,"__wrapped__"))return qr(n)}return new Pn(n)}function Tn(){}function Pn(n,t,r){this.__wrapped__=n,this.__actions__=r||[],
|
||||
this.__chain__=!!t}function zn(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=Cu,this.__views__=[]}function Bn(){this.__data__={}}function Dn(n){var t=n?n.length:0;for(this.data={hash:mu(null),set:new hu};t--;)this.push(n[t])}function Mn(n,t){var r=n.data;return(typeof t=="string"||de(t)?r.set.has(t):r.hash[t])?0:-1}function qn(n,t){var r=-1,e=n.length;for(t||(t=De(e));++r<e;)t[r]=n[r];return t}function Kn(n,t){for(var r=-1,e=n.length;++r<e&&false!==t(n[r],r,n););
|
||||
return n}function Vn(n,t){for(var r=-1,e=n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function Zn(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;){var i=n[r];t(i,r,n)&&(o[++u]=i)}return o}function Xn(n,t){for(var r=-1,e=n.length,u=De(e);++r<e;)u[r]=t(n[r],r,n);return u}function Hn(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];return n}function Qn(n,t,r,e){var u=-1,o=n.length;for(e&&o&&(r=n[++u]);++u<o;)r=t(r,n[u],u,n);return r}function nt(n,t){for(var r=-1,e=n.length;++r<e;)if(t(n[r],r,n))return true;
|
||||
return false}function tt(n,t,r,e){return n!==w&&eu.call(e,r)?n:t}function rt(n,t,r){for(var e=-1,u=Ko(t),o=u.length;++e<o;){var i=u[e],f=n[i],a=r(f,t[i],i,n,t);(a===a?a===f:f!==f)&&(f!==w||i in n)||(n[i]=a)}return n}function et(n,t){return null==t?n:ot(t,Ko(t),n)}function ut(n,t){for(var r=-1,e=null==n,u=!e&&Sr(n),o=u?n.length:0,i=t.length,f=De(i);++r<i;){var a=t[r];f[r]=u?Ur(a,o)?n[a]:w:e?w:n[a]}return f}function ot(n,t,r){r||(r={});for(var e=-1,u=t.length;++e<u;){var o=t[e];r[o]=n[o]}return r}function it(n,t,r){
|
||||
var e=typeof n;return"function"==e?t===w?n:Dt(n,t,r):null==n?Ne:"object"==e?At(n):t===w?Be(n):jt(n,t)}function ft(n,t,r,e,u,o,i){var f;if(r&&(f=u?r(n,e,u):r(n)),f!==w)return f;if(!de(n))return n;if(e=Wo(n)){if(f=Ir(n),!t)return qn(n,f)}else{var a=ou.call(n),c=a==K;if(a!=Z&&a!=z&&(!c||u))return Ln[a]?Er(n,a,t):u?n:{};if(Gn(n))return u?n:{};if(f=Rr(c?{}:n),!t)return et(f,n)}for(o||(o=[]),i||(i=[]),u=o.length;u--;)if(o[u]==n)return i[u];return o.push(n),i.push(f),(e?Kn:gt)(n,function(e,u){f[u]=ft(e,t,r,u,n,o,i);
|
||||
}),f}function at(n,t,r){if(typeof n!="function")throw new Xe(T);return _u(function(){n.apply(w,r)},t)}function ct(n,t){var e=n?n.length:0,u=[];if(!e)return u;var o=-1,i=jr(),f=i===r,a=f&&t.length>=F&&mu&&hu?new Dn(t):null,c=t.length;a&&(i=Mn,f=false,t=a);n:for(;++o<e;)if(a=n[o],f&&a===a){for(var l=c;l--;)if(t[l]===a)continue n;u.push(a)}else 0>i(t,a,0)&&u.push(a);return u}function lt(n,t){var r=true;return zu(n,function(n,e,u){return r=!!t(n,e,u)}),r}function st(n,t,r,e){var u=e,o=u;return zu(n,function(n,i,f){
|
||||
i=+t(n,i,f),(r(i,u)||i===e&&i===o)&&(u=i,o=n)}),o}function pt(n,t){var r=[];return zu(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function ht(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function _t(n,t,r,e){e||(e=[]);for(var u=-1,o=n.length;++u<o;){var i=n[u];h(i)&&Sr(i)&&(r||Wo(i)||_e(i))?t?_t(i,t,r,e):Hn(e,i):r||(e[e.length]=i)}return e}function vt(n,t){return Du(n,t,Ee)}function gt(n,t){return Du(n,t,Ko)}function yt(n,t){return Mu(n,t,Ko)}function dt(n,t){for(var r=-1,e=t.length,u=-1,o=[];++r<e;){
|
||||
var i=t[r];ye(n[i])&&(o[++u]=i)}return o}function mt(n,t,r){if(null!=n){n=Dr(n),r!==w&&r in n&&(t=[r]),r=0;for(var e=t.length;null!=n&&r<e;)n=Dr(n)[t[r++]];return r&&r==e?n:w}}function wt(n,t,r,e,u,o){if(n===t)return true;if(null==n||null==t||!de(n)&&!h(t))return n!==n&&t!==t;n:{var i=wt,f=Wo(n),a=Wo(t),c=B,l=B;f||(c=ou.call(n),c==z?c=Z:c!=Z&&(f=je(n))),a||(l=ou.call(t),l==z?l=Z:l!=Z&&je(t));var s=c==Z&&!Gn(n),a=l==Z&&!Gn(t),l=c==l;if(!l||f||s){if(!e&&(c=s&&eu.call(n,"__wrapped__"),a=a&&eu.call(t,"__wrapped__"),
|
||||
c||a)){n=i(c?n.value():n,a?t.value():t,r,e,u,o);break n}if(l){for(u||(u=[]),o||(o=[]),c=u.length;c--;)if(u[c]==n){n=o[c]==t;break n}u.push(n),o.push(t),n=(f?mr:xr)(n,t,i,r,e,u,o),u.pop(),o.pop()}else n=false}else n=wr(n,t,c)}return n}function xt(n,t,r){var e=t.length,u=e,o=!r;if(null==n)return!u;for(n=Dr(n);e--;){var i=t[e];if(o&&i[2]?i[1]!==n[i[0]]:!(i[0]in n))return false}for(;++e<u;){var i=t[e],f=i[0],a=n[f],c=i[1];if(o&&i[2]){if(a===w&&!(f in n))return false}else if(i=r?r(a,c,f):w,i===w?!wt(c,a,r,true):!i)return false;
|
||||
}return true}function bt(n,t){var r=-1,e=Sr(n)?De(n.length):[];return zu(n,function(n,u,o){e[++r]=t(n,u,o)}),e}function At(n){var t=kr(n);if(1==t.length&&t[0][2]){var r=t[0][0],e=t[0][1];return function(n){return null==n?false:(n=Dr(n),n[r]===e&&(e!==w||r in n))}}return function(n){return xt(n,t)}}function jt(n,t){var r=Wo(n),e=Wr(n)&&t===t&&!de(t),u=n+"";return n=Mr(n),function(o){if(null==o)return false;var i=u;if(o=Dr(o),!(!r&&e||i in o)){if(o=1==n.length?o:mt(o,St(n,0,-1)),null==o)return false;i=Gr(n),o=Dr(o);
|
||||
}return o[i]===t?t!==w||i in o:wt(t,o[i],w,true)}}function kt(n,t,r,e,u){if(!de(n))return n;var o=Sr(t)&&(Wo(t)||je(t)),i=o?w:Ko(t);return Kn(i||t,function(f,a){if(i&&(a=f,f=t[a]),h(f)){e||(e=[]),u||(u=[]);n:{for(var c=a,l=e,s=u,p=l.length,_=t[c];p--;)if(l[p]==_){n[c]=s[p];break n}var p=n[c],v=r?r(p,_,c,n,t):w,g=v===w;g&&(v=_,Sr(_)&&(Wo(_)||je(_))?v=Wo(p)?p:Sr(p)?qn(p):[]:xe(_)||_e(_)?v=_e(p)?Ie(p):xe(p)?p:{}:g=false),l.push(_),s.push(v),g?n[c]=kt(v,_,r,l,s):(v===v?v!==p:p===p)&&(n[c]=v)}}else c=n[a],
|
||||
l=r?r(c,f,a,n,t):w,(s=l===w)&&(l=f),l===w&&(!o||a in n)||!s&&(l===l?l===c:c!==c)||(n[a]=l)}),n}function Ot(n){return function(t){return null==t?w:Dr(t)[n]}}function It(n){var t=n+"";return n=Mr(n),function(r){return mt(r,n,t)}}function Rt(n,t){for(var r=n?t.length:0;r--;){var e=t[r];if(e!=u&&Ur(e)){var u=e;vu.call(n,e,1)}}return n}function Et(n,t){return n+wu(Ru()*(t-n+1))}function Ct(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function St(n,t,r){var e=-1,u=n.length;for(t=null==t?0:+t||0,
|
||||
0>t&&(t=-t>u?0:u+t),r=r===w||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=De(u);++e<u;)r[e]=n[e+t];return r}function Ut(n,t){var r;return zu(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function $t(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;return n}function Wt(t,r,e){var u=br(),o=-1;return r=Xn(r,function(n){return u(n)}),t=bt(t,function(n){return{a:Xn(r,function(t){return t(n)}),b:++o,c:n}}),$t(t,function(t,r){var u;n:{for(var o=-1,i=t.a,f=r.a,a=i.length,c=e.length;++o<a;)if(u=n(i[o],f[o])){
|
||||
if(o>=c)break n;o=e[o],u*="asc"===o||true===o?1:-1;break n}u=t.b-r.b}return u})}function Ft(n,t){var r=0;return zu(n,function(n,e,u){r+=+t(n,e,u)||0}),r}function Lt(n,t){var e=-1,u=jr(),o=n.length,i=u===r,f=i&&o>=F,a=f&&mu&&hu?new Dn(void 0):null,c=[];a?(u=Mn,i=false):(f=false,a=t?[]:c);n:for(;++e<o;){var l=n[e],s=t?t(l,e,n):l;if(i&&l===l){for(var p=a.length;p--;)if(a[p]===s)continue n;t&&a.push(s),c.push(l)}else 0>u(a,s,0)&&((t||f)&&a.push(s),c.push(l))}return c}function Nt(n,t){for(var r=-1,e=t.length,u=De(e);++r<e;)u[r]=n[t[r]];
|
||||
return u}function Tt(n,t,r,e){for(var u=n.length,o=e?u:-1;(e?o--:++o<u)&&t(n[o],o,n););return r?St(n,e?0:o,e?o+1:u):St(n,e?o+1:0,e?u:o)}function Pt(n,t){var r=n;r instanceof zn&&(r=r.value());for(var e=-1,u=t.length;++e<u;)var o=t[e],r=o.func.apply(o.thisArg,Hn([r],o.args));return r}function zt(n,t,r){var e=0,u=n?n.length:e;if(typeof t=="number"&&t===t&&u<=Uu){for(;e<u;){var o=e+u>>>1,i=n[o];(r?i<=t:i<t)&&null!==i?e=o+1:u=o}return u}return Bt(n,t,Ne,r)}function Bt(n,t,r,e){t=r(t);for(var u=0,o=n?n.length:0,i=t!==t,f=null===t,a=t===w;u<o;){
|
||||
var c=wu((u+o)/2),l=r(n[c]),s=l!==w,p=l===l;(i?p||e:f?p&&s&&(e||null!=l):a?p&&(e||s):null==l?0:e?l<=t:l<t)?u=c+1:o=c}return ku(o,Su)}function Dt(n,t,r){if(typeof n!="function")return Ne;if(t===w)return n;switch(r){case 1:return function(r){return n.call(t,r)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,o){return n.call(t,r,e,u,o)};case 5:return function(r,e,u,o,i){return n.call(t,r,e,u,o,i)}}return function(){return n.apply(t,arguments)}}function Mt(n){var t=new au(n.byteLength);
|
||||
return new gu(t).set(new gu(n)),t}function qt(n,t,r){for(var e=r.length,u=-1,o=ju(n.length-e,0),i=-1,f=t.length,a=De(f+o);++i<f;)a[i]=t[i];for(;++u<e;)a[r[u]]=n[u];for(;o--;)a[i++]=n[u++];return a}function Kt(n,t,r){for(var e=-1,u=r.length,o=-1,i=ju(n.length-u,0),f=-1,a=t.length,c=De(i+a);++o<i;)c[o]=n[o];for(i=o;++f<a;)c[i+f]=t[f];for(;++e<u;)c[i+r[e]]=n[o++];return c}function Vt(n,t){return function(r,e,u){var o=t?t():{};if(e=br(e,u,3),Wo(r)){u=-1;for(var i=r.length;++u<i;){var f=r[u];n(o,f,e(f,u,r),r);
|
||||
}}else zu(r,function(t,r,u){n(o,t,e(t,r,u),u)});return o}}function Zt(n){return pe(function(t,r){var e=-1,u=null==t?0:r.length,o=2<u?r[u-2]:w,i=2<u?r[2]:w,f=1<u?r[u-1]:w;for(typeof o=="function"?(o=Dt(o,f,5),u-=2):(o=typeof f=="function"?f:w,u-=o?1:0),i&&$r(r[0],r[1],i)&&(o=3>u?w:o,u=1);++e<u;)(i=r[e])&&n(t,i,o);return t})}function Yt(n,t){return function(r,e){var u=r?Vu(r):0;if(!Lr(u))return n(r,e);for(var o=t?u:-1,i=Dr(r);(t?o--:++o<u)&&false!==e(i[o],o,i););return r}}function Gt(n){return function(t,r,e){
|
||||
var u=Dr(t);e=e(t);for(var o=e.length,i=n?o:-1;n?i--:++i<o;){var f=e[i];if(false===r(u[f],f,u))break}return t}}function Jt(n,t){function r(){return(this&&this!==Yn&&this instanceof r?e:n).apply(t,arguments)}var e=Ht(n);return r}function Xt(n){return function(t){var r=-1;t=Fe(Ue(t));for(var e=t.length,u="";++r<e;)u=n(u,t[r],r);return u}}function Ht(n){return function(){var t=arguments;switch(t.length){case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:return new n(t[0],t[1],t[2]);
|
||||
case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=Pu(n.prototype),t=n.apply(r,t);return de(t)?t:r}}function Qt(n){function t(r,e,u){return u&&$r(r,e,u)&&(e=w),r=dr(r,n,w,w,w,w,w,e),r.placeholder=t.placeholder,r}return t}function nr(n,t){return pe(function(r){var e=r[0];return null==e?e:(r.push(t),n.apply(w,r))})}function tr(n,t){return function(r,e,u){
|
||||
if(u&&$r(r,e,u)&&(e=w),e=br(e,u,3),1==e.length){u=r=Wo(r)?r:Br(r);for(var o=e,i=-1,f=u.length,a=t,c=a;++i<f;){var l=u[i],s=+o(l);n(s,a)&&(a=s,c=l)}if(u=c,!r.length||u!==t)return u}return st(r,e,n,t)}}function rr(n,r){return function(e,u,o){return u=br(u,o,3),Wo(e)?(u=t(e,u,r),-1<u?e[u]:w):ht(e,u,n)}}function er(n){return function(r,e,u){return r&&r.length?(e=br(e,u,3),t(r,e,n)):-1}}function ur(n){return function(t,r,e){return r=br(r,e,3),ht(t,r,n,true)}}function or(n){return function(){for(var t,r=arguments.length,e=n?r:-1,u=0,o=De(r);n?e--:++e<r;){
|
||||
var i=o[u++]=arguments[e];if(typeof i!="function")throw new Xe(T);!t&&Pn.prototype.thru&&"wrapper"==Ar(i)&&(t=new Pn([],true))}for(e=t?-1:r;++e<r;){var i=o[e],u=Ar(i),f="wrapper"==u?Ku(i):w;t=f&&Fr(f[0])&&f[1]==(E|k|I|C)&&!f[4].length&&1==f[9]?t[Ar(f[0])].apply(t,f[3]):1==i.length&&Fr(i)?t[u]():t.thru(i)}return function(){var n=arguments,e=n[0];if(t&&1==n.length&&Wo(e)&&e.length>=F)return t.plant(e).value();for(var u=0,n=r?o[u].apply(this,n):e;++u<r;)n=o[u].call(this,n);return n}}}function ir(n,t){
|
||||
return function(r,e,u){return typeof e=="function"&&u===w&&Wo(r)?n(r,e):t(r,Dt(e,u,3))}}function fr(n){return function(t,r,e){return(typeof r!="function"||e!==w)&&(r=Dt(r,e,3)),n(t,r,Ee)}}function ar(n){return function(t,r,e){return(typeof r!="function"||e!==w)&&(r=Dt(r,e,3)),n(t,r)}}function cr(n){return function(t,r,e){var u={};return r=br(r,e,3),gt(t,function(t,e,o){o=r(t,e,o),e=n?o:e,t=n?t:o,u[e]=t}),u}}function lr(n){return function(t,r,e){return t=u(t),(n?t:"")+_r(t,r,e)+(n?"":t)}}function sr(n){
|
||||
var t=pe(function(r,e){var u=v(e,t.placeholder);return dr(r,n,w,e,u)});return t}function pr(n,t){return function(r,e,u,o){var i=3>arguments.length;return typeof e=="function"&&o===w&&Wo(r)?n(r,e,u,i):Ct(r,br(e,o,4),u,i,t)}}function hr(n,t,r,e,u,o,i,f,a,c){function l(){for(var m=arguments.length,x=m,j=De(m);x--;)j[x]=arguments[x];if(e&&(j=qt(j,e,u)),o&&(j=Kt(j,o,i)),_||y){var x=l.placeholder,k=v(j,x),m=m-k.length;if(m<c){var O=f?qn(f):w,m=ju(c-m,0),E=_?k:w,k=_?w:k,C=_?j:w,j=_?w:j;return t|=_?I:R,t&=~(_?R:I),
|
||||
g||(t&=~(b|A)),j=[n,t,r,C,E,j,k,O,a,m],O=hr.apply(w,j),Fr(n)&&Zu(O,j),O.placeholder=x,O}}if(x=p?r:this,O=h?x[n]:n,f)for(m=j.length,E=ku(f.length,m),k=qn(j);E--;)C=f[E],j[E]=Ur(C,m)?k[C]:w;return s&&a<j.length&&(j.length=a),this&&this!==Yn&&this instanceof l&&(O=d||Ht(n)),O.apply(x,j)}var s=t&E,p=t&b,h=t&A,_=t&k,g=t&j,y=t&O,d=h?w:Ht(n);return l}function _r(n,t,r){return n=n.length,t=+t,n<t&&bu(t)?(t-=n,r=null==r?" ":r+"",$e(r,du(t/r.length)).slice(0,t)):""}function vr(n,t,r,e){function u(){for(var t=-1,f=arguments.length,a=-1,c=e.length,l=De(c+f);++a<c;)l[a]=e[a];
|
||||
for(;f--;)l[a++]=arguments[++t];return(this&&this!==Yn&&this instanceof u?i:n).apply(o?r:this,l)}var o=t&b,i=Ht(n);return u}function gr(n){var t=Ve[n];return function(n,r){return(r=r===w?0:+r||0)?(r=su(10,r),t(n*r)/r):t(n)}}function yr(n){return function(t,r,e,u){var o=br(e);return null==e&&o===it?zt(t,r,n):Bt(t,r,o(e,u,1),n)}}function dr(n,t,r,e,u,o,i,f){var a=t&A;if(!a&&typeof n!="function")throw new Xe(T);var c=e?e.length:0;if(c||(t&=~(I|R),e=u=w),c-=u?u.length:0,t&R){var l=e,s=u;e=u=w}var p=a?w:Ku(n);
|
||||
return r=[n,t,r,e,u,l,s,o,i,f],p&&(e=r[1],t=p[1],f=e|t,u=t==E&&e==k||t==E&&e==C&&r[7].length<=p[8]||t==(E|C)&&e==k,(f<E||u)&&(t&b&&(r[2]=p[2],f|=e&b?0:j),(e=p[3])&&(u=r[3],r[3]=u?qt(u,e,p[4]):qn(e),r[4]=u?v(r[3],P):qn(p[4])),(e=p[5])&&(u=r[5],r[5]=u?Kt(u,e,p[6]):qn(e),r[6]=u?v(r[5],P):qn(p[6])),(e=p[7])&&(r[7]=qn(e)),t&E&&(r[8]=null==r[8]?p[8]:ku(r[8],p[8])),null==r[9]&&(r[9]=p[9]),r[0]=p[0],r[1]=f),t=r[1],f=r[9]),r[9]=null==f?a?0:n.length:ju(f-c,0)||0,n=t==b?Jt(r[0],r[2]):t!=I&&t!=(b|I)||r[4].length?hr.apply(w,r):vr.apply(w,r),
|
||||
(p?qu:Zu)(n,r)}function mr(n,t,r,e,u,o,i){var f=-1,a=n.length,c=t.length;if(a!=c&&(!u||c<=a))return false;for(;++f<a;){var l=n[f],c=t[f],s=e?e(u?c:l,u?l:c,f):w;if(s!==w){if(s)continue;return false}if(u){if(!nt(t,function(n){return l===n||r(l,n,e,u,o,i)}))return false}else if(l!==c&&!r(l,c,e,u,o,i))return false}return true}function wr(n,t,r){switch(r){case D:case M:return+n==+t;case q:return n.name==t.name&&n.message==t.message;case V:return n!=+n?t!=+t:n==+t;case Y:case G:return n==t+""}return false}function xr(n,t,r,e,u,o,i){
|
||||
var f=Ko(n),a=f.length,c=Ko(t).length;if(a!=c&&!u)return false;for(c=a;c--;){var l=f[c];if(!(u?l in t:eu.call(t,l)))return false}for(var s=u;++c<a;){var l=f[c],p=n[l],h=t[l],_=e?e(u?h:p,u?p:h,l):w;if(_===w?!r(p,h,e,u,o,i):!_)return false;s||(s="constructor"==l)}return s||(r=n.constructor,e=t.constructor,!(r!=e&&"constructor"in n&&"constructor"in t)||typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)?true:false}function br(n,t,r){var e=Nn.callback||Le,e=e===Le?it:e;return r?e(n,t,r):e}function Ar(n){
|
||||
for(var t=n.name+"",r=Fu[t],e=r?r.length:0;e--;){var u=r[e],o=u.func;if(null==o||o==n)return u.name}return t}function jr(n,t,e){var u=Nn.indexOf||Yr,u=u===Yr?r:u;return n?u(n,t,e):u}function kr(n){n=Ce(n);for(var t=n.length;t--;){var r,e=n[t];r=n[t][1],r=r===r&&!de(r),e[2]=r}return n}function Or(n,t){var r=null==n?w:n[t];return me(r)?r:w}function Ir(n){var t=n.length,r=new n.constructor(t);return t&&"string"==typeof n[0]&&eu.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Rr(n){return n=n.constructor,
|
||||
typeof n=="function"&&n instanceof n||(n=Ye),new n}function Er(n,t,r){var e=n.constructor;switch(t){case J:return Mt(n);case D:case M:return new e(+n);case X:case H:case Q:case nn:case tn:case rn:case en:case un:case on:return e instanceof e&&(e=Lu[t]),t=n.buffer,new e(r?Mt(t):t,n.byteOffset,n.length);case V:case G:return new e(n);case Y:var u=new e(n.source,kn.exec(n));u.lastIndex=n.lastIndex}return u}function Cr(n,t,r){return null==n||Wr(t,n)||(t=Mr(t),n=1==t.length?n:mt(n,St(t,0,-1)),t=Gr(t)),
|
||||
t=null==n?n:n[t],null==t?w:t.apply(n,r)}function Sr(n){return null!=n&&Lr(Vu(n))}function Ur(n,t){return n=typeof n=="number"||Rn.test(n)?+n:-1,t=null==t?$u:t,-1<n&&0==n%1&&n<t}function $r(n,t,r){if(!de(r))return false;var e=typeof t;return("number"==e?Sr(r)&&Ur(t,r.length):"string"==e&&t in r)?(t=r[t],n===n?n===t:t!==t):false}function Wr(n,t){var r=typeof n;return"string"==r&&dn.test(n)||"number"==r?true:Wo(n)?false:!yn.test(n)||null!=t&&n in Dr(t)}function Fr(n){var t=Ar(n),r=Nn[t];return typeof r=="function"&&t in zn.prototype?n===r?true:(t=Ku(r),
|
||||
!!t&&n===t[0]):false}function Lr(n){return typeof n=="number"&&-1<n&&0==n%1&&n<=$u}function Nr(n,t){return n===w?t:Fo(n,t,Nr)}function Tr(n,t){n=Dr(n);for(var r=-1,e=t.length,u={};++r<e;){var o=t[r];o in n&&(u[o]=n[o])}return u}function Pr(n,t){var r={};return vt(n,function(n,e,u){t(n,e,u)&&(r[e]=n)}),r}function zr(n){for(var t=Ee(n),r=t.length,e=r&&n.length,u=!!e&&Lr(e)&&(Wo(n)||_e(n)||Ae(n)),o=-1,i=[];++o<r;){var f=t[o];(u&&Ur(f,e)||eu.call(n,f))&&i.push(f)}return i}function Br(n){return null==n?[]:Sr(n)?Nn.support.unindexedChars&&Ae(n)?n.split(""):de(n)?n:Ye(n):Se(n);
|
||||
}function Dr(n){if(Nn.support.unindexedChars&&Ae(n)){for(var t=-1,r=n.length,e=Ye(n);++t<r;)e[t]=n.charAt(t);return e}return de(n)?n:Ye(n)}function Mr(n){if(Wo(n))return n;var t=[];return u(n).replace(mn,function(n,r,e,u){t.push(e?u.replace(An,"$1"):r||n)}),t}function qr(n){return n instanceof zn?n.clone():new Pn(n.__wrapped__,n.__chain__,qn(n.__actions__))}function Kr(n,t,r){return n&&n.length?((r?$r(n,t,r):null==t)&&(t=1),St(n,0>t?0:t)):[]}function Vr(n,t,r){var e=n?n.length:0;return e?((r?$r(n,t,r):null==t)&&(t=1),
|
||||
t=e-(+t||0),St(n,0,0>t?0:t)):[]}function Zr(n){return n?n[0]:w}function Yr(n,t,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?ju(u+e,0):e;else if(e)return e=zt(n,t),e<u&&(t===t?t===n[e]:n[e]!==n[e])?e:-1;return r(n,t,e||0)}function Gr(n){var t=n?n.length:0;return t?n[t-1]:w}function Jr(n){return Kr(n,1)}function Xr(n,t,e,u){if(!n||!n.length)return[];null!=t&&typeof t!="boolean"&&(u=e,e=$r(n,t,u)?w:t,t=false);var o=br();if((null!=e||o!==it)&&(e=o(e,u,3)),t&&jr()===r){t=e;var i;e=-1,
|
||||
u=n.length;for(var o=-1,f=[];++e<u;){var a=n[e],c=t?t(a,e,n):a;e&&i===c||(i=c,f[++o]=a)}n=f}else n=Lt(n,e);return n}function Hr(n){if(!n||!n.length)return[];var t=-1,r=0;n=Zn(n,function(n){return Sr(n)?(r=ju(n.length,r),true):void 0});for(var e=De(r);++t<r;)e[t]=Xn(n,Ot(t));return e}function Qr(n,t,r){return n&&n.length?(n=Hr(n),null==t?n:(t=Dt(t,r,4),Xn(n,function(n){return Qn(n,t,w,true)}))):[]}function ne(n,t){var r=-1,e=n?n.length:0,u={};for(!e||t||Wo(n[0])||(t=[]);++r<e;){var o=n[r];t?u[o]=t[r]:o&&(u[o[0]]=o[1]);
|
||||
}return u}function te(n){return n=Nn(n),n.__chain__=true,n}function re(n,t,r){return t.call(r,n)}function ee(n,t,r){var e=Wo(n)?Vn:lt;return r&&$r(n,t,r)&&(t=w),(typeof t!="function"||r!==w)&&(t=br(t,r,3)),e(n,t)}function ue(n,t,r){var e=Wo(n)?Zn:pt;return t=br(t,r,3),e(n,t)}function oe(n,t,r,e){var u=n?Vu(n):0;return Lr(u)||(n=Se(n),u=n.length),r=typeof r!="number"||e&&$r(t,r,e)?0:0>r?ju(u+r,0):r||0,typeof n=="string"||!Wo(n)&&Ae(n)?r<=u&&-1<n.indexOf(t,r):!!u&&-1<jr(n,t,r)}function ie(n,t,r){var e=Wo(n)?Xn:bt;
|
||||
return t=br(t,r,3),e(n,t)}function fe(n,t,r){if(r?$r(n,t,r):null==t){n=Br(n);var e=n.length;return 0<e?n[Et(0,e-1)]:w}r=-1,n=Oe(n);var e=n.length,u=e-1;for(t=ku(0>t?0:+t||0,e);++r<t;){var e=Et(r,u),o=n[e];n[e]=n[r],n[r]=o}return n.length=t,n}function ae(n,t,r){var e=Wo(n)?nt:Ut;return r&&$r(n,t,r)&&(t=w),(typeof t!="function"||r!==w)&&(t=br(t,r,3)),e(n,t)}function ce(n,t){var r;if(typeof t!="function"){if(typeof n!="function")throw new Xe(T);var e=n;n=t,t=e}return function(){return 0<--n&&(r=t.apply(this,arguments)),
|
||||
1>=n&&(t=w),r}}function le(n,t,r){function e(t,r){r&&cu(r),a=p=h=w,t&&(_=wo(),c=n.apply(s,f),p||a||(f=s=w))}function u(){var n=t-(wo()-l);0>=n||n>t?e(h,a):p=_u(u,n)}function o(){e(g,p)}function i(){if(f=arguments,l=wo(),s=this,h=g&&(p||!y),false===v)var r=y&&!p;else{a||y||(_=l);var e=v-(l-_),i=0>=e||e>v;i?(a&&(a=cu(a)),_=l,c=n.apply(s,f)):a||(a=_u(o,e))}return i&&p?p=cu(p):p||t===v||(p=_u(u,t)),r&&(i=true,c=n.apply(s,f)),!i||p||a||(f=s=w),c}var f,a,c,l,s,p,h,_=0,v=false,g=true;if(typeof n!="function")throw new Xe(T);
|
||||
if(t=0>t?0:+t||0,true===r)var y=true,g=false;else de(r)&&(y=!!r.leading,v="maxWait"in r&&ju(+r.maxWait||0,t),g="trailing"in r?!!r.trailing:g);return i.cancel=function(){p&&cu(p),a&&cu(a),_=0,a=p=h=w},i}function se(n,t){if(typeof n!="function"||t&&typeof t!="function")throw new Xe(T);var r=function(){var e=arguments,u=t?t.apply(this,e):e[0],o=r.cache;return o.has(u)?o.get(u):(e=n.apply(this,e),r.cache=o.set(u,e),e)};return r.cache=new se.Cache,r}function pe(n,t){if(typeof n!="function")throw new Xe(T);return t=ju(t===w?n.length-1:+t||0,0),
|
||||
function(){for(var r=arguments,e=-1,u=ju(r.length-t,0),o=De(u);++e<u;)o[e]=r[t+e];switch(t){case 0:return n.call(this,o);case 1:return n.call(this,r[0],o);case 2:return n.call(this,r[0],r[1],o)}for(u=De(t+1),e=-1;++e<t;)u[e]=r[e];return u[t]=o,n.apply(this,u)}}function he(n,t){return n>t}function _e(n){return h(n)&&Sr(n)&&eu.call(n,"callee")&&!pu.call(n,"callee")}function ve(n,t,r,e){return e=(r=typeof r=="function"?Dt(r,e,3):w)?r(n,t):w,e===w?wt(n,t,r):!!e}function ge(n){return h(n)&&typeof n.message=="string"&&ou.call(n)==q;
|
||||
}function ye(n){return de(n)&&ou.call(n)==K}function de(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function me(n){return null==n?false:ye(n)?fu.test(ru.call(n)):h(n)&&(Gn(n)?fu:In).test(n)}function we(n){return typeof n=="number"||h(n)&&ou.call(n)==V}function xe(n){var t;if(!h(n)||ou.call(n)!=Z||Gn(n)||_e(n)||!(eu.call(n,"constructor")||(t=n.constructor,typeof t!="function"||t instanceof t)))return false;var r;return Nn.support.ownLast?(vt(n,function(n,t,e){return r=eu.call(e,t),false}),false!==r):(vt(n,function(n,t){
|
||||
r=t}),r===w||eu.call(n,r))}function be(n){return de(n)&&ou.call(n)==Y}function Ae(n){return typeof n=="string"||h(n)&&ou.call(n)==G}function je(n){return h(n)&&Lr(n.length)&&!!Fn[ou.call(n)]}function ke(n,t){return n<t}function Oe(n){var t=n?Vu(n):0;return Lr(t)?t?Nn.support.unindexedChars&&Ae(n)?n.split(""):qn(n):[]:Se(n)}function Ie(n){return ot(n,Ee(n))}function Re(n){return dt(n,Ee(n))}function Ee(n){if(null==n)return[];de(n)||(n=Ye(n));for(var t=n.length,r=Nn.support,t=t&&Lr(t)&&(Wo(n)||_e(n)||Ae(n))&&t||0,e=n.constructor,u=-1,e=ye(e)&&e.prototype||nu,o=e===n,i=De(t),f=0<t,a=r.enumErrorProps&&(n===Qe||n instanceof qe),c=r.enumPrototypes&&ye(n);++u<t;)i[u]=u+"";
|
||||
for(var l in n)c&&"prototype"==l||a&&("message"==l||"name"==l)||f&&Ur(l,t)||"constructor"==l&&(o||!eu.call(n,l))||i.push(l);if(r.nonEnumShadows&&n!==nu)for(t=n===tu?G:n===Qe?q:ou.call(n),r=Nu[t]||Nu[Z],t==Z&&(e=nu),t=Wn.length;t--;)l=Wn[t],u=r[l],o&&u||(u?!eu.call(n,l):n[l]===e[l])||i.push(l);return i}function Ce(n){n=Dr(n);for(var t=-1,r=Ko(n),e=r.length,u=De(e);++t<e;){var o=r[t];u[t]=[o,n[o]]}return u}function Se(n){return Nt(n,Ko(n))}function Ue(n){return(n=u(n))&&n.replace(En,a).replace(bn,"");
|
||||
}function $e(n,t){var r="";if(n=u(n),t=+t,1>t||!n||!bu(t))return r;do t%2&&(r+=n),t=wu(t/2),n+=n;while(t);return r}function We(n,t,r){var e=n;return(n=u(n))?(r?$r(e,t,r):null==t)?n.slice(g(n),y(n)+1):(t+="",n.slice(o(n,t),i(n,t)+1)):n}function Fe(n,t,r){return r&&$r(n,t,r)&&(t=w),n=u(n),n.match(t||Un)||[]}function Le(n,t,r){return r&&$r(n,t,r)&&(t=w),h(n)?Te(n):it(n,t)}function Ne(n){return n}function Te(n){return At(ft(n,true))}function Pe(n,t,r){if(null==r){var e=de(t),u=e?Ko(t):w;((u=u&&u.length?dt(t,u):w)?u.length:e)||(u=false,
|
||||
r=t,t=n,n=this)}u||(u=dt(t,Ko(t)));var o=true,e=-1,i=ye(n),f=u.length;false===r?o=false:de(r)&&"chain"in r&&(o=r.chain);for(;++e<f;){r=u[e];var a=t[r];n[r]=a,i&&(n.prototype[r]=function(t){return function(){var r=this.__chain__;if(o||r){var e=n(this.__wrapped__);return(e.__actions__=qn(this.__actions__)).push({func:t,args:arguments,thisArg:n}),e.__chain__=r,e}return t.apply(n,Hn([this.value()],arguments))}}(a))}return n}function ze(){}function Be(n){return Wr(n)?Ot(n):It(n)}_=_?Jn.defaults(Yn.Object(),_,Jn.pick(Yn,$n)):Yn;
|
||||
var De=_.Array,Me=_.Date,qe=_.Error,Ke=_.Function,Ve=_.Math,Ze=_.Number,Ye=_.Object,Ge=_.RegExp,Je=_.String,Xe=_.TypeError,He=De.prototype,Qe=qe.prototype,nu=Ye.prototype,tu=Je.prototype,ru=Ke.prototype.toString,eu=nu.hasOwnProperty,uu=0,ou=nu.toString,iu=Yn._,fu=Ge("^"+ru.call(eu).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),au=_.ArrayBuffer,cu=_.clearTimeout,lu=_.parseFloat,su=Ve.pow,pu=nu.propertyIsEnumerable,hu=Or(_,"Set"),_u=_.setTimeout,vu=He.splice,gu=_.Uint8Array,yu=Or(_,"WeakMap"),du=Ve.ceil,mu=Or(Ye,"create"),wu=Ve.floor,xu=Or(De,"isArray"),bu=_.isFinite,Au=Or(Ye,"keys"),ju=Ve.max,ku=Ve.min,Ou=Or(Me,"now"),Iu=_.parseInt,Ru=Ve.random,Eu=Ze.NEGATIVE_INFINITY,Cu=Ze.POSITIVE_INFINITY,Su=4294967294,Uu=2147483647,$u=9007199254740991,Wu=yu&&new yu,Fu={},Lu={};
|
||||
Lu[X]=_.Float32Array,Lu[H]=_.Float64Array,Lu[Q]=_.Int8Array,Lu[nn]=_.Int16Array,Lu[tn]=_.Int32Array,Lu[rn]=gu,Lu[en]=_.Uint8ClampedArray,Lu[un]=_.Uint16Array,Lu[on]=_.Uint32Array;var Nu={};Nu[B]=Nu[M]=Nu[V]={constructor:true,toLocaleString:true,toString:true,valueOf:true},Nu[D]=Nu[G]={constructor:true,toString:true,valueOf:true},Nu[q]=Nu[K]=Nu[Y]={constructor:true,toString:true},Nu[Z]={constructor:true},Kn(Wn,function(n){for(var t in Nu)if(eu.call(Nu,t)){var r=Nu[t];r[n]=eu.call(r,n)}});var Tu=Nn.support={};!function(n){
|
||||
var t=function(){this.x=n},r={0:n,length:n},e=[];t.prototype={valueOf:n,y:n};for(var u in new t)e.push(u);Tu.enumErrorProps=pu.call(Qe,"message")||pu.call(Qe,"name"),Tu.enumPrototypes=pu.call(t,"prototype"),Tu.nonEnumShadows=!/valueOf/.test(e),Tu.ownLast="x"!=e[0],Tu.spliceObjects=(vu.call(r,0,1),!r[0]),Tu.unindexedChars="xx"!="x"[0]+Ye("x")[0]}(1,0),Nn.templateSettings={escape:_n,evaluate:vn,interpolate:gn,variable:"",imports:{_:Nn}};var Pu=function(){function n(){}return function(t){if(de(t)){n.prototype=t;
|
||||
var r=new n;n.prototype=w}return r||{}}}(),zu=Yt(gt),Bu=Yt(yt,true),Du=Gt(),Mu=Gt(true),qu=Wu?function(n,t){return Wu.set(n,t),n}:Ne,Ku=Wu?function(n){return Wu.get(n)}:ze,Vu=Ot("length"),Zu=function(){var n=0,t=0;return function(r,e){var u=wo(),o=W-(u-t);if(t=u,0<o){if(++n>=$)return r}else n=0;return qu(r,e)}}(),Yu=pe(function(n,t){return h(n)&&Sr(n)?ct(n,_t(t,false,true)):[]}),Gu=er(),Ju=er(true),Xu=pe(function(n){for(var t=n.length,e=t,u=De(l),o=jr(),i=o===r,f=[];e--;){var a=n[e]=Sr(a=n[e])?a:[];u[e]=i&&120<=a.length&&mu&&hu?new Dn(e&&a):null;
|
||||
}var i=n[0],c=-1,l=i?i.length:0,s=u[0];n:for(;++c<l;)if(a=i[c],0>(s?Mn(s,a):o(f,a,0))){for(e=t;--e;){var p=u[e];if(0>(p?Mn(p,a):o(n[e],a,0)))continue n}s&&s.push(a),f.push(a)}return f}),Hu=pe(function(t,r){r=_t(r);var e=ut(t,r);return Rt(t,r.sort(n)),e}),Qu=yr(),no=yr(true),to=pe(function(n){return Lt(_t(n,false,true))}),ro=pe(function(n,t){return Sr(n)?ct(n,t):[]}),eo=pe(Hr),uo=pe(function(n){var t=n.length,r=2<t?n[t-2]:w,e=1<t?n[t-1]:w;return 2<t&&typeof r=="function"?t-=2:(r=1<t&&typeof e=="function"?(--t,
|
||||
e):w,e=w),n.length=t,Qr(n,r,e)}),oo=pe(function(n){return n=_t(n),this.thru(function(t){t=Wo(t)?t:[Dr(t)];for(var r=n,e=-1,u=t.length,o=-1,i=r.length,f=De(u+i);++e<u;)f[e]=t[e];for(;++o<i;)f[e++]=r[o];return f})}),io=pe(function(n,t){return Sr(n)&&(n=Br(n)),ut(n,_t(t))}),fo=Vt(function(n,t,r){eu.call(n,r)?++n[r]:n[r]=1}),ao=rr(zu),co=rr(Bu,true),lo=ir(Kn,zu),so=ir(function(n,t){for(var r=n.length;r--&&false!==t(n[r],r,n););return n},Bu),po=Vt(function(n,t,r){eu.call(n,r)?n[r].push(t):n[r]=[t]}),ho=Vt(function(n,t,r){
|
||||
n[r]=t}),_o=pe(function(n,t,r){var e=-1,u=typeof t=="function",o=Wr(t),i=Sr(n)?De(n.length):[];return zu(n,function(n){var f=u?t:o&&null!=n?n[t]:w;i[++e]=f?f.apply(n,r):Cr(n,t,r)}),i}),vo=Vt(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),go=pr(Qn,zu),yo=pr(function(n,t,r,e){var u=n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r},Bu),mo=pe(function(n,t){if(null==n)return[];var r=t[2];return r&&$r(t[0],t[1],r)&&(t.length=1),Wt(n,_t(t),[])}),wo=Ou||function(){return(new Me).getTime();
|
||||
},xo=pe(function(n,t,r){var e=b;if(r.length)var u=v(r,xo.placeholder),e=e|I;return dr(n,e,t,r,u)}),bo=pe(function(n,t){t=t.length?_t(t):Re(n);for(var r=-1,e=t.length;++r<e;){var u=t[r];n[u]=dr(n[u],b,n)}return n}),Ao=pe(function(n,t,r){var e=b|A;if(r.length)var u=v(r,Ao.placeholder),e=e|I;return dr(t,e,n,r,u)}),jo=Qt(k),ko=Qt(O),Oo=pe(function(n,t){return at(n,1,t)}),Io=pe(function(n,t,r){return at(n,t,r)}),Ro=or(),Eo=or(true),Co=pe(function(n,t){if(t=_t(t),typeof n!="function"||!Vn(t,e))throw new Xe(T);
|
||||
var r=t.length;return pe(function(e){for(var u=ku(e.length,r);u--;)e[u]=t[u](e[u]);return n.apply(this,e)})}),So=sr(I),Uo=sr(R),$o=pe(function(n,t){return dr(n,C,w,w,w,_t(t))}),Wo=xu||function(n){return h(n)&&Lr(n.length)&&ou.call(n)==B},Fo=Zt(kt),Lo=Zt(function(n,t,r){return r?rt(n,t,r):et(n,t)}),No=nr(Lo,function(n,t){return n===w?t:n}),To=nr(Fo,Nr),Po=ur(gt),zo=ur(yt),Bo=fr(Du),Do=fr(Mu),Mo=ar(gt),qo=ar(yt),Ko=Au?function(n){var t=null==n?w:n.constructor;return typeof t=="function"&&t.prototype===n||(typeof n=="function"?Nn.support.enumPrototypes:Sr(n))?zr(n):de(n)?Au(n):[];
|
||||
}:zr,Vo=cr(true),Zo=cr(),Yo=pe(function(n,t){if(null==n)return{};if("function"!=typeof t[0])return t=Xn(_t(t),Je),Tr(n,ct(Ee(n),t));var r=Dt(t[0],t[1],3);return Pr(n,function(n,t,e){return!r(n,t,e)})}),Go=pe(function(n,t){return null==n?{}:"function"==typeof t[0]?Pr(n,Dt(t[0],t[1],3)):Tr(n,_t(t))}),Jo=Xt(function(n,t,r){return t=t.toLowerCase(),n+(r?t.charAt(0).toUpperCase()+t.slice(1):t)}),Xo=Xt(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Ho=lr(),Qo=lr(true),ni=Xt(function(n,t,r){return n+(r?"_":"")+t.toLowerCase();
|
||||
}),ti=Xt(function(n,t,r){return n+(r?" ":"")+(t.charAt(0).toUpperCase()+t.slice(1))}),ri=pe(function(n,t){try{return n.apply(w,t)}catch(r){return ge(r)?r:new qe(r)}}),ei=pe(function(n,t){return function(r){return Cr(r,n,t)}}),ui=pe(function(n,t){return function(r){return Cr(n,r,t)}}),oi=gr("ceil"),ii=gr("floor"),fi=tr(he,Eu),ai=tr(ke,Cu),ci=gr("round");return Nn.prototype=Tn.prototype,Pn.prototype=Pu(Tn.prototype),Pn.prototype.constructor=Pn,zn.prototype=Pu(Tn.prototype),zn.prototype.constructor=zn,
|
||||
Bn.prototype["delete"]=function(n){return this.has(n)&&delete this.__data__[n]},Bn.prototype.get=function(n){return"__proto__"==n?w:this.__data__[n]},Bn.prototype.has=function(n){return"__proto__"!=n&&eu.call(this.__data__,n)},Bn.prototype.set=function(n,t){return"__proto__"!=n&&(this.__data__[n]=t),this},Dn.prototype.push=function(n){var t=this.data;typeof n=="string"||de(n)?t.set.add(n):t.hash[n]=true},se.Cache=Bn,Nn.after=function(n,t){if(typeof t!="function"){if(typeof n!="function")throw new Xe(T);
|
||||
var r=n;n=t,t=r}return n=bu(n=+n)?n:0,function(){return 1>--n?t.apply(this,arguments):void 0}},Nn.ary=function(n,t,r){return r&&$r(n,t,r)&&(t=w),t=n&&null==t?n.length:ju(+t||0,0),dr(n,E,w,w,w,w,t)},Nn.assign=Lo,Nn.at=io,Nn.before=ce,Nn.bind=xo,Nn.bindAll=bo,Nn.bindKey=Ao,Nn.callback=Le,Nn.chain=te,Nn.chunk=function(n,t,r){t=(r?$r(n,t,r):null==t)?1:ju(wu(t)||1,1),r=0;for(var e=n?n.length:0,u=-1,o=De(du(e/t));r<e;)o[++u]=St(n,r,r+=t);return o},Nn.compact=function(n){for(var t=-1,r=n?n.length:0,e=-1,u=[];++t<r;){
|
||||
var o=n[t];o&&(u[++e]=o)}return u},Nn.constant=function(n){return function(){return n}},Nn.countBy=fo,Nn.create=function(n,t,r){var e=Pu(n);return r&&$r(n,t,r)&&(t=w),t?et(e,t):e},Nn.curry=jo,Nn.curryRight=ko,Nn.debounce=le,Nn.defaults=No,Nn.defaultsDeep=To,Nn.defer=Oo,Nn.delay=Io,Nn.difference=Yu,Nn.drop=Kr,Nn.dropRight=Vr,Nn.dropRightWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3),true,true):[]},Nn.dropWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3),true):[]},Nn.fill=function(n,t,r,e){
|
||||
var u=n?n.length:0;if(!u)return[];for(r&&typeof r!="number"&&$r(n,t,r)&&(r=0,e=u),u=n.length,r=null==r?0:+r||0,0>r&&(r=-r>u?0:u+r),e=e===w||e>u?u:+e||0,0>e&&(e+=u),u=r>e?0:e>>>0,r>>>=0;r<u;)n[r++]=t;return n},Nn.filter=ue,Nn.flatten=function(n,t,r){var e=n?n.length:0;return r&&$r(n,t,r)&&(t=false),e?_t(n,t):[]},Nn.flattenDeep=function(n){return n&&n.length?_t(n,true):[]},Nn.flow=Ro,Nn.flowRight=Eo,Nn.forEach=lo,Nn.forEachRight=so,Nn.forIn=Bo,Nn.forInRight=Do,Nn.forOwn=Mo,Nn.forOwnRight=qo,Nn.functions=Re,
|
||||
Nn.groupBy=po,Nn.indexBy=ho,Nn.initial=function(n){return Vr(n,1)},Nn.intersection=Xu,Nn.invert=function(n,t,r){r&&$r(n,t,r)&&(t=w),r=-1;for(var e=Ko(n),u=e.length,o={};++r<u;){var i=e[r],f=n[i];t?eu.call(o,f)?o[f].push(i):o[f]=[i]:o[f]=i}return o},Nn.invoke=_o,Nn.keys=Ko,Nn.keysIn=Ee,Nn.map=ie,Nn.mapKeys=Vo,Nn.mapValues=Zo,Nn.matches=Te,Nn.matchesProperty=function(n,t){return jt(n,ft(t,true))},Nn.memoize=se,Nn.merge=Fo,Nn.method=ei,Nn.methodOf=ui,Nn.mixin=Pe,Nn.modArgs=Co,Nn.negate=function(n){if(typeof n!="function")throw new Xe(T);
|
||||
return function(){return!n.apply(this,arguments)}},Nn.omit=Yo,Nn.once=function(n){return ce(2,n)},Nn.pairs=Ce,Nn.partial=So,Nn.partialRight=Uo,Nn.partition=vo,Nn.pick=Go,Nn.pluck=function(n,t){return ie(n,Be(t))},Nn.property=Be,Nn.propertyOf=function(n){return function(t){return mt(n,Mr(t),t+"")}},Nn.pull=function(){var n=arguments,t=n[0];if(!t||!t.length)return t;for(var r=0,e=jr(),u=n.length;++r<u;)for(var o=0,i=n[r];-1<(o=e(t,i,o));)vu.call(t,o,1);return t},Nn.pullAt=Hu,Nn.range=function(n,t,r){
|
||||
r&&$r(n,t,r)&&(t=r=w),n=+n||0,r=null==r?1:+r||0,null==t?(t=n,n=0):t=+t||0;var e=-1;t=ju(du((t-n)/(r||1)),0);for(var u=De(t);++e<t;)u[e]=n,n+=r;return u},Nn.rearg=$o,Nn.reject=function(n,t,r){var e=Wo(n)?Zn:pt;return t=br(t,r,3),e(n,function(n,r,e){return!t(n,r,e)})},Nn.remove=function(n,t,r){var e=[];if(!n||!n.length)return e;var u=-1,o=[],i=n.length;for(t=br(t,r,3);++u<i;)r=n[u],t(r,u,n)&&(e.push(r),o.push(u));return Rt(n,o),e},Nn.rest=Jr,Nn.restParam=pe,Nn.set=function(n,t,r){if(null==n)return n;
|
||||
var e=t+"";t=null!=n[e]||Wr(t,n)?[e]:Mr(t);for(var e=-1,u=t.length,o=u-1,i=n;null!=i&&++e<u;){var f=t[e];de(i)&&(e==o?i[f]=r:null==i[f]&&(i[f]=Ur(t[e+1])?[]:{})),i=i[f]}return n},Nn.shuffle=function(n){return fe(n,Cu)},Nn.slice=function(n,t,r){var e=n?n.length:0;return e?(r&&typeof r!="number"&&$r(n,t,r)&&(t=0,r=e),St(n,t,r)):[]},Nn.sortBy=function(n,t,r){if(null==n)return[];r&&$r(n,t,r)&&(t=w);var e=-1;return t=br(t,r,3),n=bt(n,function(n,r,u){return{a:t(n,r,u),b:++e,c:n}}),$t(n,f)},Nn.sortByAll=mo,
|
||||
Nn.sortByOrder=function(n,t,r,e){return null==n?[]:(e&&$r(t,r,e)&&(r=w),Wo(t)||(t=null==t?[]:[t]),Wo(r)||(r=null==r?[]:[r]),Wt(n,t,r))},Nn.spread=function(n){if(typeof n!="function")throw new Xe(T);return function(t){return n.apply(this,t)}},Nn.take=function(n,t,r){return n&&n.length?((r?$r(n,t,r):null==t)&&(t=1),St(n,0,0>t?0:t)):[]},Nn.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?$r(n,t,r):null==t)&&(t=1),t=e-(+t||0),St(n,0>t?0:t)):[]},Nn.takeRightWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3),false,true):[];
|
||||
},Nn.takeWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3)):[]},Nn.tap=function(n,t,r){return t.call(r,n),n},Nn.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new Xe(T);return false===r?e=false:de(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),le(n,t,{leading:e,maxWait:+t,trailing:u})},Nn.thru=re,Nn.times=function(n,t,r){if(n=wu(n),1>n||!bu(n))return[];var e=-1,u=De(ku(n,4294967295));for(t=Dt(t,r,1);++e<n;)4294967295>e?u[e]=t(e):t(e);return u},Nn.toArray=Oe,
|
||||
Nn.toPlainObject=Ie,Nn.transform=function(n,t,r,e){var u=Wo(n)||je(n);return t=br(t,e,4),null==r&&(u||de(n)?(e=n.constructor,r=u?Wo(n)?new e:[]:Pu(ye(e)?e.prototype:w)):r={}),(u?Kn:gt)(n,function(n,e,u){return t(r,n,e,u)}),r},Nn.union=to,Nn.uniq=Xr,Nn.unzip=Hr,Nn.unzipWith=Qr,Nn.values=Se,Nn.valuesIn=function(n){return Nt(n,Ee(n))},Nn.where=function(n,t){return ue(n,At(t))},Nn.without=ro,Nn.wrap=function(n,t){return t=null==t?Ne:t,dr(t,I,w,[n],[])},Nn.xor=function(){for(var n=-1,t=arguments.length;++n<t;){
|
||||
var r=arguments[n];if(Sr(r))var e=e?Hn(ct(e,r),ct(r,e)):r}return e?Lt(e):[]},Nn.zip=eo,Nn.zipObject=ne,Nn.zipWith=uo,Nn.backflow=Eo,Nn.collect=ie,Nn.compose=Eo,Nn.each=lo,Nn.eachRight=so,Nn.extend=Lo,Nn.iteratee=Le,Nn.methods=Re,Nn.object=ne,Nn.select=ue,Nn.tail=Jr,Nn.unique=Xr,Pe(Nn,Nn),Nn.add=function(n,t){return(+n||0)+(+t||0)},Nn.attempt=ri,Nn.camelCase=Jo,Nn.capitalize=function(n){return(n=u(n))&&n.charAt(0).toUpperCase()+n.slice(1)},Nn.ceil=oi,Nn.clone=function(n,t,r,e){return t&&typeof t!="boolean"&&$r(n,t,r)?t=false:typeof t=="function"&&(e=r,
|
||||
r=t,t=false),typeof r=="function"?ft(n,t,Dt(r,e,3)):ft(n,t)},Nn.cloneDeep=function(n,t,r){return typeof t=="function"?ft(n,true,Dt(t,r,3)):ft(n,true)},Nn.deburr=Ue,Nn.endsWith=function(n,t,r){n=u(n),t+="";var e=n.length;return r=r===w?e:ku(0>r?0:+r||0,e),r-=t.length,0<=r&&n.indexOf(t,r)==r},Nn.escape=function(n){return(n=u(n))&&hn.test(n)?n.replace(sn,c):n},Nn.escapeRegExp=function(n){return(n=u(n))&&xn.test(n)?n.replace(wn,l):n||"(?:)"},Nn.every=ee,Nn.find=ao,Nn.findIndex=Gu,Nn.findKey=Po,Nn.findLast=co,
|
||||
Nn.findLastIndex=Ju,Nn.findLastKey=zo,Nn.findWhere=function(n,t){return ao(n,At(t))},Nn.first=Zr,Nn.floor=ii,Nn.get=function(n,t,r){return n=null==n?w:mt(n,Mr(t),t+""),n===w?r:n},Nn.gt=he,Nn.gte=function(n,t){return n>=t},Nn.has=function(n,t){if(null==n)return false;var r=eu.call(n,t);if(!r&&!Wr(t)){if(t=Mr(t),n=1==t.length?n:mt(n,St(t,0,-1)),null==n)return false;t=Gr(t),r=eu.call(n,t)}return r||Lr(n.length)&&Ur(t,n.length)&&(Wo(n)||_e(n)||Ae(n))},Nn.identity=Ne,Nn.includes=oe,Nn.indexOf=Yr,Nn.inRange=function(n,t,r){
|
||||
return t=+t||0,r===w?(r=t,t=0):r=+r||0,n>=ku(t,r)&&n<ju(t,r)},Nn.isArguments=_e,Nn.isArray=Wo,Nn.isBoolean=function(n){return true===n||false===n||h(n)&&ou.call(n)==D},Nn.isDate=function(n){return h(n)&&ou.call(n)==M},Nn.isElement=function(n){return!!n&&1===n.nodeType&&h(n)&&!xe(n)},Nn.isEmpty=function(n){return null==n?true:Sr(n)&&(Wo(n)||Ae(n)||_e(n)||h(n)&&ye(n.splice))?!n.length:!Ko(n).length},Nn.isEqual=ve,Nn.isError=ge,Nn.isFinite=function(n){return typeof n=="number"&&bu(n)},Nn.isFunction=ye,Nn.isMatch=function(n,t,r,e){
|
||||
return r=typeof r=="function"?Dt(r,e,3):w,xt(n,kr(t),r)},Nn.isNaN=function(n){return we(n)&&n!=+n},Nn.isNative=me,Nn.isNull=function(n){return null===n},Nn.isNumber=we,Nn.isObject=de,Nn.isPlainObject=xe,Nn.isRegExp=be,Nn.isString=Ae,Nn.isTypedArray=je,Nn.isUndefined=function(n){return n===w},Nn.kebabCase=Xo,Nn.last=Gr,Nn.lastIndexOf=function(n,t,r){var e=n?n.length:0;if(!e)return-1;var u=e;if(typeof r=="number")u=(0>r?ju(e+r,0):ku(r||0,e-1))+1;else if(r)return u=zt(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1;
|
||||
if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},Nn.lt=ke,Nn.lte=function(n,t){return n<=t},Nn.max=fi,Nn.min=ai,Nn.noConflict=function(){return Yn._=iu,this},Nn.noop=ze,Nn.now=wo,Nn.pad=function(n,t,r){n=u(n),t=+t;var e=n.length;return e<t&&bu(t)?(e=(t-e)/2,t=wu(e),e=du(e),r=_r("",e,r),r.slice(0,t)+n+r):n},Nn.padLeft=Ho,Nn.padRight=Qo,Nn.parseInt=function(n,t,r){return(r?$r(n,t,r):null==t)?t=0:t&&(t=+t),n=We(n),Iu(n,t||(On.test(n)?16:10))},Nn.random=function(n,t,r){r&&$r(n,t,r)&&(t=r=w);
|
||||
var e=null==n,u=null==t;return null==r&&(u&&typeof n=="boolean"?(r=n,n=1):typeof t=="boolean"&&(r=t,u=true)),e&&u&&(t=1,u=false),n=+n||0,u?(t=n,n=0):t=+t||0,r||n%1||t%1?(r=Ru(),ku(n+r*(t-n+lu("1e-"+((r+"").length-1))),t)):Et(n,t)},Nn.reduce=go,Nn.reduceRight=yo,Nn.repeat=$e,Nn.result=function(n,t,r){var e=null==n?w:Dr(n)[t];return e===w&&(null==n||Wr(t,n)||(t=Mr(t),n=1==t.length?n:mt(n,St(t,0,-1)),e=null==n?w:Dr(n)[Gr(t)]),e=e===w?r:e),ye(e)?e.call(n):e},Nn.round=ci,Nn.runInContext=m,Nn.size=function(n){
|
||||
var t=n?Vu(n):0;return Lr(t)?t:Ko(n).length},Nn.snakeCase=ni,Nn.some=ae,Nn.sortedIndex=Qu,Nn.sortedLastIndex=no,Nn.startCase=ti,Nn.startsWith=function(n,t,r){return n=u(n),r=null==r?0:ku(0>r?0:+r||0,n.length),n.lastIndexOf(t,r)==r},Nn.sum=function(n,t,r){if(r&&$r(n,t,r)&&(t=w),t=br(t,r,3),1==t.length){n=Wo(n)?n:Br(n),r=n.length;for(var e=0;r--;)e+=+t(n[r])||0;n=e}else n=Ft(n,t);return n},Nn.template=function(n,t,r){var e=Nn.templateSettings;r&&$r(n,t,r)&&(t=r=w),n=u(n),t=rt(et({},r||t),e,tt),r=rt(et({},t.imports),e.imports,tt);
|
||||
var o,i,f=Ko(r),a=Nt(r,f),c=0;r=t.interpolate||Cn;var l="__p+='";r=Ge((t.escape||Cn).source+"|"+r.source+"|"+(r===gn?jn:Cn).source+"|"+(t.evaluate||Cn).source+"|$","g");var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,u,f,a){return e||(e=u),l+=n.slice(c,a).replace(Sn,s),r&&(o=true,l+="'+__e("+r+")+'"),f&&(i=true,l+="';"+f+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=a+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(fn,""):l).replace(an,"$1").replace(cn,"$1;"),
|
||||
l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}",t=ri(function(){return Ke(f,p+"return "+l).apply(w,a)}),t.source=l,ge(t))throw t;return t},Nn.trim=We,Nn.trimLeft=function(n,t,r){var e=n;return(n=u(n))?n.slice((r?$r(e,t,r):null==t)?g(n):o(n,t+"")):n},Nn.trimRight=function(n,t,r){var e=n;return(n=u(n))?(r?$r(e,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,i(n,t+"")+1):n;
|
||||
},Nn.trunc=function(n,t,r){r&&$r(n,t,r)&&(t=w);var e=S;if(r=U,null!=t)if(de(t)){var o="separator"in t?t.separator:o,e="length"in t?+t.length||0:e;r="omission"in t?u(t.omission):r}else e=+t||0;if(n=u(n),e>=n.length)return n;if(e-=r.length,1>e)return r;if(t=n.slice(0,e),null==o)return t+r;if(be(o)){if(n.slice(e).search(o)){var i,f=n.slice(0,e);for(o.global||(o=Ge(o.source,(kn.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(f);)i=n.index;t=t.slice(0,null==i?e:i)}}else n.indexOf(o,e)!=e&&(o=t.lastIndexOf(o),
|
||||
-1<o&&(t=t.slice(0,o)));return t+r},Nn.unescape=function(n){return(n=u(n))&&pn.test(n)?n.replace(ln,d):n},Nn.uniqueId=function(n){var t=++uu;return u(n)+t},Nn.words=Fe,Nn.all=ee,Nn.any=ae,Nn.contains=oe,Nn.eq=ve,Nn.detect=ao,Nn.foldl=go,Nn.foldr=yo,Nn.head=Zr,Nn.include=oe,Nn.inject=go,Pe(Nn,function(){var n={};return gt(Nn,function(t,r){Nn.prototype[r]||(n[r]=t)}),n}(),false),Nn.sample=fe,Nn.prototype.sample=function(n){return this.__chain__||null!=n?this.thru(function(t){return fe(t,n)}):fe(this.value());
|
||||
},Nn.VERSION=x,Kn("bind bindKey curry curryRight partial partialRight".split(" "),function(n){Nn[n].placeholder=Nn}),Kn(["drop","take"],function(n,t){zn.prototype[n]=function(r){var e=this.__filtered__;if(e&&!t)return new zn(this);r=null==r?1:ju(wu(r)||0,0);var u=this.clone();return e?u.__takeCount__=ku(u.__takeCount__,r):u.__views__.push({size:r,type:n+(0>u.__dir__?"Right":"")}),u},zn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),Kn(["filter","map","takeWhile"],function(n,t){
|
||||
var r=t+1,e=r!=N;zn.prototype[n]=function(n,t){var u=this.clone();return u.__iteratees__.push({iteratee:br(n,t,1),type:r}),u.__filtered__=u.__filtered__||e,u}}),Kn(["first","last"],function(n,t){var r="take"+(t?"Right":"");zn.prototype[n]=function(){return this[r](1).value()[0]}}),Kn(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");zn.prototype[n]=function(){return this.__filtered__?new zn(this):this[r](1)}}),Kn(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?At:Be;zn.prototype[n]=function(n){
|
||||
return this[r](e(n))}}),zn.prototype.compact=function(){return this.filter(Ne)},zn.prototype.reject=function(n,t){return n=br(n,t,1),this.filter(function(t){return!n(t)})},zn.prototype.slice=function(n,t){n=null==n?0:+n||0;var r=this;return r.__filtered__&&(0<n||0>t)?new zn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==w&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r)},zn.prototype.takeRightWhile=function(n,t){return this.reverse().takeWhile(n,t).reverse()},zn.prototype.toArray=function(){return this.take(Cu);
|
||||
},gt(zn.prototype,function(n,t){var r=/^(?:filter|map|reject)|While$/.test(t),e=/^(?:first|last)$/.test(t),u=Nn[e?"take"+("last"==t?"Right":""):t];u&&(Nn.prototype[t]=function(){var t=e?[1]:arguments,o=this.__chain__,i=this.__wrapped__,f=!!this.__actions__.length,a=i instanceof zn,c=t[0],l=a||Wo(i);l&&r&&typeof c=="function"&&1!=c.length&&(a=l=false);var s=function(n){return e&&o?u(n,1)[0]:u.apply(w,Hn([n],t))},c={func:re,args:[s],thisArg:w},f=a&&!f;return e&&!o?f?(i=i.clone(),i.__actions__.push(c),
|
||||
n.call(i)):u.call(w,this.value())[0]:!e&&l?(i=f?i:new zn(this),i=n.apply(i,t),i.__actions__.push(c),new Pn(i,o)):this.thru(s)})}),Kn("join pop push replace shift sort splice split unshift".split(" "),function(n){var t=(/^(?:replace|split)$/.test(n)?tu:He)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=!Tu.spliceObjects&&/^(?:pop|shift|splice)$/.test(n),u=/^(?:join|pop|replace|shift)$/.test(n),o=e?function(){var n=t.apply(this,arguments);return 0===this.length&&delete this[0],n}:t;Nn.prototype[n]=function(){
|
||||
var n=arguments;return u&&!this.__chain__?o.apply(this.value(),n):this[r](function(t){return o.apply(t,n)})}}),gt(zn.prototype,function(n,t){var r=Nn[t];if(r){var e=r.name+"";(Fu[e]||(Fu[e]=[])).push({name:t,func:r})}}),Fu[hr(w,A).name]=[{name:"wrapper",func:w}],zn.prototype.clone=function(){var n=new zn(this.__wrapped__);return n.__actions__=qn(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=qn(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=qn(this.__views__),
|
||||
n},zn.prototype.reverse=function(){if(this.__filtered__){var n=new zn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},zn.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=Wo(t),u=0>r,o=e?t.length:0;n=0;for(var i=o,f=this.__views__,a=-1,c=f.length;++a<c;){var l=f[a],s=l.size;switch(l.type){case"drop":n+=s;break;case"dropRight":i-=s;break;case"take":i=ku(i,n+s);break;case"takeRight":n=ju(n,i-s)}}if(n={start:n,end:i},i=n.start,f=n.end,n=f-i,
|
||||
u=u?f:i-1,i=this.__iteratees__,f=i.length,a=0,c=ku(n,this.__takeCount__),!e||o<F||o==n&&c==n)return Pt(t,this.__actions__);e=[];n:for(;n--&&a<c;){for(u+=r,o=-1,l=t[u];++o<f;){var p=i[o],s=p.type,p=p.iteratee(l);if(s==N)l=p;else if(!p){if(s==L)continue n;break n}}e[a++]=l}return e},Nn.prototype.chain=function(){return te(this)},Nn.prototype.commit=function(){return new Pn(this.value(),this.__chain__)},Nn.prototype.concat=oo,Nn.prototype.plant=function(n){for(var t,r=this;r instanceof Tn;){var e=qr(r);
|
||||
t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},Nn.prototype.reverse=function(){var n=this.__wrapped__,t=function(n){return n.reverse()};return n instanceof zn?(this.__actions__.length&&(n=new zn(this)),n=n.reverse(),n.__actions__.push({func:re,args:[t],thisArg:w}),new Pn(n,this.__chain__)):this.thru(t)},Nn.prototype.toString=function(){return this.value()+""},Nn.prototype.run=Nn.prototype.toJSON=Nn.prototype.valueOf=Nn.prototype.value=function(){return Pt(this.__wrapped__,this.__actions__);
|
||||
},Nn.prototype.collect=Nn.prototype.map,Nn.prototype.head=Nn.prototype.first,Nn.prototype.select=Nn.prototype.filter,Nn.prototype.tail=Nn.prototype.rest,Nn}var w,x="3.10.1",b=1,A=2,j=4,k=8,O=16,I=32,R=64,E=128,C=256,S=30,U="...",$=150,W=16,F=200,L=1,N=2,T="Expected a function",P="__lodash_placeholder__",z="[object Arguments]",B="[object Array]",D="[object Boolean]",M="[object Date]",q="[object Error]",K="[object Function]",V="[object Number]",Z="[object Object]",Y="[object RegExp]",G="[object String]",J="[object ArrayBuffer]",X="[object Float32Array]",H="[object Float64Array]",Q="[object Int8Array]",nn="[object Int16Array]",tn="[object Int32Array]",rn="[object Uint8Array]",en="[object Uint8ClampedArray]",un="[object Uint16Array]",on="[object Uint32Array]",fn=/\b__p\+='';/g,an=/\b(__p\+=)''\+/g,cn=/(__e\(.*?\)|\b__t\))\+'';/g,ln=/&(?:amp|lt|gt|quot|#39|#96);/g,sn=/[&<>"'`]/g,pn=RegExp(ln.source),hn=RegExp(sn.source),_n=/<%-([\s\S]+?)%>/g,vn=/<%([\s\S]+?)%>/g,gn=/<%=([\s\S]+?)%>/g,yn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,dn=/^\w*$/,mn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,wn=/^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,xn=RegExp(wn.source),bn=/[\u0300-\u036f\ufe20-\ufe23]/g,An=/\\(\\)?/g,jn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,kn=/\w*$/,On=/^0[xX]/,In=/^\[object .+?Constructor\]$/,Rn=/^\d+$/,En=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Cn=/($^)/,Sn=/['\n\r\u2028\u2029\\]/g,Un=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),$n="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout isFinite parseFloat parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap".split(" "),Wn="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Fn={};
|
||||
Fn[X]=Fn[H]=Fn[Q]=Fn[nn]=Fn[tn]=Fn[rn]=Fn[en]=Fn[un]=Fn[on]=true,Fn[z]=Fn[B]=Fn[J]=Fn[D]=Fn[M]=Fn[q]=Fn[K]=Fn["[object Map]"]=Fn[V]=Fn[Z]=Fn[Y]=Fn["[object Set]"]=Fn[G]=Fn["[object WeakMap]"]=false;var Ln={};Ln[z]=Ln[B]=Ln[J]=Ln[D]=Ln[M]=Ln[X]=Ln[H]=Ln[Q]=Ln[nn]=Ln[tn]=Ln[V]=Ln[Z]=Ln[Y]=Ln[G]=Ln[rn]=Ln[en]=Ln[un]=Ln[on]=true,Ln[q]=Ln[K]=Ln["[object Map]"]=Ln["[object Set]"]=Ln["[object WeakMap]"]=false;var Nn={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a",
|
||||
"\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y",
|
||||
"\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Tn={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},Pn={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},zn={"function":true,object:true},Bn={0:"x30",1:"x31",2:"x32",3:"x33",4:"x34",5:"x35",6:"x36",7:"x37",8:"x38",9:"x39",A:"x41",B:"x42",C:"x43",D:"x44",E:"x45",F:"x46",a:"x61",b:"x62",c:"x63",d:"x64",e:"x65",f:"x66",n:"x6e",r:"x72",t:"x74",u:"x75",v:"x76",x:"x78"},Dn={"\\":"\\",
|
||||
"'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Mn=zn[typeof exports]&&exports&&!exports.nodeType&&exports,qn=zn[typeof module]&&module&&!module.nodeType&&module,Kn=zn[typeof self]&&self&&self.Object&&self,Vn=zn[typeof window]&&window&&window.Object&&window,Zn=qn&&qn.exports===Mn&&Mn,Yn=Mn&&qn&&typeof global=="object"&&global&&global.Object&&global||Vn!==(this&&this.window)&&Vn||Kn||this,Gn=function(){try{Object({toString:0}+"")}catch(n){return function(){return false}}return function(n){
|
||||
return typeof n.toString!="function"&&typeof(n+"")=="string"}}(),Jn=m();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Yn._=Jn, define(function(){return Jn})):Mn&&qn?Zn?(qn.exports=Jn)._=Jn:Mn._=Jn:Yn._=Jn}).call(this);
|
||||
1272
connexion/vendor/swagger-ui/lib/marked.js
vendored
@@ -1,23 +0,0 @@
|
||||
if (typeof Object.assign != 'function') {
|
||||
(function () {
|
||||
Object.assign = function (target) {
|
||||
'use strict';
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError('Cannot convert undefined or null to object');
|
||||
}
|
||||
|
||||
var output = Object(target);
|
||||
for (var index = 1; index < arguments.length; index++) {
|
||||
var source = arguments[index];
|
||||
if (source !== undefined && source !== null) {
|
||||
for (var nextKey in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, nextKey)) {
|
||||
output[nextKey] = source[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
})();
|
||||
}
|
||||
347
connexion/vendor/swagger-ui/lib/swagger-oauth.js
vendored
@@ -1,347 +0,0 @@
|
||||
var appName;
|
||||
var popupMask;
|
||||
var popupDialog;
|
||||
var clientId;
|
||||
var realm;
|
||||
var redirect_uri;
|
||||
var clientSecret;
|
||||
var scopeSeparator;
|
||||
var additionalQueryStringParams;
|
||||
|
||||
function handleLogin() {
|
||||
var scopes = [];
|
||||
|
||||
var auths = window.swaggerUi.api.authSchemes || window.swaggerUi.api.securityDefinitions;
|
||||
if(auths) {
|
||||
var key;
|
||||
var defs = auths;
|
||||
for(key in defs) {
|
||||
var auth = defs[key];
|
||||
if(auth.type === 'oauth2' && auth.scopes) {
|
||||
var scope;
|
||||
if(Array.isArray(auth.scopes)) {
|
||||
// 1.2 support
|
||||
var i;
|
||||
for(i = 0; i < auth.scopes.length; i++) {
|
||||
scopes.push(auth.scopes[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 2.0 support
|
||||
for(scope in auth.scopes) {
|
||||
scopes.push({scope: scope, description: auth.scopes[scope], OAuthSchemeKey: key});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(window.swaggerUi.api
|
||||
&& window.swaggerUi.api.info) {
|
||||
appName = window.swaggerUi.api.info.title;
|
||||
}
|
||||
|
||||
$('.api-popup-dialog').remove();
|
||||
popupDialog = $(
|
||||
[
|
||||
'<div class="api-popup-dialog">',
|
||||
'<div class="api-popup-title">Select OAuth2.0 Scopes</div>',
|
||||
'<div class="api-popup-content">',
|
||||
'<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.',
|
||||
'<a href="#">Learn how to use</a>',
|
||||
'</p>',
|
||||
'<p><strong>' + appName + '</strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>',
|
||||
'<ul class="api-popup-scopes">',
|
||||
'</ul>',
|
||||
'<p class="error-msg"></p>',
|
||||
'<div class="api-popup-actions"><button class="api-popup-authbtn api-button green" type="button">Authorize</button><button class="api-popup-cancel api-button gray" type="button">Cancel</button></div>',
|
||||
'</div>',
|
||||
'</div>'].join(''));
|
||||
$(document.body).append(popupDialog);
|
||||
|
||||
//TODO: only display applicable scopes (will need to pass them into handleLogin)
|
||||
popup = popupDialog.find('ul.api-popup-scopes').empty();
|
||||
for (i = 0; i < scopes.length; i ++) {
|
||||
scope = scopes[i];
|
||||
str = '<li><input type="checkbox" id="scope_' + i + '" scope="' + scope.scope + '"' +'" oauthtype="' + scope.OAuthSchemeKey +'"/>' + '<label for="scope_' + i + '">' + scope.scope ;
|
||||
if (scope.description) {
|
||||
if ($.map(auths, function(n, i) { return i; }).length > 1) //if we have more than one scheme, display schemes
|
||||
str += '<br/><span class="api-scope-desc">' + scope.description + ' ('+ scope.OAuthSchemeKey+')' +'</span>';
|
||||
else
|
||||
str += '<br/><span class="api-scope-desc">' + scope.description + '</span>';
|
||||
}
|
||||
str += '</label></li>';
|
||||
popup.append(str);
|
||||
}
|
||||
|
||||
var $win = $(window),
|
||||
dw = $win.width(),
|
||||
dh = $win.height(),
|
||||
st = $win.scrollTop(),
|
||||
dlgWd = popupDialog.outerWidth(),
|
||||
dlgHt = popupDialog.outerHeight(),
|
||||
top = (dh -dlgHt)/2 + st,
|
||||
left = (dw - dlgWd)/2;
|
||||
|
||||
popupDialog.css({
|
||||
top: (top < 0? 0 : top) + 'px',
|
||||
left: (left < 0? 0 : left) + 'px'
|
||||
});
|
||||
|
||||
popupDialog.find('button.api-popup-cancel').click(function() {
|
||||
popupMask.hide();
|
||||
popupDialog.hide();
|
||||
popupDialog.empty();
|
||||
popupDialog = [];
|
||||
});
|
||||
|
||||
$('button.api-popup-authbtn').unbind();
|
||||
popupDialog.find('button.api-popup-authbtn').click(function() {
|
||||
popupMask.hide();
|
||||
popupDialog.hide();
|
||||
|
||||
var authSchemes = window.swaggerUi.api.authSchemes;
|
||||
var host = window.location;
|
||||
var pathname = location.pathname.substring(0, location.pathname.lastIndexOf("/"));
|
||||
var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html';
|
||||
var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl;
|
||||
var url = null;
|
||||
var scopes = []
|
||||
var o = popup.find('input:checked');
|
||||
var OAuthSchemeKeys = [];
|
||||
var state;
|
||||
for(k =0; k < o.length; k++) {
|
||||
var scope = $(o[k]).attr('scope');
|
||||
if (scopes.indexOf(scope) === -1)
|
||||
scopes.push(scope);
|
||||
var OAuthSchemeKey = $(o[k]).attr('oauthtype');
|
||||
if (OAuthSchemeKeys.indexOf(OAuthSchemeKey) === -1)
|
||||
OAuthSchemeKeys.push(OAuthSchemeKey);
|
||||
}
|
||||
|
||||
//TODO: merge not replace if scheme is different from any existing
|
||||
//(needs to be aware of schemes to do so correctly)
|
||||
window.enabledScopes=scopes;
|
||||
|
||||
for (var key in authSchemes) {
|
||||
if (authSchemes.hasOwnProperty(key) && OAuthSchemeKeys.indexOf(key) != -1) { //only look at keys that match this scope.
|
||||
var flow = authSchemes[key].flow;
|
||||
|
||||
if(authSchemes[key].type === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) {
|
||||
var dets = authSchemes[key];
|
||||
url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code');
|
||||
window.swaggerUi.tokenName = dets.tokenName || 'access_token';
|
||||
window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null);
|
||||
state = key;
|
||||
}
|
||||
else if(authSchemes[key].type === 'oauth2' && flow && (flow === 'application')) {
|
||||
var dets = authSchemes[key];
|
||||
window.swaggerUi.tokenName = dets.tokenName || 'access_token';
|
||||
clientCredentialsFlow(scopes, dets.tokenUrl, key);
|
||||
return;
|
||||
}
|
||||
else if(authSchemes[key].grantTypes) {
|
||||
// 1.2 support
|
||||
var o = authSchemes[key].grantTypes;
|
||||
for(var t in o) {
|
||||
if(o.hasOwnProperty(t) && t === 'implicit') {
|
||||
var dets = o[t];
|
||||
var ep = dets.loginEndpoint.url;
|
||||
url = dets.loginEndpoint.url + '?response_type=token';
|
||||
window.swaggerUi.tokenName = dets.tokenName;
|
||||
}
|
||||
else if (o.hasOwnProperty(t) && t === 'accessCode') {
|
||||
var dets = o[t];
|
||||
var ep = dets.tokenRequestEndpoint.url;
|
||||
url = dets.tokenRequestEndpoint.url + '?response_type=code';
|
||||
window.swaggerUi.tokenName = dets.tokenName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redirect_uri = redirectUrl;
|
||||
|
||||
url += '&redirect_uri=' + encodeURIComponent(redirectUrl);
|
||||
url += '&realm=' + encodeURIComponent(realm);
|
||||
url += '&client_id=' + encodeURIComponent(clientId);
|
||||
url += '&scope=' + encodeURIComponent(scopes.join(scopeSeparator));
|
||||
url += '&state=' + encodeURIComponent(state);
|
||||
for (var key in additionalQueryStringParams) {
|
||||
url += '&' + key + '=' + encodeURIComponent(additionalQueryStringParams[key]);
|
||||
}
|
||||
|
||||
window.open(url);
|
||||
});
|
||||
|
||||
popupMask.show();
|
||||
popupDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
function handleLogout() {
|
||||
for(key in window.swaggerUi.api.clientAuthorizations.authz){
|
||||
window.swaggerUi.api.clientAuthorizations.remove(key)
|
||||
}
|
||||
window.enabledScopes = null;
|
||||
$('.api-ic.ic-on').addClass('ic-off');
|
||||
$('.api-ic.ic-on').removeClass('ic-on');
|
||||
|
||||
// set the info box
|
||||
$('.api-ic.ic-warning').addClass('ic-error');
|
||||
$('.api-ic.ic-warning').removeClass('ic-warning');
|
||||
}
|
||||
|
||||
function initOAuth(opts) {
|
||||
var o = (opts||{});
|
||||
var errors = [];
|
||||
|
||||
appName = (o.appName||errors.push('missing appName'));
|
||||
popupMask = (o.popupMask||$('#api-common-mask'));
|
||||
popupDialog = (o.popupDialog||$('.api-popup-dialog'));
|
||||
clientId = (o.clientId||errors.push('missing client id'));
|
||||
clientSecret = (o.clientSecret||null);
|
||||
realm = (o.realm||errors.push('missing realm'));
|
||||
scopeSeparator = (o.scopeSeparator||' ');
|
||||
additionalQueryStringParams = (o.additionalQueryStringParams||{});
|
||||
|
||||
if(errors.length > 0){
|
||||
log('auth unable initialize oauth: ' + errors);
|
||||
return;
|
||||
}
|
||||
|
||||
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
|
||||
$('.api-ic').unbind();
|
||||
$('.api-ic').click(function(s) {
|
||||
if($(s.target).hasClass('ic-off'))
|
||||
handleLogin();
|
||||
else {
|
||||
handleLogout();
|
||||
}
|
||||
false;
|
||||
});
|
||||
}
|
||||
|
||||
function clientCredentialsFlow(scopes, tokenUrl, OAuthSchemeKey) {
|
||||
var params = {
|
||||
'client_id': clientId,
|
||||
'client_secret': clientSecret,
|
||||
'scope': scopes.join(' '),
|
||||
'grant_type': 'client_credentials'
|
||||
}
|
||||
$.ajax(
|
||||
{
|
||||
url : tokenUrl,
|
||||
type: "POST",
|
||||
data: params,
|
||||
success:function(data, textStatus, jqXHR)
|
||||
{
|
||||
onOAuthComplete(data,OAuthSchemeKey);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown)
|
||||
{
|
||||
onOAuthComplete("");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
window.processOAuthCode = function processOAuthCode(data) {
|
||||
var OAuthSchemeKey = data.state;
|
||||
|
||||
// redirect_uri is required in auth code flow
|
||||
// see https://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1.3
|
||||
var host = window.location;
|
||||
var pathname = location.pathname.substring(0, location.pathname.lastIndexOf("/"));
|
||||
var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html';
|
||||
var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl;
|
||||
|
||||
var params = {
|
||||
'client_id': clientId,
|
||||
'code': data.code,
|
||||
'grant_type': 'authorization_code',
|
||||
'redirect_uri': redirectUrl
|
||||
};
|
||||
|
||||
if (clientSecret) {
|
||||
params.client_secret = clientSecret;
|
||||
}
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
url : window.swaggerUi.tokenUrl,
|
||||
type: "POST",
|
||||
data: params,
|
||||
success:function(data, textStatus, jqXHR)
|
||||
{
|
||||
onOAuthComplete(data, OAuthSchemeKey);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown)
|
||||
{
|
||||
onOAuthComplete("");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) {
|
||||
if(token) {
|
||||
if(token.error) {
|
||||
var checkbox = $('input[type=checkbox],.secured')
|
||||
checkbox.each(function(pos){
|
||||
checkbox[pos].checked = false;
|
||||
});
|
||||
alert(token.error);
|
||||
}
|
||||
else {
|
||||
var b = token[window.swaggerUi.tokenName];
|
||||
if (!OAuthSchemeKey){
|
||||
OAuthSchemeKey = token.state;
|
||||
}
|
||||
if(b){
|
||||
// if all roles are satisfied
|
||||
var o = null;
|
||||
$.each($('.auth .api-ic .api_information_panel'), function(k, v) {
|
||||
var children = v;
|
||||
if(children && children.childNodes) {
|
||||
var requiredScopes = [];
|
||||
$.each((children.childNodes), function (k1, v1){
|
||||
var inner = v1.innerHTML;
|
||||
if(inner)
|
||||
requiredScopes.push(inner);
|
||||
});
|
||||
var diff = [];
|
||||
for(var i=0; i < requiredScopes.length; i++) {
|
||||
var s = requiredScopes[i];
|
||||
if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) {
|
||||
diff.push(s);
|
||||
}
|
||||
}
|
||||
if(diff.length > 0){
|
||||
o = v.parentNode.parentNode;
|
||||
$(o.parentNode).find('.api-ic.ic-on').addClass('ic-off');
|
||||
$(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on');
|
||||
|
||||
// sorry, not all scopes are satisfied
|
||||
$(o).find('.api-ic').addClass('ic-warning');
|
||||
$(o).find('.api-ic').removeClass('ic-error');
|
||||
}
|
||||
else {
|
||||
o = v.parentNode.parentNode;
|
||||
$(o.parentNode).find('.api-ic.ic-off').addClass('ic-on');
|
||||
$(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off');
|
||||
|
||||
// all scopes are satisfied
|
||||
$(o).find('.api-ic').addClass('ic-info');
|
||||
$(o).find('.api-ic').removeClass('ic-warning');
|
||||
$(o).find('.api-ic').removeClass('ic-error');
|
||||
}
|
||||
}
|
||||
});
|
||||
window.swaggerUi.api.clientAuthorizations.add(window.OAuthSchemeKey, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header'));
|
||||
window.swaggerUi.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
20
connexion/vendor/swagger-ui/o2c.html
vendored
@@ -1,20 +0,0 @@
|
||||
<script>
|
||||
var qp = null;
|
||||
if(window.location.hash) {
|
||||
qp = location.hash.substring(1);
|
||||
}
|
||||
else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
qp = qp ? JSON.parse('{"' + qp.replace(/&/g, '","').replace(/=/g,'":"') + '"}',
|
||||
function(key, value) {
|
||||
return key===""?value:decodeURIComponent(value) }
|
||||
):{}
|
||||
|
||||
if (window.opener.swaggerUi.tokenUrl)
|
||||
window.opener.processOAuthCode(qp);
|
||||
else
|
||||
window.opener.onOAuthComplete(qp);
|
||||
|
||||
window.close();
|
||||
</script>
|
||||
24993
connexion/vendor/swagger-ui/swagger-ui.js
vendored
14
connexion/vendor/swagger-ui/swagger-ui.min.js
vendored
20
connexion/vendor/swagger-ui/update.sh
vendored
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo 'Usage: ./update.sh <SWAGGER-UI-VERSION>'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$1
|
||||
archive="https://github.com/swagger-api/swagger-ui/archive/v${version}.tar.gz"
|
||||
|
||||
folder="swagger-ui-${version}"
|
||||
rm -fr "$folder"
|
||||
mkdir -p "$folder"
|
||||
wget $archive -O - | tar -xvz -C "$folder" --strip-components=1
|
||||
rsync -av "$folder/dist/" ./
|
||||
|
||||
# apply our patch
|
||||
patch index.html index.html.patch
|
||||
|
||||
rm -fr "$folder"
|
||||
@@ -65,6 +65,5 @@ This scope indicates the OAuth 2 Server did not generate a token with all the sc
|
||||
contains 3 properties
|
||||
- ``required_scopes`` - The scopes that were required for this endpoint
|
||||
- ``token_scopes`` - The scopes that were granted for this endpoint
|
||||
- ``missing_scopes`` - The scopes that were not given by the OAuth 2 server that are required to access this endpoint
|
||||
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ change the validation, you can override the defaults with:
|
||||
'body': CustomRequestBodyValidator,
|
||||
'parameter': CustomParameterValidator
|
||||
}
|
||||
app = connexion.FlaskApp(__name__, ..., validator_map=validator_map)
|
||||
app = connexion.FlaskApp(__name__)
|
||||
app.add_api('api.yaml', ..., validator_map=validator_map)
|
||||
|
||||
See custom validator example in ``examples/enforcedefaults``.
|
||||
|
||||
@@ -79,7 +79,8 @@ the validation, you can override the default class with:
|
||||
validator_map = {
|
||||
'response': CustomResponseValidator
|
||||
}
|
||||
app = connexion.FlaskApp(__name__, ..., validator_map=validator_map)
|
||||
app = connexion.FlaskApp(__name__)
|
||||
app.add_api('api.yaml', ..., validator_map=validator_map)
|
||||
|
||||
|
||||
Error Handling
|
||||
|
||||
@@ -4,35 +4,71 @@ Security
|
||||
OAuth 2 Authentication and Authorization
|
||||
----------------------------------------
|
||||
|
||||
Connexion supports one of the three OAuth 2 handling methods. (See
|
||||
"TODO" below.) With Connexion, the API security definition **must**
|
||||
include a 'x-tokenInfoUrl' or 'x-tokenInfoFunc (or set ``TOKENINFO_URL``
|
||||
or ``TOKENINFO_FUNC`` env var respectively).
|
||||
Connexion supports one of the three OAuth 2 handling methods.
|
||||
With Connexion, the API security definition **must** include a
|
||||
``x-tokenInfoFunc`` or set ``TOKENINFO_FUNC`` env var.
|
||||
|
||||
If 'x-tokenInfoFunc' is used, it must contain a reference to a function
|
||||
``x-tokenInfoFunc`` must contain a reference to a function
|
||||
used to obtain the token info. This reference should be a string using
|
||||
the same syntax that is used to connect an ``operationId`` to a Python
|
||||
function when routing. For example, an ``x-tokenInfoFunc`` of
|
||||
``auth.verifyToken`` would pass the user's token string to the function
|
||||
``verifyToken`` in the module ``auth.py``. The referenced function should
|
||||
return a dict containing a ``scope`` or ``scopes`` field that is either
|
||||
a space-separated list or an array of scopes belonging to the supplied
|
||||
token. This list of scopes will be validated against the scopes required
|
||||
by the API security definition to determine if the user is authorized.
|
||||
``verifyToken`` in the module ``auth.py``. The referenced function accepts
|
||||
a token string as argument and should return a dict containing a ``scope``
|
||||
field that is either a space-separated list or an array of scopes belonging to
|
||||
the supplied token. This list of scopes will be validated against the scopes
|
||||
required by the API security definition to determine if the user is authorized.
|
||||
You can supply a custom scope validation func with ``x-scopeValidateFunc``
|
||||
or set ``SCOPEVALIDATE_FUNC`` env var, otherwise
|
||||
``connexion.decorators.security.validate_scope`` will be used as default.
|
||||
|
||||
If 'x-tokenInfoUrl' is used, it must contain a URL to validate and get
|
||||
the token information which complies with `RFC 6749 <rfc6749_>`_.
|
||||
|
||||
When both 'x-tokenInfoUrl' and 'x-tokenInfoFunc' are used, Connexion
|
||||
will prioritize the function method. Connexion expects to receive the
|
||||
OAuth token in the ``Authorization`` header field in the format
|
||||
described in `RFC 6750 <rfc6750_>`_ section 2.1. This aspect represents
|
||||
a significant difference from the usual OAuth flow.
|
||||
The recommended approach is to return a dict which complies with
|
||||
`RFC 7662 <rfc7662_>`_. Note that you have to validate the ``active``
|
||||
or ``exp`` fields etc. yourself.
|
||||
|
||||
The ``uid`` property (username) of the Token Info response will be passed in the ``user`` argument to the handler function.
|
||||
The ``sub`` property of the Token Info response will be passed in the ``user``
|
||||
argument to the handler function.
|
||||
|
||||
Deprecated features, retained for backward compability:
|
||||
|
||||
- As alternative to ``x-tokenInfoFunc``, you can set ``x-tokenInfoUrl`` or
|
||||
``TOKENINFO_URL`` env var. It must contain a URL to validate and get the token
|
||||
information which complies with `RFC 6749 <rfc6749_>`_.
|
||||
When both ``x-tokenInfoUrl`` and ``x-tokenInfoFunc`` are used, Connexion
|
||||
will prioritize the function method. Connexion expects the authorization
|
||||
server to receive the OAuth token in the ``Authorization`` header field in the
|
||||
format described in `RFC 6750 <rfc6750_>`_ section 2.1. This aspect represents
|
||||
a significant difference from the usual OAuth flow.
|
||||
- ``scope`` field can also be named ``scopes``.
|
||||
- ``sub`` field can also be named ``uid``.
|
||||
|
||||
You can find a `minimal OAuth example application`_ in Connexion's "examples" folder.
|
||||
|
||||
|
||||
Basic Authentication
|
||||
--------------------
|
||||
|
||||
With Connexion, the API security definition **must** include a
|
||||
``x-basicInfoFunc`` or set ``BASICINFO_FUNC`` env var. It uses the same
|
||||
semantics as for ``x-tokenInfoFunc``, but the function accepts three
|
||||
parameters: username, password and required_scopes. If the security declaration
|
||||
of the operation also has an oauth security requirement, required_scopes is
|
||||
taken from there, otherwise it's None. This allows authorizing individual
|
||||
operations with oauth scope while using basic authentication for
|
||||
authentication.
|
||||
|
||||
ApiKey Authentication
|
||||
---------------------
|
||||
|
||||
With Connexion, the API security definition **must** include a
|
||||
``x-apikeyInfoFunc`` or set ``APIKEYINFO_FUNC`` env var. It uses the same
|
||||
semantics as for ``x-basicInfoFunc``, but the function accepts two
|
||||
parameters: apikey and required_scopes.
|
||||
|
||||
You can find a `minimal Basic Auth example application`_ in Connexion's "examples" folder.
|
||||
|
||||
|
||||
HTTPS Support
|
||||
-------------
|
||||
|
||||
@@ -43,7 +79,7 @@ Swagger UI cannot be used to play with the API. What is the correct
|
||||
way to start a HTTPS server when using Connexion?
|
||||
|
||||
.. _rfc6750: https://tools.ietf.org/html/rfc6750
|
||||
.. _swager.spec.security_definition: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object
|
||||
.. _swager.spec.security_requirement: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-requirement-object
|
||||
.. _rfc6749: https://tools.ietf.org/html/rfc6749
|
||||
.. _minimal OAuth example application: https://github.com/zalando/connexion/tree/master/examples/oauth2
|
||||
.. _rfc7662: https://tools.ietf.org/html/rfc7662
|
||||
.. _minimal OAuth example application: https://github.com/zalando/connexion/tree/master/examples/swagger2/oauth2
|
||||
.. _minimal Basic Auth example application: https://github.com/zalando/connexion/tree/master/examples/swagger2/basicauth
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
=======================
|
||||
HTTP Basic Auth Example
|
||||
=======================
|
||||
|
||||
Running:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo pip3 install --upgrade connexion # install Connexion from PyPI
|
||||
$ ./app.py
|
||||
|
||||
Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI.
|
||||
|
||||
The hardcoded credentials are ``admin`` and ``secret``.
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Connexion HTTP Basic Auth example
|
||||
|
||||
Most of the code stolen from http://flask.pocoo.org/snippets/8/
|
||||
|
||||
Warning: It is recommended to use 'decorator' package to create decorators for
|
||||
your view functions to keep Connexion working as expected. For more
|
||||
details please check: https://github.com/zalando/connexion/issues/142
|
||||
'''
|
||||
|
||||
import connexion
|
||||
import flask
|
||||
|
||||
try:
|
||||
from decorator import decorator
|
||||
except ImportError:
|
||||
import sys
|
||||
import logging
|
||||
logging.error('Missing dependency. Please run `pip install decorator`')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_auth(username: str, password: str):
|
||||
'''This function is called to check if a username /
|
||||
password combination is valid.'''
|
||||
return username == 'admin' and password == 'secret'
|
||||
|
||||
|
||||
def authenticate():
|
||||
'''Sends a 401 response that enables basic auth'''
|
||||
return flask.Response('You have to login with proper credentials', 401,
|
||||
{'WWW-Authenticate': 'Basic realm="Login Required"'})
|
||||
|
||||
|
||||
@decorator
|
||||
def requires_auth(f: callable, *args, **kwargs):
|
||||
auth = flask.request.authorization
|
||||
if not auth or not check_auth(auth.username, auth.password):
|
||||
return authenticate()
|
||||
return f(*args, **kwargs)
|
||||
|
||||
|
||||
@requires_auth
|
||||
def get_secret() -> str:
|
||||
return 'This is a very secret string requiring authentication!'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = connexion.FlaskApp(__name__)
|
||||
app.add_api('swagger.yaml')
|
||||
app.run(port=8080)
|
||||
20
examples/openapi3/basicauth/README.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
=======================
|
||||
HTTP Basic Auth Example
|
||||
=======================
|
||||
|
||||
Running:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo pip3 install --upgrade connexion[swagger-ui] # install Connexion from PyPI
|
||||
$ ./app.py
|
||||
|
||||
Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI.
|
||||
|
||||
The hardcoded credentials are ``admin`` and ``secret``. For an example with
|
||||
correct authentication but missing access rights, use ``foo`` and ``bar``.
|
||||
|
||||
For a more simple example which doesn't use oauth scope for authorization see
|
||||
the `Swagger2 Basic Auth example`_.
|
||||
|
||||
.. _Swagger2 Basic Auth example: https://github.com/zalando/connexion/tree/master/examples/swagger2/basicauth
|
||||
42
examples/openapi3/basicauth/app.py
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Basic example of a resource server
|
||||
'''
|
||||
|
||||
import connexion
|
||||
from connexion.decorators.security import validate_scope
|
||||
from connexion.exceptions import OAuthScopeProblem
|
||||
|
||||
|
||||
def basic_auth(username, password, required_scopes=None):
|
||||
if username == 'admin' and password == 'secret':
|
||||
info = {'sub': 'admin', 'scope': 'secret'}
|
||||
elif username == 'foo' and password == 'bar':
|
||||
info = {'sub': 'user1', 'scope': ''}
|
||||
else:
|
||||
# optional: raise exception for custom error response
|
||||
return None
|
||||
|
||||
# optional
|
||||
if required_scopes is not None and not validate_scope(required_scopes, info['scope']):
|
||||
raise OAuthScopeProblem(
|
||||
description='Provided user doesn\'t have the required access rights',
|
||||
required_scopes=required_scopes,
|
||||
token_scopes=info['scope']
|
||||
)
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def dummy_func(token):
|
||||
return None
|
||||
|
||||
|
||||
def get_secret(user) -> str:
|
||||
return "You are {user} and the secret is 'wbevuec'".format(user=user)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = connexion.FlaskApp(__name__)
|
||||
app.add_api('openapi.yaml')
|
||||
app.run(port=8080)
|
||||
33
examples/openapi3/basicauth/openapi.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: Basic Auth Example
|
||||
version: '1.0'
|
||||
paths:
|
||||
/secret:
|
||||
get:
|
||||
summary: Return secret string
|
||||
operationId: app.get_secret
|
||||
responses:
|
||||
'200':
|
||||
description: secret response
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- oauth2: ['secret']
|
||||
- basic: []
|
||||
components:
|
||||
securitySchemes:
|
||||
oauth2:
|
||||
type: oauth2
|
||||
x-tokenInfoFunc: app.dummy_func
|
||||
flows:
|
||||
implicit:
|
||||
authorizationUrl: https://example.com/oauth2/dialog
|
||||
scopes:
|
||||
secret: Allow accessing secret
|
||||
basic:
|
||||
type: http
|
||||
scheme: basic
|
||||
x-basicInfoFunc: app.basic_auth
|
||||
12
examples/openapi3/helloworld/hello.py
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import connexion
|
||||
|
||||
|
||||
def post_greeting(name: str) -> str:
|
||||
return 'Hello {name}'.format(name=name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = connexion.FlaskApp(__name__, port=9090, specification_dir='openapi/')
|
||||
app.add_api('helloworld-api.yaml', arguments={'title': 'Hello World Example'})
|
||||
app.run()
|
||||
30
examples/openapi3/helloworld/openapi/helloworld-api.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
openapi: "3.0.0"
|
||||
|
||||
info:
|
||||
title: Hello World
|
||||
version: "1.0"
|
||||
servers:
|
||||
- url: http://localhost:9090/v1.0
|
||||
|
||||
paths:
|
||||
/greeting/{name}:
|
||||
post:
|
||||
summary: Generate greeting
|
||||
description: Generates a greeting message.
|
||||
operationId: hello.post_greeting
|
||||
responses:
|
||||
200:
|
||||
description: greeting response
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
example: "hello dave!"
|
||||
parameters:
|
||||
- name: name
|
||||
in: path
|
||||
description: Name of the person to greet.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: "dave"
|
||||
51
examples/openapi3/restyresolver/api/pets.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import datetime
|
||||
|
||||
from connexion import NoContent
|
||||
|
||||
pets = {}
|
||||
|
||||
|
||||
def post(body):
|
||||
name = body.get("name")
|
||||
tag = body.get("tag")
|
||||
count = len(pets)
|
||||
pet = {}
|
||||
pet['id'] = count + 1
|
||||
pet["tag"] = tag
|
||||
pet["name"] = name
|
||||
pet['last_updated'] = datetime.datetime.now()
|
||||
pets[pet['id']] = pet
|
||||
return pet, 201
|
||||
|
||||
|
||||
def put(body):
|
||||
id_ = body["id"]
|
||||
name = body["name"]
|
||||
tag = body.get("tag")
|
||||
id_ = int(id_)
|
||||
pet = pets.get(id_, {"id": id_})
|
||||
pet["name"] = name
|
||||
pet["tag"] = tag
|
||||
pet['last_updated'] = datetime.datetime.now()
|
||||
pets[id_] = pet
|
||||
return pets[id_]
|
||||
|
||||
|
||||
def delete(id_):
|
||||
id_ = int(id_)
|
||||
if pets.get(id_) is None:
|
||||
return NoContent, 404
|
||||
del pets[id_]
|
||||
return NoContent, 204
|
||||
|
||||
|
||||
def get(petId):
|
||||
id_ = int(petId)
|
||||
if pets.get(id_) is None:
|
||||
return NoContent, 404
|
||||
return pets[id_]
|
||||
|
||||
|
||||
def search(limit=100):
|
||||
# NOTE: we need to wrap it with list for Python 3 as dict_values is not JSON serializable
|
||||
return list(pets.values())[0:limit]
|
||||
148
examples/openapi3/restyresolver/resty-api.yaml
Normal file
@@ -0,0 +1,148 @@
|
||||
openapi: "3.0.0"
|
||||
info:
|
||||
version: 1.0.0
|
||||
title: Swagger Petstore
|
||||
license:
|
||||
name: MIT
|
||||
servers:
|
||||
- url: http://localhost:9090/v1.0
|
||||
paths:
|
||||
/pets:
|
||||
get:
|
||||
summary: List all pets
|
||||
tags:
|
||||
- pets
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
description: How many items to return at one time (max 100)
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
responses:
|
||||
'200':
|
||||
description: An paged array of pets
|
||||
headers:
|
||||
x-next:
|
||||
description: A link to the next page of responses
|
||||
schema:
|
||||
type: string
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Pets"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
post:
|
||||
summary: Create a pet
|
||||
tags:
|
||||
- pets
|
||||
requestBody:
|
||||
description: Pet to add to the system
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Pet"
|
||||
responses:
|
||||
'201':
|
||||
description: Null response
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
put:
|
||||
summary: Update a pet
|
||||
tags:
|
||||
- pets
|
||||
requestBody:
|
||||
description: Pet to add to the system
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/Pet"
|
||||
- type: object
|
||||
required:
|
||||
- id
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
example:
|
||||
id: 1
|
||||
name: chester
|
||||
tag: sleepy
|
||||
responses:
|
||||
'201':
|
||||
description: Null response
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
|
||||
/pets/{petId}:
|
||||
get:
|
||||
summary: Info for a specific pet
|
||||
tags:
|
||||
- pets
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
required: true
|
||||
description: The id of the pet to retrieve
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Expected response to a valid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Pets"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
components:
|
||||
schemas:
|
||||
Pet:
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
id:
|
||||
readOnly: true
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
example:
|
||||
name: chester
|
||||
tag: fluffy
|
||||
Pets:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Pet"
|
||||
Error:
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
format: int32
|
||||
message:
|
||||
type: string
|
||||
64
examples/openapi3/sqlalchemy/app.py
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import connexion
|
||||
from connexion import NoContent
|
||||
|
||||
import orm
|
||||
|
||||
db_session = None
|
||||
|
||||
|
||||
def get_pets(limit, animal_type=None):
|
||||
q = db_session.query(orm.Pet)
|
||||
if animal_type:
|
||||
q = q.filter(orm.Pet.animal_type == animal_type)
|
||||
return [p.dump() for p in q][:limit]
|
||||
|
||||
|
||||
def get_pet(pet_id):
|
||||
pet = db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).one_or_none()
|
||||
return pet.dump() if pet is not None else ('Not found', 404)
|
||||
|
||||
|
||||
def put_pet(pet_id, pet):
|
||||
p = db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).one_or_none()
|
||||
pet['id'] = pet_id
|
||||
if p is not None:
|
||||
logging.info('Updating pet %s..', pet_id)
|
||||
p.update(**pet)
|
||||
else:
|
||||
logging.info('Creating pet %s..', pet_id)
|
||||
pet['created'] = datetime.datetime.utcnow()
|
||||
db_session.add(orm.Pet(**pet))
|
||||
db_session.commit()
|
||||
return NoContent, (200 if p is not None else 201)
|
||||
|
||||
|
||||
def delete_pet(pet_id):
|
||||
pet = db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).one_or_none()
|
||||
if pet is not None:
|
||||
logging.info('Deleting pet %s..', pet_id)
|
||||
db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).delete()
|
||||
db_session.commit()
|
||||
return NoContent, 204
|
||||
else:
|
||||
return NoContent, 404
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
db_session = orm.init_db('sqlite:///:memory:')
|
||||
app = connexion.FlaskApp(__name__)
|
||||
app.add_api('openapi.yaml')
|
||||
|
||||
application = app.app
|
||||
|
||||
|
||||
@application.teardown_appcontext
|
||||
def shutdown_session(exception=None):
|
||||
db_session.remove()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=8081, use_reloader=False, threaded=False)
|
||||
120
examples/openapi3/sqlalchemy/openapi.yaml
Normal file
@@ -0,0 +1,120 @@
|
||||
openapi: 3.0.0
|
||||
servers:
|
||||
- url: http://localhost:8081/
|
||||
info:
|
||||
title: Pet Shop Example API
|
||||
version: '0.1'
|
||||
paths:
|
||||
/pets:
|
||||
get:
|
||||
tags:
|
||||
- Pets
|
||||
operationId: app.get_pets
|
||||
summary: Get all pets
|
||||
parameters:
|
||||
- name: animal_type
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
pattern: '^[a-zA-Z0-9]*$'
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 100
|
||||
responses:
|
||||
'200':
|
||||
description: Return pets
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'/pets/{pet_id}':
|
||||
get:
|
||||
tags:
|
||||
- Pets
|
||||
operationId: app.get_pet
|
||||
summary: Get a single pet
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/pet_id'
|
||||
responses:
|
||||
'200':
|
||||
description: Return pet
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'404':
|
||||
description: Pet does not exist
|
||||
put:
|
||||
tags:
|
||||
- Pets
|
||||
operationId: app.put_pet
|
||||
summary: Create or update a pet
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/pet_id'
|
||||
responses:
|
||||
'200':
|
||||
description: Pet updated
|
||||
'201':
|
||||
description: New pet created
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
x-body-name: pet
|
||||
$ref: '#/components/schemas/Pet'
|
||||
delete:
|
||||
tags:
|
||||
- Pets
|
||||
operationId: app.delete_pet
|
||||
summary: Remove a pet
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/pet_id'
|
||||
responses:
|
||||
'204':
|
||||
description: Pet was deleted
|
||||
'404':
|
||||
description: Pet does not exist
|
||||
components:
|
||||
parameters:
|
||||
pet_id:
|
||||
name: pet_id
|
||||
description: Pet's Unique identifier
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
pattern: '^[a-zA-Z0-9-]+$'
|
||||
schemas:
|
||||
Pet:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- animal_type
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Unique identifier
|
||||
example: '123'
|
||||
readOnly: true
|
||||
name:
|
||||
type: string
|
||||
description: Pet's name
|
||||
example: Susie
|
||||
minLength: 1
|
||||
maxLength: 100
|
||||
animal_type:
|
||||
type: string
|
||||
description: Kind of animal
|
||||
example: cat
|
||||
minLength: 1
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Creation time
|
||||
example: '2015-07-07T15:49:51.230+02:00'
|
||||
readOnly: true
|
||||