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
This commit is contained in:
João Santos
2018-11-05 14:50:42 +01:00
committed by GitHub
parent 08faf2aa86
commit 44ea9336fe
160 changed files with 4561 additions and 39687 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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):
"""

View File

@@ -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):

View File

@@ -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,

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View File

@@ -0,0 +1,4 @@
FORM_CONTENT_TYPES = [
'application/x-www-form-urlencoded',
'multipart/form-data'
]

109
connexion/json_schema.py Normal file
View 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})

View File

@@ -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

View File

@@ -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)

View 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

View 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)

View 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"])

View 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())

View 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"])

View File

@@ -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

View File

@@ -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')

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -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');
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

View File

@@ -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>&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body>
</html>

View File

@@ -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){

View File

@@ -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"
});

View File

@@ -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"
});

View File

@@ -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ó"
});

View File

@@ -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"
});

View File

@@ -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":"სერვერმა დააბრუნა"
});

View File

@@ -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"
});

View File

@@ -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":"サーバからの返答"
});

View File

@@ -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":"서버 응답함."
});

View File

@@ -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ł"
});

View File

@@ -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"
});

View File

@@ -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":"сервер сказал"
});

View File

@@ -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ü"
});

View File

@@ -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;
}
};

View File

@@ -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":"服务器返回"
});

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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);
}
};
})();

File diff suppressed because one or more lines are too long

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();}});}});};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"},Pn={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#96;":"`"},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);

File diff suppressed because it is too large Load Diff

View File

@@ -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;
};
})();
}

File diff suppressed because one or more lines are too long

View File

@@ -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();
}
}
}
};

View File

@@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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"

View File

@@ -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

View File

@@ -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``.

View File

@@ -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

View File

@@ -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

View File

@@ -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``.

View File

@@ -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)

View 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

View 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)

View 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

View 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()

View 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"

View 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]

View 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

View 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)

View 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

Some files were not shown because too many files have changed in this diff Show More