From 2c4f950780f06caf231d1e2a9a3b97ff9c887f61 Mon Sep 17 00:00:00 2001 From: Joel Dunham Date: Mon, 18 Jun 2018 14:01:08 -0700 Subject: [PATCH] Add Beta REST API: OpenAPI and autogenerated client --- requirements/base.txt | 4 +- .../locations/api/beta/__init__.py | 112 ++ .../locations/api/beta/remple/README.rst | 14 + .../locations/api/beta/remple/__init__.py | 60 + .../api/beta/remple/clientbuilder.py | 740 ++++++++ .../locations/api/beta/remple/constants.py | 92 + .../locations/api/beta/remple/openapi.py | 1573 +++++++++++++++++ .../locations/api/beta/remple/querybuilder.py | 681 +++++++ .../locations/api/beta/remple/resources.py | 866 +++++++++ .../locations/api/beta/remple/routebuilder.py | 437 +++++ .../locations/api/beta/remple/schemata.py | 120 ++ .../locations/api/beta/remple/utils.py | 48 + .../locations/api/beta/resources/__init__.py | 244 +++ .../locations/api/beta/resources/locations.py | 136 ++ .../locations/api/beta/schemata.py | 178 ++ storage_service/locations/api/resources.py | 1 + .../locations/api/sword/helpers.py | 2 +- storage_service/locations/api/urls.py | 2 + storage_service/locations/fixtures/files.json | 65 + .../locations/fixtures/package.json | 16 +- .../migrations/0020_add_fields_to_file.py | 59 + storage_service/locations/models/event.py | 11 + storage_service/locations/models/location.py | 2 +- storage_service/locations/models/package.py | 119 +- .../locations/tests/test_api_beta.py | 407 +++++ .../tests/test_api_beta_querybuilder.py | 387 ++++ .../locations/tests/test_api_beta_urls.py | 243 +++ .../locations/tests/test_z_api_beta_client.py | 293 +++ storage_service/static/css/swagger-ui.css | 3 + .../static/images/favicon-16x16.png | Bin 0 -> 445 bytes .../static/images/favicon-32x32.png | Bin 0 -> 1141 bytes .../static/js/swagger-ui-bundle.js | 104 ++ .../static/js/swagger-ui-standalone-preset.js | 14 + storage_service/static/js/swagger-ui.js | 9 + .../templates/locations/api/beta/openapi.html | 71 + tox.ini | 4 +- 36 files changed, 7108 insertions(+), 9 deletions(-) create mode 100644 storage_service/locations/api/beta/__init__.py create mode 100644 storage_service/locations/api/beta/remple/README.rst create mode 100644 storage_service/locations/api/beta/remple/__init__.py create mode 100644 storage_service/locations/api/beta/remple/clientbuilder.py create mode 100644 storage_service/locations/api/beta/remple/constants.py create mode 100644 storage_service/locations/api/beta/remple/openapi.py create mode 100644 storage_service/locations/api/beta/remple/querybuilder.py create mode 100644 storage_service/locations/api/beta/remple/resources.py create mode 100644 storage_service/locations/api/beta/remple/routebuilder.py create mode 100644 storage_service/locations/api/beta/remple/schemata.py create mode 100644 storage_service/locations/api/beta/remple/utils.py create mode 100644 storage_service/locations/api/beta/resources/__init__.py create mode 100644 storage_service/locations/api/beta/resources/locations.py create mode 100644 storage_service/locations/api/beta/schemata.py create mode 100644 storage_service/locations/fixtures/files.json create mode 100644 storage_service/locations/migrations/0020_add_fields_to_file.py create mode 100644 storage_service/locations/tests/test_api_beta.py create mode 100644 storage_service/locations/tests/test_api_beta_querybuilder.py create mode 100644 storage_service/locations/tests/test_api_beta_urls.py create mode 100644 storage_service/locations/tests/test_z_api_beta_client.py create mode 100644 storage_service/static/css/swagger-ui.css create mode 100644 storage_service/static/images/favicon-16x16.png create mode 100644 storage_service/static/images/favicon-32x32.png create mode 100644 storage_service/static/js/swagger-ui-bundle.js create mode 100644 storage_service/static/js/swagger-ui-standalone-preset.js create mode 100644 storage_service/static/js/swagger-ui.js create mode 100644 storage_service/templates/locations/api/beta/openapi.html diff --git a/requirements/base.txt b/requirements/base.txt index 2dad7855e..34e753360 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -15,13 +15,15 @@ django-extensions==1.7.9 django-model-utils==3.0.0 #tastypie 0.13.3 has breaking changes django-tastypie==0.13.1 +formencode==1.3.1 futures==3.0.5 # used by gunicorn's async workers gevent==1.2.1 # used by gunicorn's async workers gunicorn==19.7.1 +inflect==0.2.1 jsonfield==2.0.1 logutils==0.3.4.1 lxml==3.7.3 -metsrw==0.2.0 +git+https://github.com/artefactual-labs/mets-reader-writer.git@dev/issue-11581-premis-parsing ndg-httpsclient==0.4.2 pyasn1==0.2.3 python-gnupg==0.4.0 diff --git a/storage_service/locations/api/beta/__init__.py b/storage_service/locations/api/beta/__init__.py new file mode 100644 index 000000000..66ee12479 --- /dev/null +++ b/storage_service/locations/api/beta/__init__.py @@ -0,0 +1,112 @@ +"""Version beta of the Storage Service API. + +The Storage Service exposes the following resources via a consistent HTTP JSON +interface under the path namespace /api/beta/: + +- /locations/ --- purpose-specific paths within a /spaces/ resource +- /spaces/ --- storage space with behaviour specific to backing system +- /pipelines/ --- an Archivematica instance that is the source of a package + +The following resources are read-only (update, create, delete and related +operations are disabled): + +- /packages/ --- Information Package (SIP, DIP or AIP) +- /file/ --- a file on disk (which is in a package), represented as db row. + +All resources have endpoints that follow this pattern:: + + +-----------------+-------------+----------------------------+------------+ + | Purpose | HTTP Method | Path | Method | + +-----------------+-------------+----------------------------+------------+ + | Create new | POST | // | create | + | Get create data | GET | //new/ | new | + | Read all | GET | // | index | + | Read specific | GET | /// | show | + | Update specific | PUT | /// | update | + | Get update data | GET | ///edit/ | edit | + | Delete specific | DELETE | /// | delete | + | Search | SEARCH | // | search | + | Search | POST | //search/ | search | + | Get search data | GET | //new_search/ | new_search | + +-----------------+-------------+----------------------------+------------+ + +.. note:: To remove the search-related routes for a given resource, create a + ``'searchable'`` key with value ``False`` in the configuration for the + resource in the ``RESOURCES`` dict. E.g., ``'location': {'searchable': + False}`` will make the /locations/ resource non-searchable. + +.. note:: All resources expose the same endpoints. If a resource needs special + treatment, it should be done at the corresponding class level. E.g., if + ``POST /packages/`` (creating a package) is special, then do special stuff + in ``resources.py::Packages.create``. Similarly, if packages are indelible, + then ``resources.py::Packages.delete`` should return 404. +""" + +from locations.api.beta.remple import API +from locations.api.beta.resources import ( + ArkivumSpaces, + # Asyncs, + Callbacks, + DataverseSpaces, + DSpaceSpaces, + DuracloudSpaces, + Events, + FedoraSpaces, + FixityLogs, + LockssomaticSpaces, + NFSSpaces, + S3Spaces, + SwiftSpaces, + Files, + GPGSpaces, + LocalFilesystemSpaces, + Locations, + Packages, + Pipelines, + PipelineLocalSpaces, + Spaces, + Users, + Groups, + Permissions, + ContentTypes, +) + +API_VERSION = 'beta' +SERVICE_NAME = 'Archivematica Storage Service' + +resources = { + 'pipeline': {'resource_cls': Pipelines}, + 'location': {'resource_cls': Locations}, + 'space': {'resource_cls': Spaces}, + 'user': {'resource_cls': Users}, + + # Space sub-types + 'local_filesystem_space': {'resource_cls': LocalFilesystemSpaces}, + 'gpg_space': {'resource_cls': GPGSpaces}, + 'arkivum_space': {'resource_cls': ArkivumSpaces}, + 'dataverse_space': {'resource_cls': DataverseSpaces}, + 'dspace_space': {'resource_cls': DSpaceSpaces}, + 'duracloud_space': {'resource_cls': DuracloudSpaces}, + 'fedora_space': {'resource_cls': FedoraSpaces}, + 'lockssomatic_space': {'resource_cls': LockssomaticSpaces}, + 'nfs_space': {'resource_cls': NFSSpaces}, + 's3_space': {'resource_cls': S3Spaces}, + 'swift_space': {'resource_cls': SwiftSpaces}, + 'pipeline_local_space': {'resource_cls': PipelineLocalSpaces}, + + # The following resources are read-only because of their super-classes + 'file': {'resource_cls': Files}, + 'package': {'resource_cls': Packages}, + 'event': {'resource_cls': Events}, + 'callback': {'resource_cls': Callbacks}, + 'fixity_log': {'resource_cls': FixityLogs}, + 'group': {'resource_cls': Groups}, + 'permission': {'resource_cls': Permissions}, + 'contenttype': {'resource_cls': ContentTypes}, +} + +api = API(api_version=API_VERSION, service_name=SERVICE_NAME) +api.register_resources(resources) +urls = api.get_urlpatterns() + +__all__ = ('urls', 'api') diff --git a/storage_service/locations/api/beta/remple/README.rst b/storage_service/locations/api/beta/remple/README.rst new file mode 100644 index 000000000..32ea4aa1a --- /dev/null +++ b/storage_service/locations/api/beta/remple/README.rst @@ -0,0 +1,14 @@ +================================================================================ + Remple: REST Simple +================================================================================ + +Usage:: + + >>> from remple import API, Resources + >>> class Users(Resources): + ... model_cls = User # A Django model class + ... schema_cls = UserSchema # A Formencode class + >>> resources = {'user': {'resource_cls': Users}} + >>> api = API(api_version='0.1.0', service_name='User City!') + >>> api.register_resources(resources) + >>> urls = api.get_urlpatterns() # Include these in Django urlpatterns diff --git a/storage_service/locations/api/beta/remple/__init__.py b/storage_service/locations/api/beta/remple/__init__.py new file mode 100644 index 000000000..f324f029e --- /dev/null +++ b/storage_service/locations/api/beta/remple/__init__.py @@ -0,0 +1,60 @@ +"""Remple: REST Simple + +Usage:: + + >>> from remple import API, Resources + >>> class Users(Resources): + ... model_cls = User # A Django model class + ... schema_cls = UserSchema # A Formencode class + >>> resources = {'user': {'resource_cls': Users}} + >>> api = API(api_version='0.1.0', service_name='User City!') + >>> api.register_resources(resources) + >>> urls = api.get_urlpatterns() # Include thes in Django urlpatterns +""" + +from __future__ import absolute_import + +from .resources import Resources, ReadonlyResources +from .querybuilder import QueryBuilder +from .routebuilder import RouteBuilder as API +from .routebuilder import ( + ID_PATT, + UUID_PATT, + get_collection_targeting_regex, + get_member_targeting_regex, +) +from .openapi import ( + CustomEndPoint, + schema_name2path, + get_error_schema_path, + get_ref_response, +) +from . import utils +from .schemata import ResourceURI +from .constants import ( + OK_STATUS, + NOT_FOUND_STATUS, + UNAUTHORIZED_MSG, + FORBIDDEN_STATUS, +) + +__all__ = ( + 'API', + 'CustomEndPoint', + 'FORBIDDEN_STATUS', + 'ID_PATT', + 'NOT_FOUND_STATUS', + 'OK_STATUS', + 'QueryBuilder', + 'ReadonlyResources', + 'Resources', + 'ResourceURI', + 'UNAUTHORIZED_MSG', + 'UUID_PATT', + 'get_collection_targeting_regex', + 'get_error_schema_path', + 'get_ref_response', + 'get_member_targeting_regex', + 'schema_name2path', + 'utils', +) diff --git a/storage_service/locations/api/beta/remple/clientbuilder.py b/storage_service/locations/api/beta/remple/clientbuilder.py new file mode 100644 index 000000000..27e873c36 --- /dev/null +++ b/storage_service/locations/api/beta/remple/clientbuilder.py @@ -0,0 +1,740 @@ +"""Remple OpenAPI Client + +This module is not a code generator. It does not generate source code. It takes +an OpenAPI 3.0 definition of an API as an OrderedDict (see OPENAPI_SPEC) and +returns a module with a class (dynamically named according to the title of the +API defined in the OpenAPI spec) that provides a Pythonic interface to the +OpenAPI-described API. + +Imagine an API entitled "Archivematica Storage Service API" and which exposes +CRUD endpoints on two resources: locations and spaces. Example usage might be:: + + >>> from clientbuilder import ArchivematicaStorageServiceApiClient + >>> c = ArchivematicaStorageServiceApiClient( + ... username='test', + ... api_key='test', + ... url='http://127.0.0.1:62081/') + >>> first_2_spaces = c.space.get_many( + ... items_per_page=2, order_by_attribute='id', page=1)['items'] + >>> first_space = first_2_spaces[0] + >>> locations_of_first_space = c.location.search({ + ... 'filter': ['space', 'uuid', '=', first_space['uuid']]})[ + ... 'items'] + >>> first_location = c.location.get(locations_of_first_space[0]['uuid']) + >>> first_location['purpose'] = 'AS' + >>> updated_first_location = c.location.update( + ... pk=first_location['uuid'], **first_location) + >>> new_location = c.location.create( + ... purpose='AS', + ... relative_path='some/path', + ... space=first_space['uuid']) + >>> pprint.pprint(new_location) + ... {u'pipeline': [u'bb603958-c7f6-46c0-8677-7ce1a4a45497'], + ... u'used': 0, + ... u'description': None, + ... u'space': u'ad48b0df-295e-4f97-810e-b8de14b92c4b', + ... u'enabled': True, + ... u'quota': None, + ... u'relative_path': u'some/path', + ... u'purpose': u'AS', + ... u'id': 6, + ... u'resource_uri': u'/api/v3/locations/ec9c2e51-8883-472a-986a-48c7dc44e3a9/', + ... u'replicators': [], + ... u'uuid': u'ec9c2e51-8883-472a-986a-48c7dc44e3a9'} + +TODOs: + +- handle client-side validation of circular request bodies, i.e., search +- Document that `update` (PUT) is update and not PATCH. That is, the resource + sent with an update/PUT request determines the updated state of the new + resource completely. You cannot send just the updated attributes. You must + send all attributes. That is, you must do your "patching" on the client and + send the *entire* new state of the updated resource to the server. + +""" + + +from collections import OrderedDict +import logging +import json +import sys +import textwrap + +import requests + + +logger = logging.getLogger(__name__) +log_lvl = logging.INFO +out_hdlr = logging.StreamHandler(sys.stdout) +logger.addHandler(out_hdlr) +logger.setLevel(log_lvl) + + +OPENAPI_SPEC = None + + +HTTP_METHODS = ('get', 'delete', 'post', 'put') +METHOD_GET = "GET" +METHOD_POST = "POST" +METHOD_DELETE = "DELETE" + + +class Undefined(object): + pass + + +indented_wrapper = textwrap.TextWrapper( + width=80, + initial_indent=' ' * 4, + subsequent_indent=' ' * 8) + + +class ClientError(Exception): + """Base exception for this module.""" + + +class ValidationError(ClientError): + """If the arguments to a method are invalid given the API spec.""" + + +class HTTPError(ClientError): + """If an HTTP request fails unexpectedly.""" + + +class ResponseError(ClientError): + """if an HTTP response is unexpected given the API spec.""" + + +methods_raise = (ValidationError, HTTPError, ResponseError,) + + +def _call_url_json(self, url, params=None, method=METHOD_GET, headers=None, + assume_json=True, responses_cfg=None): + """Helper to GET a URL where the expected response is 200 with JSON. + + :param str url: URL to call + :param dict params: Params to pass as HTTP query string or JSON body + :param str method: HTTP method (e.g., 'GET') + :param dict headers: HTTP headers + :param bool assume_json: set to False if the response body should not be + decoded as JSON + :returns: Dict of the returned JSON or an integer error + code to be looked up + """ + method = method.upper() + try: + if method == METHOD_GET or method == METHOD_DELETE: + response = requests.request(method, + url=url, + params=params, + headers=headers) + else: + response = requests.request(method, + url=url, + data=params, + headers=headers) + logger.debug('Response: %s', response) + logger.debug('type(response.text): %s ', type(response.text)) + logger.debug('Response content-type: %s', + response.headers['content-type']) + except requests.exceptions.ConnectionError as err: + msg = 'Connection error {}'.format(err) + logger.error(msg) + raise HTTPError(msg[:30]) + except Exception as err: + msg = 'Unknown error making HTTP request {}'.format(err) + logger.error(msg) + raise HTTPError(msg[:30]) + responses_cfg = responses_cfg or {} + return self._handle_response(response, method, url, responses_cfg) + + +def _handle_response(self, response, method, url, responses_cfg): + """Use the OpenAPI-specified responses object to handle how we return the + response. Warning: a lot of assumptions in here; OpenAPI could definitely + be used more intelligently and generally here. + """ + status_code = str(response.status_code) + response_cfg = responses_cfg.get(status_code) + if not response_cfg: + msg = ( + 'Unknown status code {status_code} ("{reason}") returned for' + '{method} request to {url}'.format( + status_code=status_code, reason=response.reason, method=method, + url=url)) + logger.warning(msg) + raise ResponseError(msg) + resp_descr = response_cfg.get('description', 'Response lacks a description') + logger.debug('Received an expected response with this description: %s', + resp_descr) + resp_json_cfg = response_cfg.get('content', {}).get('application/json', {}) + if resp_json_cfg: + try: + ret = response.json() + except ValueError: # JSON could not be decoded + msg = 'Could not parse JSON from response' + logger.warning(msg) + raise ResponseError(msg) + else: + return {'error': 'Not a JSON response; this client expects only JSON' + ' responses.', + 'response_text': response.text} + return ret # How should the happy path response schema inform the return value? + + +def get_openapi_spec(): + return OPENAPI_SPEC + + +def get_client_class_name(openapi_spec): + title = openapi_spec['info']['title'] + return '{}Client'.format( + ''.join(w.capitalize() for w in title.strip().lower().split())) + + +def get_client_class_docstring(openapi_spec, resource_classes): + docstring = [] + title = openapi_spec['info']['title'] + version = openapi_spec['info']['version'] + docstring.append('{} version {} client'.format(title, version)) + description = openapi_spec['info'].get('description') + if description: + docstring.append('\n\n') + docstring.append(textwrap.fill(description, 80)) + if resource_classes: + docstring.append('\n\n') + docstring.append(textwrap.fill( + 'The following instance attributes allow interaction with the' + ' resources that the API exposes. See their documentation:', + 80)) + docstring.append('\n\n') + for attr_name in sorted(resource_classes): + docstring.append('- ``self.{}``\n'.format(attr_name)) + if description or resource_classes: + docstring.append('\n') + return ''.join(docstring) + + +def deref(openapi_spec, ref_path): + """Given an OpenAPI $ref path like '#/components/schemas/ErrorSchema', + dereference it, i.e., return its corresponding object (typically a dict). + """ + ref_path_parts = ref_path.strip('#/').split('/') + dict_ = openapi_spec + for key in ref_path_parts: + dict_ = dict_[key] + return dict_ + + +def recursive_deref(openapi_spec, ref_path, seen_paths=None): + """Recursively dereference OpenAPI $ref path ``ref_path``, returning a + 2-tuple containing the corresponding object as well as a boolean indicating + whether the object is circularly referential. + """ + circular = False + seen_paths = seen_paths or [] + if ref_path in seen_paths: + return ref_path, True + seen_paths.append(ref_path) + derefed = deref(openapi_spec, ref_path) + properties = derefed.get('properties') + if properties: + for key, cfg in properties.items(): + ref = cfg.get('$ref') + if ref: + ret, circ = recursive_deref( + openapi_spec, ref, seen_paths=seen_paths) + derefed['properties'][key] = ret + if circ: + circular = True + one_of = derefed.get('oneOf') + if one_of: + new_one_of = [] + for these in one_of: + ret, circ = recursive_deref( + openapi_spec, these['$ref'], seen_paths=seen_paths) + new_one_of.append(ret) + if circ: + circular = True + derefed['oneOf'] = new_one_of + return derefed, circular + + +def ref_path2param_name(ref_path): + return ref_path.strip('#/').split('/')[-1] + + +def process_param(parameter, openapi_spec): + try: + ref_path = parameter['$ref'] + except KeyError: + return parameter['name'], parameter + param_name = ref_path2param_name(ref_path) + param_cfg = deref(openapi_spec, ref_path) + return param_name, param_cfg + + +def _reconstruct_params(locals_, args_, kwargs_): + ret = {} + for arg_name, arg_cfg in args_.items(): + ret[arg_name] = locals_[arg_name] + for arg_name, arg_cfg in kwargs_.items(): + try: + ret[arg_name] = locals_[arg_name] + except KeyError: + try: + ret[arg_name] = arg_cfg['default'] + except KeyError: + pass + return ret + + +openapitype2pythontype = { + 'string': ((str, unicode), 'str or unicode'), + 'integer': ((int,), 'int'), + 'number': ((int, float), 'int or float'), + 'array': ((list,), 'list'), + 'boolean': ((bool,), 'bool'), +} + + +def get_param_docstring_line(arg, arg_cfg): + arg_line = [arg] + arg_type = arg_cfg.get('type', arg_cfg.get('schema', {}).get('type')) + if arg_type: + _, arg_type = openapitype2pythontype.get(arg_type, (None, arg_type)) + arg_format = arg_cfg.get('format', arg_cfg.get('schema', {}).get('format')) + if (not arg_format) and arg_type == 'list': + arg_format = arg_cfg.get('items', {}).get('format') + if arg_format: + arg_format = 'each element is a {}'.format(arg_format) + if arg_format: + arg_line.append(' ({}; {}):'.format(arg_type, arg_format)) + else: + arg_line.append(' ({}):'.format(arg_type)) + arg_description = arg_cfg.get('description') + if arg_description: + if not arg_description.endswith('.'): + arg_description = '{}.'.format(arg_description) + arg_line.append(' {}'.format(arg_description)) + + arg_enum = arg_cfg.get('enum', arg_cfg.get('schema', {}).get('enum')) + if arg_enum: + arg_line.append(' Must be one of {}.'.format( + ', '.join(repr(e) for e in arg_enum))) + return '\n' + indented_wrapper.fill(''.join(arg_line)) + + +def decapitalize(string): + return string[:1].lower() + string[1:] if string else '' + + +def get_returns_docstring_line(openapi_spec, code, cfg): + return_description = [] + condition = decapitalize( + cfg.get('description', '{} response'.format(code))).rstrip('.') + ref = cfg.get('content', {}).get('application/json', {}).get( + 'schema', {}).get('$ref') + if ref: + schema = deref(openapi_spec, ref) + ret_type = {'object': 'dict'}.get( + schema.get('type'), 'unknown type') + return_description.append('{}:'.format(ret_type)) + if ret_type == 'dict': + required_keys = schema.get('required') + if required_keys: + return_description.append( + ' with key(s): "{}"'.format('", "'.join(required_keys))) + else: + return_description.append('\n unknown type:') + return_description.append(', if {}.'.format(condition)) + return '\n' + indented_wrapper.fill(''.join(return_description)) + + +def get_method_docstring(op_cfg, args, kwargs, openapi_spec): + summary = op_cfg.get('summary') + description = op_cfg.get('description') + if not summary: + return None + docstring = [summary] + if description and description != summary: + docstring.append('\n\n') + docstring.append(description) + if args or kwargs: + docstring.append('\n\n') + docstring.append('Args:') + for arg, arg_cfg in args.items(): + docstring.append(get_param_docstring_line(arg, arg_cfg)) + for kwarg, kwarg_cfg in kwargs.items(): + docstring.append(get_param_docstring_line(kwarg.replace('_param', ''), kwarg_cfg)) + docstring.append('\n\n') + docstring.append('Returns:') + for code, cfg in op_cfg.get('responses').items(): + docstring.append(get_returns_docstring_line(openapi_spec, code, cfg)) + docstring.append('\n\n') + docstring.append('Raises:') + for exc in methods_raise: + docstring.append('\n' + indented_wrapper.fill('{}: {}'.format( + exc.__name__, exc.__doc__))) + return ''.join(docstring) + + +def _validate_min(param_name, param_val, param_schema): + param_min = param_schema.get('minimum') + if param_min: + if param_val < param_min: + raise ValidationError( + 'Value {} for argument "{}" must be {} or greater.'.format( + param_val, param_name, param_min)) + + +def _validate_max(param_name, param_val, param_schema): + param_max = param_schema.get('maximum') + if param_max: + if param_val > param_max: + raise ValidationError( + 'Value {} for argument "{}" is greater than the maximum' + ' allowed value {}.'.format( + param_val, param_name, param_max)) + + +def _validate_enum(param_name, param_val, param_schema): + param_enum = param_schema.get('enum') + if param_enum: + if param_val not in param_enum: + raise ValidationError( + 'Value {} for argument "{}" must be one of {}'.format( + repr(param_val), param_name, + ', '.join(repr(e) for e in param_enum))) + + +def _is_uuid(inp): + err = ValidationError('"{}" is not a valid UUID'.format(inp)) + try: + recomposed = [] + parts = inp.split('-') + if [len(p) for p in parts] != [8, 4, 4, 4, 12]: + raise err + for part in parts: + new_part = ''.join(c for c in part if c in '0123456789abcdef') + recomposed.append(new_part) + recomposed = '-'.join(recomposed) + except Exception: + raise err + if recomposed != inp: + raise err + + +format_validators = { + 'uuid': _is_uuid, +} + + +def _validate_format(param_name, param_val, param_schema): + param_format = param_schema.get('format') + if not param_format: + return + validator = format_validators.get(param_format) + if not validator: + return + validator(param_val) + + +def _validate(params, args_, kwargs_): + """Validate user-supplied ``params`` using the parameter configurations + described in the ``args_`` and ``kwargs_`` dicts. Raise a + ``ValidationError`` if a value is invalid. Also remove unneeded + values from ``params``, which is what gets sent (as request body or query + params) in the request. TODO: we need to validate complex objects in this + function but this is a non-trivial issue. + """ + to_delete = [] + for param_name, param_val in params.items(): + param_required = False + param_cfg = args_.get(param_name) + if param_cfg: + param_required = True + else: + param_cfg = kwargs_[param_name] + param_schema = param_cfg.get('schema', {}) + param_type = param_schema.get('type', param_cfg.get('type')) + param_dflt = param_cfg.get('default', Undefined) + if not param_type: + continue + if param_type == 'object': + if not param_val: + if param_required: + raise ValidationError( + 'Property {} is required'.format(param_name)) + continue + # TODO: we need to validate all of the properties of this object: + # for key, val in param_val.items(): + # # validate against param_cfg['properties'][key] + continue + param_type, _ = openapitype2pythontype.get(param_type, (None, None)) + if not param_type: + continue + if ((param_val is None) and + (not param_required) and + (not param_val == param_dflt) and + (not isinstance(None, param_type))): + to_delete.append(param_name) + continue + if ((not isinstance(param_val, param_type)) and + (not param_val == param_dflt)): + raise ValidationError( + 'Value "{}" for argument "{}" is of type {}; it must be of type(s)' + ' {}'.format(param_val, param_name, type(param_val), + ', '.join(str(t) for t in param_type))) + _validate_min(param_name, param_val, param_schema) + _validate_max(param_name, param_val, param_schema) + _validate_format(param_name, param_val, param_schema) + _validate_enum(param_name, param_val, param_schema) + param_in = param_cfg.get('in') + if param_in == 'path': + to_delete.append(param_name) + for td in to_delete: + del params[td] + + +def _get_kwarg_names(kwargs_): + if not kwargs_: + return '' + kwarg_names = [] + for kwarg_name, cfg in sorted(kwargs_.items()): + default = cfg.get('default') + if not default: + default = cfg.get('schema', {}).get('default') + kwarg_names.append('{}={}'.format(kwarg_name, repr(default))) + return ', ' + ', '.join(kwarg_names) + + +def _serialize_params(self, params, request_body_config): + rb_json_cfg = request_body_config.get( + 'content', {}).get('application/json') + if not rb_json_cfg: + return params + return json.dumps(params) + + +def get_method_signature(method_name, arg_names, kwarg_names): + ret = 'def {method_name}(self{arg_names}{kwarg_names}):'.format( + method_name=method_name, arg_names=arg_names, kwarg_names=kwarg_names) + indent = ' ' * len('def {}('.format(method_name)) + return textwrap.TextWrapper(width=80, subsequent_indent=indent).fill(ret) + + +def generate_method_code(method_name, docstring, args_, kwargs_): + arg_names = '' + if args_: + arg_names = ', ' + ', '.join(args_) + kwarg_names = _get_kwarg_names(kwargs_) + method_sig = get_method_signature(method_name, arg_names, kwarg_names) + + return ''' +{method_sig} + """{docstring} + """ + locals_copy = locals().copy() + format_kwargs = {{key: locals_copy[key] for key in args_}} + path_ = globals()['path_'].format(**format_kwargs) + url = self.url + path_ + params = _reconstruct_params(locals(), args_, kwargs_) + _validate(params, args_, kwargs_) + params = self._serialize_params(params, request_body_config) + return self._call_url_json( + url, params=params, method=http_method.upper(), + headers=self.get_auth_headers(), responses_cfg=responses_cfg) +'''.format(method_sig=method_sig, docstring=docstring) + + +def _get_request_body_schema(openapi_spec, operation_config): + request_body = operation_config.get('requestBody') + if not request_body: + return None + try: + rb_ref_path = request_body[ + 'content']['application/json']['schema']['$ref'] + except KeyError: + return None + schema, _ = recursive_deref(openapi_spec, rb_ref_path) + return schema + + +def _get_request_body_args_kwargs(openapi_spec, op_cfg): + args = OrderedDict() + kwargs = {} + rb_schema = _get_request_body_schema(openapi_spec, op_cfg) + if rb_schema: + for arg_name in sorted(rb_schema.get('required', [])): + args[arg_name] = rb_schema['properties'][arg_name] + for kwarg, cfg in rb_schema['properties'].items(): + if kwarg in args: + continue + kwargs[kwarg] = cfg + return args, kwargs + + +def get_method(openapi_spec, path, path_params, http_method, op_cfg): + """Return a Python method, its name and its namespace, given the operation + defined by the unique combination of ``path`` and ``http_method``. E.g., + path /locations/{pk}/ and http_method GET will return a method named + ``get`` with namespace ``location``. The ``get`` method will be assigned to + a ``LocationClient`` instance of the ``Client`` instance, thus allowing the + caller to call ``myclient.location.get(pk_of_a_resource)``. + """ + # pylint: disable=exec-used,too-many-locals + method_name, namespace = op_cfg['operationId'].split('.') + namespace_description = None + tags = op_cfg.get('tags', []) + if tags: + tag = tags[0] + tag_cfg = [t for t in openapi_spec['tags'] if t['name'] == tag][0] + namespace_description = tag_cfg.get('description') + rb_args, rb_kwargs = _get_request_body_args_kwargs(openapi_spec, op_cfg) + parameters = op_cfg.get('parameters', []) + args_ = OrderedDict() + kwargs_ = {} + for parameter in path_params: + args_[parameter['name']] = parameter + for param_name, param_cfg in rb_args.items(): + args_[param_name] = param_cfg + for param_name, param_cfg in rb_kwargs.items(): + kwargs_[param_name] = param_cfg + for parameter in parameters: + param_name, param_cfg = process_param(parameter, openapi_spec) + if param_cfg.get('required', False): + args_[param_name] = param_cfg + else: + kwargs_[param_name] = param_cfg + docstring = get_method_docstring(op_cfg, args_, kwargs_, openapi_spec) + method_ = generate_method_code(method_name, docstring, args_, kwargs_) + # This is necessary so that the method is a closure over these values + temp_globals = globals().copy() + temp_globals.update({'path_': path, + 'args_': args_, + 'kwargs_': kwargs_, + 'http_method': http_method, + 'responses_cfg': op_cfg['responses'], + 'request_body_config': op_cfg.get('requestBody', {}), + }) + exec method_ in temp_globals, locals() + return locals()[method_name], method_name, namespace, namespace_description + + +def get_namespaces_methods(openapi_spec): + """Return a dict from namespaces to method names to methods, e.g.,:: + + >>> {'location': {'get': , + ... 'get_many': , + ... ...}, + ... 'package': {'get': , + ... ...}, + ... ...} + """ + methods = {} + for path, cfg in openapi_spec['paths'].items(): + path_params = cfg.get('parameters', []) + for http_method, op_cfg in cfg.items(): + if http_method in HTTP_METHODS: + method, method_name, namespace, namespace_description = ( + get_method(openapi_spec, path, path_params, http_method, + op_cfg)) + methods.setdefault(namespace, {}) + methods[namespace][method_name] = method + methods[namespace]['__doc__'] = namespace_description + return methods + + +def get_get_auth_headers_meth(openapi_spec): + """Return a method for the client class that returns the authentication + headers needed to make requests. Just hard-coding this for now to be + specific to the AM SS API, but it should be generalized to parse the + OpenAPI spec. + """ + # pylint: disable=unused-argument + def get_auth_headers(self): + return {'Authorization': 'ApiKey {}:{}'.format( + self.username, + self.api_key)} + return get_auth_headers + + +def get_base_client_class(openapi_spec): + + def __init__(self, username, api_key, url): + """Args: + username (str): The username to use when authenticating to the API. + api_key (str): The API key to use when authenticating to the API. + url (str): The URL of the API. + """ + self.username = username + self.api_key = api_key + self.url = url.rstrip('/') + self.openapi_spec['servers'][0]['url'] + + def get_auth_headers(self): + """Return the authorization header(s) as a dict.""" + return {'Authorization': 'ApiKey {}:{}'.format( + self.username, + self.api_key)} + + return type( + 'BaseClient', + (object,), + { + '__init__': __init__, + 'get_auth_headers': get_auth_headers, + 'openapi_spec': openapi_spec, + '_call_url_json': _call_url_json, + '_handle_response': _handle_response, + '_serialize_params': _serialize_params, + } + ) + + +def get_init_meth(): + def __init__(self, username, api_key, url): + super(self.__class__, self).__init__(username, api_key, url) + for resource_name, resource_class in self.resource_classes.items(): + setattr(self, resource_name, resource_class(username, api_key, url)) + return __init__ + + +def get_rsrc_cls_docstring(resource_name, current_doc): + if current_doc: + return current_doc + return 'Provides access to the {} resource'.format(resource_name) + + +def metaprog_client_class(): + """Define and return the client class and its name. + """ + openapi_spec = get_openapi_spec() + client_class_name_ = get_client_class_name(openapi_spec) + BaseClient = get_base_client_class(openapi_spec) + namespaces_methods = get_namespaces_methods(openapi_spec) + resource_classes = {} + for namespace, rsrc_methods in namespaces_methods.items(): + cls_name = namespace.capitalize() + 'Client' + rsrc_methods['__doc__'] = get_rsrc_cls_docstring( + namespace, rsrc_methods['__doc__']) + rsrc_cls = type(cls_name, (BaseClient,), rsrc_methods) + resource_classes[namespace] = rsrc_cls + client_class_docstring = get_client_class_docstring( + openapi_spec, resource_classes) + attributes = {'__init__': get_init_meth(), + 'resource_classes': resource_classes, + '__doc__': client_class_docstring} + client_class_ = type( + client_class_name_, # e.g., ArchivematicaStorageServiceAPIClass + (BaseClient,), # superclass(es) + attributes # class attributes and methods (descriptors) + ) + return client_class_, client_class_name_ + + +client_class, client_class_name = metaprog_client_class() +globals()[client_class_name] = client_class + +# pylint: disable=undefined-all-variable +__all__ = ('client_class', client_class_name) diff --git a/storage_service/locations/api/beta/remple/constants.py b/storage_service/locations/api/beta/remple/constants.py new file mode 100644 index 000000000..bc72338f6 --- /dev/null +++ b/storage_service/locations/api/beta/remple/constants.py @@ -0,0 +1,92 @@ +from django.db.models.fields.related import OneToOneField +from django.db.models.fields import ( + AutoField, + BigIntegerField, + BooleanField, + CharField, + DateTimeField, + EmailField, + IntegerField, + NullBooleanField, + PositiveIntegerField, + TextField, + UUIDField, + URLField, +) +from django_extensions.db.fields import UUIDField as UUIDFieldExt +from jsonfield.fields import JSONField +from formencode.validators import ( + Bool, + Email, + Int, + IPAddress, + OneOf, + Regex, + UnicodeString, + URL, +) + +JSONDecodeErrorResponse = { + 'error': 'JSON decode error: the parameters provided were not valid' + ' JSON.' +} + +UNAUTHORIZED_MSG = { + 'error': 'You are not authorized to access this resource.' +} + +READONLY_RSLT = {'error': 'This resource is read-only.'} + +OK_STATUS = 200 +CREATED_STATUS = 201 +BAD_REQUEST_STATUS = 400 +FORBIDDEN_STATUS = 403 +NOT_FOUND_STATUS = 404 +METHOD_NOT_ALLOWED_STATUS = 405 + + +django_field2openapi_type = { + AutoField: 'integer', + BigIntegerField: 'integer', + IntegerField: 'integer', + PositiveIntegerField: 'integer', + BooleanField: 'boolean', + NullBooleanField: 'boolean', + CharField: 'string', + EmailField: 'string', + TextField: 'string', + UUIDField: 'string', + DateTimeField: 'string', + JSONField: 'object', + OneToOneField: 'string', + UUIDFieldExt: 'string', + URLField: 'string', +} + +django_field2openapi_format = { + UUIDField: 'uuid', + DateTimeField: 'date-time', + URLField: 'uri', +} + +python_type2openapi_type = { + str: 'string', + int: 'integer', + float: 'integer', +} + +formencode_field2openapi_type = { + UnicodeString: 'string', + Regex: 'string', + Email: 'string', + OneOf: 'string', # note: not universally accurate + IPAddress: 'string', + URL: 'string', + Int: 'integer', + Bool: 'boolean', +} + +formencode_field2openapi_format = { + IPAddress: 'ipv4', + URL: 'uri', +} diff --git a/storage_service/locations/api/beta/remple/openapi.py b/storage_service/locations/api/beta/remple/openapi.py new file mode 100644 index 000000000..cdb85d70b --- /dev/null +++ b/storage_service/locations/api/beta/remple/openapi.py @@ -0,0 +1,1573 @@ +from collections import OrderedDict +import json +import logging +import yaml +try: + from yaml import CDumper as Dumper +except ImportError: + from yaml import Dumper +from yaml.representer import SafeRepresenter + +from django.db.models.fields.related import ( + ForeignKey, + ManyToManyRel, + ManyToManyField, + ManyToOneRel, + OneToOneRel, +) +from django.db.models.fields import NOT_PROVIDED +from formencode.foreach import ForEach +from formencode.compound import Any +from formencode.validators import OneOf + +from .schemata import schemata +from .resources import Resources +from .constants import ( + django_field2openapi_type, + django_field2openapi_format, + python_type2openapi_type, + formencode_field2openapi_type, + formencode_field2openapi_format, +) +from .querybuilder import QueryBuilder +from .schemata import ResourceURI + + +logger = logging.getLogger(__name__) + + +def dict_representer(dumper, data): + return dumper.represent_dict(data.iteritems()) + + +def yaml_dump(obj): + """Allows us to create YAML from Python OrderedDict instances and also have + Python strings and unicode objects correctly represented. From + https://gist.github.com/oglops/c70fb69eef42d40bed06 + """ + Dumper.add_representer(OrderedDict, dict_representer) + Dumper.add_representer(str, SafeRepresenter.represent_str) + Dumper.add_representer(unicode, SafeRepresenter.represent_unicode) + # Cf. http://signal0.com/2013/02/06/disabling_aliases_in_pyyaml.html + Dumper.ignore_aliases = lambda self, data: True + return yaml.dump(obj, Dumper=Dumper, default_flow_style=False) + + +OPENAPI_VERSION = '3.0.0' +SCHEMAS_ABS_PATH = '#/components/schemas/' +ERROR_SCHEMA_NAME = 'ErrorSchema' +PAGINATOR_SCHEMA_NAME = 'PaginatorSchema' + + +class CustomEndPoint(object): + """A descriptor for defining a custom API end point.""" + + def __init__(self, path, http_method, action, method_name, summary='', + description='', tags=None, parameters=None, schemata=None, + responses=None, request_body=None): + self.path = path + self.http_method = http_method + self.action = action + self.method_name = method_name + self.summary = summary + self.description = description + self.tags = tags or [] + self.parameters = parameters or [] + self.request_body = request_body or {} + self.responses = responses or {} + self.schemata = schemata or {} + + def __get__(self, instance, owner): + return self + + +class OpenAPI(object): + + def __init__(self, api_version='0.1.0', service_name='My Service', + path_prefix='/api/'): + self.api_version = api_version + self.service_name = service_name + self.path_prefix = path_prefix + + def generate_open_api_spec(self): + """Generate and OpenAPI specification for this API. + + Returns a Python OrderedDict that can be converted to a YAML file which + describes this ``OpenAPI`` instance. + """ + return OrderedDict([ + ('openapi', OPENAPI_VERSION), + ('info', self._get_api_info()), + ('servers', self._get_api_servers()), + ('security', self._get_api_security()), + ('components', OrderedDict([ + ('securitySchemes', self._get_api_security_schemes()), + ('parameters', self._get_api_remple_parameters()), + ('schemas', self._get_schemas()), + ])), + ('paths', self._get_paths()), + ('tags', self._get_tags()), + + ]) + + def _get_dflt_server_description(self): + return 'The default server for the {}.'.format(self.service_name) + + def _get_api_description(self): + return 'An API for the {}.'.format(self.service_name) + + def _get_api_title(self): + return '{} API'.format(self.service_name) + + def _get_tags(self): + tags = [] + for resource_name, resource_cfg in sorted(self.resources.items()): + resource_cls = resource_cfg['resource_cls'] + description = 'Access to the {} resource.'.format( + resource_name.lower().replace('_', ' ')) + model_docstring = resource_cls.model_cls.__doc__.strip() + if model_docstring.startswith( + '{}('.format(resource_cls.model_cls.__name__)): + model_docstring = None + if model_docstring: + description += ' {}'.format(model_docstring) + tags.append(OrderedDict([ + ('name', self.inflp.plural(resource_name)), + ('description', description), + ])) + return tags + + def _get_paths(self): + paths = {} + for resource_name, resource_cfg in sorted(self.resources.items()): + pk_patt = resource_cfg.get('pk_patt', 'pk') + resource_cls = resource_cfg['resource_cls'] + rsrc_collection_name = self.inflp.plural(resource_name) + self._set_crud_paths(paths, resource_name, resource_cls, pk_patt, + rsrc_collection_name) + if resource_cfg.get('searchable', True): + self._set_search_paths(paths, resource_name, resource_cls, + pk_patt, rsrc_collection_name) + self._set_custom_paths(paths, resource_name, resource_cls, pk_patt, + rsrc_collection_name) + return paths + + def _set_crud_paths(self, paths, resource_name, resource_cls, pk_patt, + rsrc_collection_name): + for action in self.RESOURCE_ACTIONS: + # Read-only resources need no mutating paths + if (not issubclass(resource_cls, Resources) and + action in self.MUTATING_ACTIONS): + continue + http_method = self.ACTIONS2METHODS.get( + action, self.DEFAULT_METHOD).lower() + operation_id = _get_operation_id(action, resource_name) + path_params = None + if action in self.COLLECTION_TARGETING: + path = get_collection_targeting_openapi_path( + rsrc_collection_name) + elif action in self.MEMBER_TARGETING: + path, path_params = get_member_targeting_openapi_path( + resource_name, rsrc_collection_name, pk_patt, resource_cls) + elif action == 'new': + path = get_collection_targeting_openapi_path( + rsrc_collection_name, modifiers=['new']) + else: # edit is default case + path, path_params = get_member_targeting_openapi_path( + resource_name, rsrc_collection_name, pk_patt, resource_cls, + modifiers=['edit']) + if path_params: + path_dict = paths.setdefault(path, {'parameters': path_params}) + else: + path_dict = paths.setdefault(path, {}) + responses_meth = '_get_{}_responses'.format(action) + parameters_meth = '_get_{}_parameters'.format(action) + request_body_meth = '_get_{}_request_body'.format(action) + parameters = getattr(self, parameters_meth, lambda x: None)( + resource_name) + request_body = getattr(self, request_body_meth, lambda x: None)( + resource_name) + summary, description = _summarize( + action, resource_name, rsrc_collection_name) + path_dict[http_method] = OrderedDict([ + ('summary', summary), + ('description', description), + ('operationId', operation_id), + ]) + if parameters: + path_dict[http_method]['parameters'] = parameters + if request_body: + path_dict[http_method]['requestBody'] = request_body + path_dict[http_method]['responses'] = getattr( + self, responses_meth)(resource_name) + path_dict[http_method]['tags'] = [rsrc_collection_name] + + def _set_custom_paths(self, paths, resource_name, resource_cls, pk_patt, + rsrc_collection_name): + """Set an attribute on dict ``paths`` for each custom endpoint + configured on ``resource_cls``. + """ + for custom_endpoint in self._get_custom_endpoints(resource_cls): + http_method = custom_endpoint.http_method + operation_id = _get_operation_id( + custom_endpoint.action, resource_name) + path = '/{}/{}'.format(rsrc_collection_name, custom_endpoint.path) + path_dict = { + http_method: OrderedDict([ + ('operationId', operation_id), + ('summary', custom_endpoint.summary), + ('description', custom_endpoint.description), + ]) + } + for attr in ('parameters', 'request_body', 'responses', 'tags'): + val = getattr(custom_endpoint, attr) + if val: + path_dict[http_method][attr] = val + paths[path] = path_dict + + def _get_search_request_body_examples(self, resource_name): + """Note: unfortunately, these examples will not be displayed in the + Swagger UI since that functionality is not implemented yet. See + https://github.com/swagger-api/swagger-ui/issues/3771. + """ + if resource_name == 'package': + array_search_example = { + 'paginator': {'page': 1, 'items_per_page': 10}, + 'query': { + 'filter': [ + 'and', [['description', 'like', '%a%'], + ['not', ['description', 'like', 'T%']], + ['or', [['size', '<', 1000], + ['size', '>', 512]]]]]}} + object_search_example = { + 'paginator': {'page': 1, 'items_per_page': 10}, + 'query': { + 'filter': { + 'conjunction': 'and', + 'complement': [{'attribute': 'description', + 'relation': 'like', + 'value': '%a%'}, + {'negation': 'not', + 'complement': {'attribute': 'description', + 'relation': 'like', + 'value': 'T%'}}, + {'conjunction': 'or', + 'complement': [{'attribute': 'size', + 'relation': '<', + 'value': 1000}, + {'attribute': 'size', + 'relation': '>', + 'value': 512}]}]}}} + return {'ArraySearchOverPackagesExample': array_search_example, + 'ObjectSearchOverPackagesExample': object_search_example} + + def _set_search_paths(self, paths, resource_name, resource_cls, pk_patt, + rsrc_collection_name): + for action in ('search_post', 'new_search'): + http_method = {'search_post': 'post', 'new_search': 'get'}.get( + action, 'search') + operation_id = _get_operation_id(action, resource_name) + modifiers = {'search_post': ['search'], + 'new_search': ['new_search']}.get( + action, []) + path = get_collection_targeting_openapi_path( + rsrc_collection_name, modifiers=modifiers) + path_dict = paths.setdefault(path, {}) + responses_meth = '_get_{}_responses'.format(action) + parameters_meth = '_get_{}_parameters'.format(action) + request_body_meth = '_get_{}_request_body'.format(action) + request_body_examples_meth = '_get_{}_request_body_examples'.format( + action) + parameters = getattr(self, parameters_meth, lambda x: None)( + resource_name) + request_body_examples = getattr( + self, request_body_examples_meth, lambda x: None)(resource_name) + request_body_meth = getattr(self, request_body_meth, None) + request_body = None + if request_body_meth: + request_body = request_body_meth( + resource_name, examples=request_body_examples) + summary, description = _summarize( + action, resource_name, rsrc_collection_name) + path_dict[http_method] = OrderedDict([ + ('summary', summary), + ('description', description), + ('operationId', operation_id), + ]) + if parameters: + path_dict[http_method]['parameters'] = parameters + if request_body: + path_dict[http_method]['requestBody'] = request_body + path_dict[http_method]['responses'] = getattr( + self, responses_meth)(resource_name) + path_dict[http_method]['tags'] = [rsrc_collection_name] + + # ========================================================================= + # Schema name getters + # ========================================================================= + + def _get_read_schema_name(self, resource_name): + return '{}View'.format(resource_name.capitalize()) + + def _get_create_schema_name(self, resource_name): + return '{}Create'.format(resource_name.capitalize()) + + def _get_update_schema_name(self, resource_name): + return '{}Update'.format(resource_name.capitalize()) + + def _get_edit_schema_name(self, resource_name): + return 'EditA{}'.format(resource_name.capitalize()) + + def _get_new_schema_name(self, resource_name): + return 'New{}'.format(resource_name.capitalize()) + + def _get_paginated_schema_name(self, resource_name): + return 'PaginatedSubsetOf{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_search_schema_name(self, resource_name): + return 'SearchOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_query_schema_name(self, resource_name): + return 'SearchQueryOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_filter_schema_name(self, resource_name): + return 'FilterOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_object_filter_schema_name(self, resource_name): + return 'ObjectFilterOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_array_filter_schema_name(self, resource_name): + return 'ArrayFilterOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_new_search_schema_name(self, resource_name): + return 'DataForNewSearchOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_related_filter_schema_name(self, resource_name, attribute): + return 'FilterOver{}{}'.format( + self.inflp.plural(resource_name).capitalize(), + attribute.lower().capitalize()) + + def _get_coordinative_filter_schema_name(self, resource_name): + return 'CoordinativeFilterOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_negative_filter_schema_name(self, resource_name): + return 'NegativeFilterOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + def _get_simple_filter_schema_name(self, resource_name): + return 'SimpleFilterOver{}'.format( + self.inflp.plural(resource_name).capitalize()) + + # ========================================================================= + # Schema path getters + # ========================================================================= + + def _get_read_schema_path(self, resource_name): + return schema_name2path(self._get_read_schema_name(resource_name)) + + def _get_create_schema_path(self, resource_name): + return schema_name2path(self._get_create_schema_name(resource_name)) + + def _get_update_schema_path(self, resource_name): + return schema_name2path(self._get_update_schema_name(resource_name)) + + def _get_edit_schema_path(self, resource_name): + return schema_name2path(self._get_edit_schema_name(resource_name)) + + def _get_paginated_schema_path(self, resource_name): + return schema_name2path(self._get_paginated_schema_name(resource_name)) + + def _get_new_schema_path(self, resource_name): + return schema_name2path(self._get_new_schema_name(resource_name)) + + def _get_search_schema_path(self, resource_name): + return schema_name2path(self._get_search_schema_name(resource_name)) + + def _get_query_schema_path(self, resource_name): + return schema_name2path(self._get_query_schema_name(resource_name)) + + def _get_filter_schema_path(self, resource_name): + return schema_name2path(self._get_filter_schema_name(resource_name)) + + def _get_object_filter_schema_path(self, resource_name): + return schema_name2path(self._get_object_filter_schema_name(resource_name)) + + def _get_array_filter_schema_path(self, resource_name): + return schema_name2path(self._get_array_filter_schema_name(resource_name)) + + def _get_coordinative_filter_schema_path(self, resource_name): + return schema_name2path(self._get_coordinative_filter_schema_name( + resource_name)) + + def _get_negative_filter_schema_path(self, resource_name): + return schema_name2path(self._get_negative_filter_schema_name( + resource_name)) + + def _get_simple_filter_schema_path(self, resource_name): + return schema_name2path(self._get_simple_filter_schema_name( + resource_name)) + + def _get_related_filter_schema_path(self, resource_name, attribute): + return schema_name2path(self._get_related_filter_schema_name( + resource_name, attribute)) + + def _get_related_filter_schema_paths_refs(self, resource_name, + resource_cfg): + return [ + {'$ref': + self._get_related_filter_schema_path(resource_name, attribute)} + for attribute, _ + in self._get_relational_attributes(resource_name, resource_cfg)] + + def _get_new_search_schema_path(self, resource_name): + return schema_name2path(self._get_new_search_schema_name(resource_name)) + + def _get_paginator_schema_path(self): + return schema_name2path(PAGINATOR_SCHEMA_NAME) + + # ========================================================================= + # Response getters + # ========================================================================= + + def _get_create_responses(self, resource_name): + """Return an OpenAPI ``responses`` object for the "create" action on + resource ``resource_name``. + """ + return OrderedDict([ + ('201', get_ref_response( + description='Creation of a new {} succeeded.'.format( + resource_name), + ref=self._get_read_schema_path(resource_name))), + ('400', get_ref_response( + description='Creation of a new {} failed.'.format( + resource_name), + ref=get_error_schema_path())), + ]) + + def _get_delete_responses(self, resource_name): + return OrderedDict([ + ('200', get_ref_response( + description='Deletion of the {} resource succeeded.'.format( + resource_name), + ref=self._get_read_schema_path(resource_name))), + ('404', get_ref_response( + description='Deletion of the {} resource failed because there' + ' is no {} with the specified pk.'.format( + resource_name, resource_name), + ref=get_error_schema_path())), + ('403', get_ref_response( + description='Deletion of the {} resource failed because user is' + ' forbidden from performing this action'.format( + resource_name), + ref=get_error_schema_path())), + ]) + + def _get_edit_responses(self, resource_name): + """Return an OpenAPI ``responses`` object for the "edit" action on + resource ``resource_name``. + """ + return OrderedDict([ + ('200', get_ref_response( + description='Request for the data needed to edit a(n) {}' + ' resource succeeded.'.format(resource_name), + ref=self._get_edit_schema_path(resource_name))), + ('404', get_ref_response( + description='Request for the data needed to edit a(n) {}' + ' failed because there is no {} resource with the' + ' specified pk'.format( + resource_name, resource_name), + ref=get_error_schema_path())), + ('403', get_ref_response( + description='Request for the data needed to edit a(n) {}' + ' failed because the user is forbidden from editing' + ' this {} resource.'.format( + resource_name, resource_name), + ref=get_error_schema_path())), + ]) + + def _get_index_responses(self, resource_name): + """Return an OpenAPI ``responses`` object for the "index" action on + resource ``resource_name``. + """ + rsrc_collection_name = self.inflp.plural(resource_name) + return OrderedDict([ + ('200', get_ref_response( + description='Request for a collection of {} succeeded.'.format( + rsrc_collection_name), + ref=self._get_paginated_schema_path(resource_name))), + ('400', get_ref_response( + description='Request for a collection of {} failed.'.format( + rsrc_collection_name), + ref=get_error_schema_path())), + ]) + + def _get_new_responses(self, resource_name): + """Return an OpenAPI ``responses`` object for the "new" action on + resource ``resource_name``. + """ + return OrderedDict([ + ('200', get_ref_response( + description='Request for the data needed to create a' + ' new {} resource succeeded.'.format(resource_name), + ref=self._get_new_schema_path(resource_name))), + ]) + + def _get_show_responses(self, resource_name): + """Return an OpenAPI ``responses`` object for the "show" action on + resource ``resource_name``. + """ + return OrderedDict([ + ('200', get_ref_response( + description='Request for a single {} succeeded.'.format( + resource_name), + ref=self._get_read_schema_path(resource_name))), + ('404', get_ref_response( + description='Request for a single {} failed because there is no' + ' {} resource with the specified pk.'.format( + resource_name, resource_name), + ref=get_error_schema_path())), + ('403', get_ref_response( + description='Request for a single {} failed because the user is' + ' forbidden from viewing this {} resource.'.format( + resource_name, resource_name), + ref=get_error_schema_path())), + ]) + + def _get_update_responses(self, resource_name): + """Return an OpenAPI ``responses`` object for the "update" action on + resource ``resource_name``. + """ + return OrderedDict([ + ('200', get_ref_response( + description='Updating of an existing {} resource' + ' succeeded.'.format(resource_name), + ref=self._get_edit_schema_path(resource_name))), + ('404', get_ref_response( + description='Updating of an existing {} resource failed because' + ' there is no {} with the specified pk.'.format( + resource_name, resource_name), + ref=get_error_schema_path())), + ('403', get_ref_response( + description='Updating of an existing {} resource failed because' + ' the user is forbidden from updating this' + ' {} resource.'.format(resource_name, resource_name), + ref=get_error_schema_path())), + ('400', get_ref_response( + description='Updating of an existing {} resource' + ' failed.'.format(resource_name), + ref=get_error_schema_path())), + ]) + + def _get_search_responses(self, resource_name): + """Return an OpenAPI ``responses`` object for the "search" action on + resource ``resource_name``. + """ + rsrc_collection_name = self.inflp.plural(resource_name) + return OrderedDict([ + ('200', get_ref_response( + description='Search across all {} succeeded.'.format( + rsrc_collection_name), + ref=self._get_paginated_schema_path(resource_name))), + ('400', get_ref_response( + description='Search across all {} failed.'.format( + rsrc_collection_name), + ref=get_error_schema_path())), + ]) + + def _get_search_post_responses(self, resource_name): + return self._get_search_responses(resource_name) + + def _get_new_search_responses(self, resource_name): + rsrc_collection_name = self.inflp.plural(resource_name) + return OrderedDict([ + ('200', get_ref_response( + description='Request to get the data needed to' + ' search across all {} succeeded.'.format( + rsrc_collection_name), + ref=self._get_new_search_schema_path(resource_name))), + ]) + + # ========================================================================= + # Request body getters + # ========================================================================= + + def _get_create_request_body(self, resource_name): + return OrderedDict([ + ('description', 'JSON object required to create a new {}'.format( + resource_name)), + ('required', True), + ('content', { + 'application/json': { + 'schema': { + '$ref': + self._get_create_schema_path(resource_name) + } + } + }), + ]) + + def _get_update_request_body(self, resource_name): + return OrderedDict([ + ('description', 'JSON object required to update an existing' + ' {}'.format(resource_name)), + ('required', True), + ('content', { + 'application/json': { + 'schema': { + '$ref': + self._get_update_schema_path(resource_name) + } + } + }), + ]) + + def _get_search_request_body(self, resource_name, examples=None): + rsrc_collection_name = self.inflp.plural(resource_name) + application_json = OrderedDict([ + ('schema', {'$ref': + self._get_search_schema_path(resource_name)}), + ]) + if examples: + application_json['example'] = examples + return OrderedDict([ + ('description', 'JSON object required to search over all {}'.format( + rsrc_collection_name)), + ('required', True), + ('content', {'application/json': application_json}), + ]) + + def _get_search_post_request_body(self, resource_name, examples=None): + return self._get_search_request_body(resource_name, examples=examples) + + def _get_index_parameters(self, *args): + """Return an OpenAPI ``parameters`` object for the "index" action.""" + return [ + {'$ref': '#/components/parameters/items_per_page'}, + {'$ref': '#/components/parameters/page'}, + {'$ref': '#/components/parameters/order_by_attribute'}, + {'$ref': '#/components/parameters/order_by_subattribute'}, + {'$ref': '#/components/parameters/order_by_direction'}, + ] + + def to_yaml(self, open_api_spec): + """Return the input OrderedDict as a YAML string.""" + return yaml_dump(open_api_spec) + + def to_json(self, open_api_spec): + """Return the input OrderedDict as a JSON string.""" + return json.dumps(open_api_spec) + + def _get_api_info(self): + """Return an OrderedDict for the top-level ``info`` attribute.""" + return OrderedDict([ + ('version', self.api_version), + ('title', self._get_api_title()), + ('description', self._get_api_description()), + ]) + + def get_dflt_server_path(self): + return '{}{}'.format( + self.path_prefix, self.get_api_version_slug()) + + def _get_api_servers(self): + """Return a list of OrderedDicts for the top-level ``servers`` + attribute. + """ + return [ + OrderedDict([ + ('url', self.get_dflt_server_path()), + ('description', self._get_dflt_server_description()), + ]), + ] + + def _get_api_security(self): + """Return a list of OrderedDicts for the top-level ``security`` + attribute. + """ + return [ + OrderedDict([('ApiKeyAuth', [])]), + ] + + def _get_api_security_schemes(self): + """Return an OrderedDict for the ``components.securitySchemes`` + attribute. + """ + return OrderedDict([ + ('ApiKeyAuth', OrderedDict([ + ('type', 'apiKey'), + ('in', 'header'), + ('name', 'Authorization'), + # Note: the value of this header must be of the form + # ``ApiKey :``. + # TODO: the OpenAPI spec does not allow a pattern here ... + # ('pattern', r'ApiKey (?\w+):(?\w+)') + ])), + ]) + + def _get_api_remple_parameters(self): + """Return an OrderedDict of OrderedDicts for OpenAPI + ``components.parameters``; these are for the Remple-internal + internal schemata like the paginator. + """ + parameters = OrderedDict() + for schema in schemata: + for parameter in schema.extract_parameters(): + parameter_name = parameter['name'] + parameters[parameter_name] = parameter + for parameter_name, parameter in self._get_order_by_parameters().items(): + parameters[parameter_name] = parameter + return parameters + + def _get_order_by_parameters(self): + """Return a dict of OpenAPI query ``parameters`` for ordering the + results of an idnex request. + """ + return { + 'order_by_attribute': OrderedDict([ + ('in', 'query'), + ('name', 'order_by_attribute'), + ('schema', {'type': 'string'}), + ('description', 'Attribute of the resource that' + ' view results should be ordered by.'), + ('required', False), + ]), + 'order_by_subattribute': OrderedDict([ + ('in', 'query'), + ('name', 'order_by_subattribute'), + ('schema', {'type': 'string'}), + ('required', False), + ('description', 'Attribute of the related attribute' + ' order_by_attribute of the resource' + ' that view results should be' + ' ordered by.'), + ]), + 'order_by_direction': OrderedDict([ + ('in', 'query'), + ('name', 'order_by_direction'), + ('schema', OrderedDict([ + ('type', 'string'), + ('enum', [obd for obd in QueryBuilder.order_by_directions + if obd]), + ])), + ('required', False), + ('description', 'The direction of the ordering; omitting this' + ' parameter means ascending direction.'), + ]) + } + + def _get_error_schema(self): + return OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('error', OrderedDict([ + ('type', 'string'), + ])), + ])), + ('required', ['error']), + ]) + + def _get_custom_endpoints(self, resource_cls): + """Return the custom endpoints defined on ``resource_cls``. + """ + ret = [] + for attr_name in dir(resource_cls): + if attr_name.startswith('_'): + continue + custom_endpoint = getattr(resource_cls, attr_name) + if isinstance(custom_endpoint, CustomEndPoint): + ret.append(custom_endpoint) + return ret + + def _get_cust_endpt_schemata(self, resource_name, resource_cfg): + """Return an interable of custom endpoint schema names and schemata for + custom endpoints defined on the resource class. + """ + ret = [] + for custom_endpoint in self._get_custom_endpoints( + resource_cfg['resource_cls']): + for schema_name, schema in custom_endpoint.schemata.items(): + ret.append((schema_name, schema)) + return ret + + def _get_schemas(self): + """Return an OpenAPI ``schemas`` OrderedDict. + + It contains a "View", a "Create", and an "Update" schema for each + resource in ``self.resources``, e.g., ``LocationView``, + ``LocationCreate``, and ``LocationUpdate``. The create and update + schemata are only included if the resources is not read-only. The view + schema is constructed by introspecting the Django model, while the + create and update schemata are constructed by introspecting the relevant + Formencode schemata attached as class attributes on the resource class. + """ + schemas = OrderedDict([ + ('ErrorSchema', self._get_error_schema()), + ('PaginatorSchema', self._get_paginator_schema()), + ]) + for resource_name, resource_cfg in sorted(self.resources.items()): + read_schema_name, read_schema = self._get_read_schema( + resource_name, resource_cfg) + schemas[read_schema_name] = read_schema + paginated_schema_name, paginated_schema = ( + self._get_paginated_subset_schema( + resource_name, resource_cfg, read_schema)) + schemas[paginated_schema_name] = paginated_schema + for cust_endpt_schema_name, cust_endpt_schema in ( + self._get_cust_endpt_schemata(resource_name, resource_cfg)): + schemas[cust_endpt_schema_name] = cust_endpt_schema + # Only mutable resources need the following schemata + if issubclass(resource_cfg['resource_cls'], Resources): + create_schema_name, create_schema = self._get_create_schema( + resource_name, resource_cfg, read_schema) + schemas[create_schema_name] = create_schema + update_schema_name, update_schema = self._get_update_schema( + resource_name, resource_cfg, read_schema) + schemas[update_schema_name] = update_schema + new_schema_name, new_schema = ( + self._get_new_schema(resource_name, resource_cfg, + read_schema)) + schemas[new_schema_name] = new_schema + edit_schema_name, edit_schema = ( + self._get_edit_schema(resource_name, resource_cfg, + read_schema)) + schemas[edit_schema_name] = edit_schema + if resource_cfg.get('searchable', True): + filter_schemas = ( + self._get_filter_schemas(resource_name, resource_cfg, + read_schema)) + for filter_schema_name, filter_schema in filter_schemas: + schemas[filter_schema_name] = filter_schema + query_schema_name, query_schema = ( + self._get_query_schema(resource_name, resource_cfg, + read_schema)) + schemas[query_schema_name] = query_schema + + search_schema_name, search_schema = ( + self._get_search_schema(resource_name, resource_cfg, + read_schema)) + schemas[search_schema_name] = search_schema + new_search_schema_name, new_search_schema = ( + self._get_new_search_schema(resource_name, resource_cfg, + read_schema)) + schemas[new_search_schema_name] = new_search_schema + return schemas + + def _get_paginator_schema(self): + return OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('count', {'type': 'integer'}), + ('page', {'type': 'integer', 'default': 1, 'minimum': 1}), + ('items_per_page', {'type': 'integer', 'default': 10, 'minimum': 1}), + ])), + ('required', ['page', 'items_per_page']), + ]) + + def _get_paginated_subset_schema(self, resource_name, resource_cfg, + read_schema): + paginated_schema_name = self._get_paginated_schema_name(resource_name) + paginated_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('paginator', {'$ref': self._get_paginator_schema_path()}), + ('items', OrderedDict([ + ('type', 'array'), + ('items', + {'$ref': + self._get_read_schema_path(resource_name)}), + ])), + ])), + ('required', ['paginator', 'items']), + ]) + return paginated_schema_name, paginated_schema + + def _get_edit_schema(self, resource_name, resource_cfg, read_schema): + edit_schema_name = self._get_edit_schema_name(resource_name) + edit_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('data', { + '$ref': + self._get_new_schema_path(resource_name)}), + ('resource', { + '$ref': + self._get_read_schema_path(resource_name)}), + ])), + ('required', ['data', 'resource']), + ]) + return edit_schema_name, edit_schema + + def _get_filter_schemas(self, resource_name, resource_cfg, read_schema): + """Each resource will generate multiple filter schemas: a coordinative + one, a negative one, a simple one and zero or more related (relational) + ones, depending on how many other resources (models) it is related to. + This method returns these schemas as a list. + + Note, there is a shorthand filter schema, which is based on arrays and + which is exemplified via the following (and which canNOT be described + using the OpenAPI spec):: + + ["and", [["Location", "purpose", "=", "AS"], + ["Location", "description", "regex", "2018"]]] + ["not", ["Location", "purpose", "=", "AS"]] + ["Location", "purpose", "=", "AS"] + ["Location", "space", "path", "like", "/usr/data/%"] + + Then there is the long-hand filter schema, which is based on objects + and which is exemplified via the following (which CAN be described + using the OpenAPI spec):: + + {"conjunction": "and", + "complement": [ + {"attribute": "purpose", + "relation": "=", + "value": "AS"}, + {"attribute": "description", + "relation": "regex", + "value": "2018"}]} + + {"negation": "not", + "complement": {"attribute": "purpose", + "relation": "=", + "value": "AS"}} + + {"attribute": "purpose", "relation": "=", "value": "AS"} + + {"attribute": "space", + "subattribute": "path", + "relation": "like", + "value": "/usr/data/%"} + + Note that the filter schema is inherently recursive and the Swagger-ui + web app cannot currently fully display a recursive schema. See + https://github.com/swagger-api/swagger-ui/issues/1679. + """ + return ( + [self._get_simple_filter_schema(resource_name, resource_cfg)] + + self._get_related_filter_schemas(resource_name, resource_cfg) + + [self._get_coordinative_filter_schema(resource_name, resource_cfg), + self._get_negative_filter_schema(resource_name, resource_cfg), + self._get_array_filter_schema(resource_name, resource_cfg), + self._get_object_filter_schema(resource_name, resource_cfg), + self._get_filter_schema(resource_name, resource_cfg)]) + + def _get_simple_filter_schema(self, resource_name, resource_cfg): + model_cls_name = resource_cfg['resource_cls'].model_cls.__name__ + simple_schema_name = self._get_simple_filter_schema_name(resource_name) + simple_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('attribute', OrderedDict([ + ('type', 'string'), + ('enum', self._get_simple_attributes( + model_cls_name, resource_cfg)), + ])), + ('relation', OrderedDict([ + ('type', 'string'), + ('enum', self._get_relations(resource_name, resource_cfg)), + ])), + ('value', {'anyOf': [{'type': 'string'}, + {'type': 'number'}, + {'type': 'boolean'}]}), + ])), + ]) + return simple_schema_name, simple_schema + + def _get_query_schemata(self, resource_cfg): + resource_cls = resource_cfg['resource_cls'] + query_builder = resource_cls._get_query_builder() + return query_builder.schemata + + def _get_relational_attributes(self, resource_name, resource_cfg): + model_name = resource_cfg['resource_cls'].model_cls.__name__ + query_schemata = self._get_query_schemata(resource_cfg) + return [(attr, cfg.get('foreign_model')) + for attr, cfg in + query_schemata[model_name].items() + if cfg.get('foreign_model')] + + def _get_simple_attributes(self, model_cls_name, resource_cfg): + query_schemata = self._get_query_schemata(resource_cfg) + return [attr for attr, cfg in + query_schemata[model_cls_name].items() + if not cfg.get('foreign_model')] + + def _get_related_attributes(self, resource_cfg, related_model): + query_schemata = self._get_query_schemata(resource_cfg) + return [attr for attr, cfg in query_schemata[related_model].items() + if not cfg.get('foreign_model')] + + def _get_relations(self, resource_name, resource_cfg): + resource_cls = resource_cfg['resource_cls'] + query_builder = resource_cls._get_query_builder() + return list(query_builder.relations) + + def _get_related_filter_schemas(self, resource_name, resource_cfg): + schemas = [] + for attribute, related_model_cls in self._get_relational_attributes( + resource_name, resource_cfg): + related_schema_name = self._get_related_filter_schema_name( + resource_name, attribute) + related_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('attribute', OrderedDict([ + ('type', 'string'), + ('enum', [attribute]), + ])), + ('subattribute', OrderedDict([ + ('type', 'string'), + ('enum', self._get_related_attributes( + resource_cfg, related_model_cls)), + ])), + ('relation', OrderedDict([ + ('type', 'string'), + ('enum', self._get_relations(resource_name, resource_cfg)), + ])), + ('value', {'anyOf': [{'type': 'string'}, + {'type': 'number'}, + {'type': 'boolean'}]}), + ])), + ]) + schemas.append((related_schema_name, related_schema)) + return schemas + + def _get_coordinative_filter_schema(self, resource_name, resource_cfg): + coord_schema_name = self._get_coordinative_filter_schema_name(resource_name) + coord_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('conjunction', OrderedDict([ + ('type', 'string'), + ('enum', ['and', 'or']), + ])), + ('complement', OrderedDict([ + ('type', 'array'), + ('items', {'$ref': + self._get_filter_schema_path(resource_name)}), + ])), + ])), + ]) + return coord_schema_name, coord_schema + + def _get_negative_filter_schema(self, resource_name, resource_cfg): + neg_schema_name = self._get_negative_filter_schema_name(resource_name) + neg_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('negation', OrderedDict([ + ('type', 'string'), + ('enum', ['not']), + ])), + ('complement', + {'$ref': self._get_filter_schema_path(resource_name)}), + ])), + ]) + return neg_schema_name, neg_schema + + def _get_filter_schema(self, resource_name, resource_cfg): + filter_schema_name = self._get_filter_schema_name(resource_name) + filter_schema = { + 'oneOf': [ + {'$ref': self._get_object_filter_schema_path(resource_name)}, + {'$ref': self._get_array_filter_schema_path(resource_name)}, + ] + } + return filter_schema_name, filter_schema + + def _get_array_filter_schema(self, resource_name, resource_cfg): + array_filter_schema_name = self._get_array_filter_schema_name(resource_name) + array_filter_schema = OrderedDict([ + ('type', 'array'), + ('items', {'oneOf': [{'type': 'string'}, + {'type': 'integer'}]}), + ]) + return array_filter_schema_name, array_filter_schema + + def _get_object_filter_schema(self, resource_name, resource_cfg): + object_filter_schema_name = self._get_object_filter_schema_name( + resource_name) + object_filter_schema = { + 'oneOf': [ + {'$ref': self._get_coordinative_filter_schema_path( + resource_name)}, + {'$ref': self._get_negative_filter_schema_path(resource_name)}, + {'$ref': self._get_simple_filter_schema_path(resource_name)}, + ] + self._get_related_filter_schema_paths_refs(resource_name, + resource_cfg) + } + return object_filter_schema_name, object_filter_schema + + def _get_query_schema(self, resource_name, resource_cfg, read_schema): + query_schema_name = self._get_query_schema_name(resource_name) + query_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('filter', {'$ref': + self._get_filter_schema_path(resource_name)}), + ('order_by', OrderedDict([ + ('type', 'array'), + ('items', OrderedDict([ + ('type', 'array'), + ('items', OrderedDict([ + ('type', 'string'), + ])), + ])), + ])), + ])), + ('required', ['filter']), + ]) + return query_schema_name, query_schema + + def _get_search_schema(self, resource_name, resource_cfg, read_schema): + search_schema_name = self._get_search_schema_name(resource_name) + search_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('query', {'$ref': self._get_query_schema_path(resource_name)}), + ('paginator', {'$ref': self._get_paginator_schema_path()}), + ])), + ('required', ['query']), + ]) + return search_schema_name, search_schema + + def _get_new_search_schema(self, resource_name, resource_cfg, read_schema): + new_search_schema_name = self._get_new_search_schema_name(resource_name) + new_search_schema = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('search_parameters', OrderedDict([ + # TODO: this is not a string, but an object with + # "attributes" and "relations". + ('type', 'string'), + ])), + ])), + ('required', ['search_parameters']), + ]) + return new_search_schema_name, new_search_schema + + def _get_new_schema(self, resource_name, resource_cfg, read_schema): + new_schema_name = self._get_new_schema_name(resource_name) + new_schema = OrderedDict([('type', 'object')]) + properties = OrderedDict() + resource_cls = resource_cfg['resource_cls'] + resource_inst = resource_cls( + None, server_path=self.get_dflt_server_path(), + other_resources=self.resources) + required_fields = [] + for field_name in resource_inst._get_new_edit_collections(): + properties[field_name] = OrderedDict([ + ('type', 'array'), + ('items', OrderedDict([ + ('type', 'string'), + ('format', 'URI of a(n) {} resource'.format(field_name)), + ])), + ]) + required_fields.append(field_name) + if properties: + new_schema['properties'] = properties + new_schema['required'] = required_fields + return new_schema_name, new_schema + + def _get_create_schema(self, resource_name, resource_cfg, read_schema): + """Return a create schema for the resource named ``resource_name``. + + The create schema describes what is needed to create an instance of the + input-named resource. + """ + create_schema_name = self._get_create_schema_name(resource_name) + resource_cls = resource_cfg['resource_cls'] + schema_cls = resource_cls.get_create_schema_cls() + create_schema = self._get_create_update_schema( + resource_name, resource_cfg, read_schema, schema_cls) + return create_schema_name, create_schema + + def _get_update_schema(self, resource_name, resource_cfg, read_schema): + """Return an update schema for the resource named ``resource_name``. + + The update schema describes what is needed to update an instance of the + input-named resource. + + TODO: should every parameter in an update request be optional, given + that the resource being updated is presumably valid? + + """ + update_schema_name = self._get_update_schema_name(resource_name) + resource_cls = resource_cfg['resource_cls'] + schema_cls = resource_cls.get_update_schema_cls() + update_schema = self._get_create_update_schema( + resource_name, resource_cfg, read_schema, schema_cls) + return update_schema_name, update_schema + + @staticmethod + def _get_create_update_schema(resource_name, resource_cfg, + read_schema, schema_cls): + schema_properties = {} + schema = {'type': 'object'} + fields = schema_cls.fields + required_fields = [] + for field_name, field in fields.items(): + field_dict = { + ResourceURI: single_reference_mut_field_dict, + ForEach: multi_reference_mut_field_dict, + OneOf: enum_field_dict, + Any: disjunctive_field_dict, + }.get(type(field), scalar_mut_field_dict)( + **{'field': field}) + if not field_dict.get('type') and not field_dict.get('anyOf'): + logger.warning('{}.{} is of unknown type (class {})'.format( + resource_name, field_name, type(field))) + continue + default = read_schema['properties'].get(field_name, {}).get( + 'default', NOT_PROVIDED) + if default != NOT_PROVIDED: + field_dict['default'] = default + if field_name in read_schema.get('required', []): + required_fields.append(field_name) + description = read_schema['properties'].get(field_name, {}).get( + 'description') + if description: + field_dict['description'] = description + schema_properties[field_name] = field_dict + schema['properties'] = schema_properties + if required_fields: + schema['required'] = required_fields + return schema + + def _get_read_schema(self, resource_name, resource_cfg): + """Return a read schema for the resource named ``resource_name``. + + The read schema describes what is returned by the server as a + representation of an instance of the input-named resource. + """ + read_schema_name = self._get_read_schema_name(resource_name) + read_schema_properties = {} + read_schema = {'type': 'object'} + resource_cls = resource_cfg['resource_cls'] + model_cls = resource_cls.model_cls + fields = model_cls._meta.get_fields() + required_fields = [] + for field in fields: + field_name = field.name + field_name, field_dict = { + ForeignKey: single_reference_field_dict, + OneToOneRel: single_reference_field_dict, + ManyToManyField: multi_reference_field_dict, + ManyToManyRel: multi_reference_field_dict, + ManyToOneRel: multi_reference_field_dict, + }.get(type(field), scalar_field_dict)( + field_name=field_name, field=field) + choices = get_choices(field) + if choices: + field_dict['enum'] = choices + if getattr(field, 'null', False) is True: + field_dict['nullable'] = True + default = get_default(field) + required, field_is_null = get_required(field, default) + default = get_default_for_type(field, default, field_is_null) + if default != NOT_PROVIDED: + field_dict['default'] = default + if required: + required_fields.append(field_name) + if not field_dict.get('type'): + logger.warning('{}.{} is of unknown type (class {})'.format( + resource_name, field.name, type(field))) + continue + description = getattr(field, 'help_text', None) + if description: + if callable(description): + description = description() + field_dict['description'] = str(description) + read_schema_properties[field_name] = field_dict + read_schema['properties'] = read_schema_properties + if required_fields: + read_schema['required'] = required_fields + return read_schema_name, read_schema + + def get_api_version_slug(self): + try: + return self._get_api_version_slug(self.api_version) + except ValueError: + return self.api_version + + @staticmethod + def _get_api_version_slug(version): + """Given a version number like 'X.Y.Z', return a slug representation of + it. E.g., + + >>> get_api_version_slug('3.0.0') + ... 'v3' + >>> get_api_version_slug('3.0.1') + ... 'v3_0_1' + >>> get_api_version_slug('3.0') + ... 'v3' + >>> get_api_version_slug('3.9') + ... 'v3_9' + """ + parts = version.strip().split('.') + new_parts = [] + for index, part in enumerate(parts): + part_int = int(part) + if part_int: + new_parts.append(part) + else: + parts_to_right = parts[index + 1:] + non_empty_ptr = [p for p in parts_to_right if int(p)] + if non_empty_ptr: + new_parts.append(part) + return 'v{}'.format('_'.join(new_parts)) + + +def single_reference_field_dict(**kwargs): + """Return an OpenAPI OrderedDict for a Django ForeingKey or OneToOneRel. + """ + if isinstance(kwargs['field'], OneToOneRel): + return (kwargs['field'].get_accessor_name(), + OrderedDict([('type', 'string'), ('format', 'uri')])) + return (kwargs['field_name'], + OrderedDict([('type', 'string'), ('format', 'uri')])) + + +def multi_reference_field_dict(**kwargs): + """Return an OpenAPI OrderedDict for a Django ManyToManyField, + ManyToManyRel, or ManyToOneRel. + """ + field_name = kwargs.get('field_name') + if isinstance(kwargs['field'], ManyToOneRel): + field_name = kwargs['field'].get_accessor_name() + return field_name, OrderedDict([ + ('type', 'array'), + ('items', OrderedDict([ + ('type', 'string'), ('format', 'uri')]))]) + + +def scalar_field_dict(**kwargs): + """Return an OpenAPI OrderedDict for a Django scalar field, e.g., a string + or an int. + """ + field = kwargs.get('field') + openapi_type = django_field2openapi_type.get( + type(field)) + field_dict = OrderedDict([('type', openapi_type)]) + openapi_format = django_field2openapi_format.get( + type(field)) + if openapi_format: + field_dict['format'] = openapi_format + return kwargs['field_name'], field_dict + + +def get_choices(field): + choices = getattr(field, 'choices', None) + if choices: + return [c[0] for c in choices] + return None + + +def get_default(field): + return getattr(field, 'default', NOT_PROVIDED) + + +def get_required(field, default): + field_is_null = getattr(field, 'null', False) + if isinstance(field, (OneToOneRel, ManyToManyRel, ManyToOneRel)): + return False, field_is_null + field_is_blank = getattr(field, 'blank', False) + if (not field_is_blank) and (default == NOT_PROVIDED): + return True, field_is_null + return False, field_is_null + + +def _get_format_from_resource_uri_validator(resource_uri_validator): + resource_name = resource_uri_validator.model_cls.__name__.lower() + return 'URI of a {} resource'.format(resource_name) + + +def single_reference_mut_field_dict(**kwargs): + format_ = _get_format_from_resource_uri_validator(kwargs['field']) + return OrderedDict([('type', 'string'), + ('format', format_)]) + + +def multi_reference_mut_field_dict(**kwargs): + format_ = _get_format_from_resource_uri_validator( + kwargs['field'].validators[0]) + return OrderedDict([ + ('type', 'array'), + ('items', OrderedDict([ + ('type', 'string'), + ('format', format_), + ])), + ]) + + +def enum_field_dict(**kwargs): + enum = list(kwargs['field'].list) + enum_type = type(enum[0]) + return OrderedDict([ + ('type', python_type2openapi_type.get(enum_type, 'string')), + ('enum', enum)]) + + +def disjunctive_field_dict(**kwargs): + anyOf = [] + field_dict = OrderedDict([('anyOf', anyOf)]) + for validator in kwargs['field'].validators: + validator_dict = OrderedDict() + validator_dict['type'] = formencode_field2openapi_type.get( + type(validator), 'string') + validator_format = ( + formencode_field2openapi_format.get( + type(validator))) + if validator_format: + validator_dict['format'] = validator_format + anyOf.append(validator_dict) + return field_dict + + +def scalar_mut_field_dict(**kwargs): + openapi_type = formencode_field2openapi_type.get( + type(kwargs['field'])) + field_dict = OrderedDict([('type', openapi_type)]) + openapi_format = formencode_field2openapi_format.get( + type(kwargs['field'])) + if openapi_format: + field_dict['format'] = openapi_format + field = kwargs['field'] + field_min = getattr(field, 'min', None) + field_max = getattr(field, 'max', None) + if field_min: + field_dict['minLength'] = field_min + if field_max: + field_dict['maxLength'] = field_max + return field_dict + + +def get_collection_targeting_openapi_path(rsrc_collection_name, + modifiers=None): + """Return an OpenAPI path of the form '//' + with optional trailing modifiers, e.g., '//new/'. + """ + if modifiers: + return r'/{rsrc_collection_name}/{modifiers}/'.format( + rsrc_collection_name=rsrc_collection_name, + modifiers='/'.join(modifiers)) + return r'/{rsrc_collection_name}/'.format( + rsrc_collection_name=rsrc_collection_name) + + +def get_member_targeting_openapi_path(resource_name, rsrc_collection_name, + pk_patt, resource_cls, modifiers=None): + """Return a regex of the form '^//$' + with optional modifiers after the pk, e.g., + '^//edit/$'. + """ + pk_type = 'string' + pk_format = 'uuid' + primary_key = getattr(resource_cls, 'primary_key', 'uuid') + if primary_key == 'id': + pk_type = 'integer' + pk_format = 'database id' + path_params = [OrderedDict([ + ('in', 'path'), + ('name', 'pk'), + ('required', True), + ('schema', OrderedDict([ + ('type', pk_type), + ('format', pk_format), + ])), + ('description', 'The primary key of the {}.'.format(resource_name)), + ])] + if modifiers: + path = (r'/{rsrc_collection_name}/{{pk}}/' + r'{modifiers}/'.format( + rsrc_collection_name=rsrc_collection_name, + pk_patt=pk_patt, + modifiers='/'.join(modifiers))) + else: + path = r'/{rsrc_collection_name}/{{pk}}/'.format( + rsrc_collection_name=rsrc_collection_name, pk_patt=pk_patt) + return path, path_params + + +def _summarize(action, resource_name, rsrc_collection_name): + create_summary = 'Create a new {}.'.format(resource_name) + delete_summary = 'Delete an existing {}.'.format(resource_name) + edit_summary = 'Get the data needed to update an existing {}.'.format( + resource_name) + index_summary = 'View all {}.'.format(rsrc_collection_name) + new_summary = 'Get the data needed to create a new {}.'.format( + resource_name) + show_summary = 'View an existing {}.'.format(resource_name) + update_summary = 'Update an existing {}.'.format(resource_name) + search_summary = 'Search over all {}.'.format(rsrc_collection_name) + search_post_summary = search_summary + new_search_summary = 'Get the data needed to search over all {}.'.format( + rsrc_collection_name) + return { + 'create': (create_summary, create_summary), + 'delete': (delete_summary, delete_summary), + 'edit': (edit_summary, edit_summary), + 'index': (index_summary, index_summary), + 'new': (new_summary, new_summary), + 'show': (show_summary, show_summary), + 'update': (update_summary, update_summary), + 'search': (search_summary, search_summary), + 'search_post': (search_post_summary, search_post_summary), + 'new_search': (new_search_summary, new_search_summary), + }[action] + + +def schema_name2path(schema_name): + return '{}{}'.format(SCHEMAS_ABS_PATH, schema_name) + + +def get_error_schema_path(): + return schema_name2path(ERROR_SCHEMA_NAME) + + +def get_ref_response(description, ref, examples=None): + """Given a description string and a ref(erence) path to an existing + schema, return the dict describing that response. + """ + application_json = OrderedDict([ + ('schema', OrderedDict([ + ('$ref', ref), + ])), + ]) + if examples: + application_json['examples'] = examples + return OrderedDict([ + ('description', description), + ('content', OrderedDict([ + ('application/json', application_json), + ])), + ]) + + +def _get_operation_id(action, resource_name): + operation_name = { + 'index': 'get_many', + 'show': 'get', + 'new': 'data_for_new', + 'edit': 'data_for_edit', + 'search_post': 'search', + }.get(action, action) + return '{}.{}'.format(operation_name, resource_name) + + +def get_default_for_type(field, default, field_is_null): + if field_is_null: + return None + return { + 'integer': 0, + 'float': 0.0, + 'boolean': False, + }.get(django_field2openapi_type.get(type(field), 'string'), '') diff --git a/storage_service/locations/api/beta/remple/querybuilder.py b/storage_service/locations/api/beta/remple/querybuilder.py new file mode 100644 index 000000000..5d37ae91a --- /dev/null +++ b/storage_service/locations/api/beta/remple/querybuilder.py @@ -0,0 +1,681 @@ +"""This module defines :class:`QueryBuilder`. A ``QueryBuilder`` instance is +used to build a query object from simple data structure, viz. nested lists. + +The primary public method is ``get_query_set``. It takes a dict with +``'filter'`` and ``'order_by'`` keys. The filter key evaluates to a filter +expression represented as a Python list (or JSON array). The ``get_query_set`` +method returns a Django QuerySet instance. Errors in the Python filter +expression will cause custom ``SearchParseError``s to be raised. + +The searchable models and their attributes (scalars & collections) are defined +in QueryBuilder.schema. + +Simple filter expressions are lists with four or five items. Complex filter +expressions are constructed via lists whose first element is one of the boolean +keywords 'and', 'or', 'not' and whose second element is a filter expression or +a list thereof (in the case of 'and' and 'or'). The examples below show a +filter expression accepted by ``QueryBuilder('Package').get_query_expression`` +on the first line followed by the equivalent Django Q expression. Note that +the target model of the QueryBuilder defaults to 'Package' so all queries will +target the Package model by default. + +1. Queries on scalar attributes:: + + >>> ['Package', 'description', 'like', '%a%'] + >>> Q(description__contains='%a%') + +2. Queries on scalar attributes of related resources:: + + >>> ['Package', 'origin_pipeline', 'description', 'regex', '^[JS]'] + >>> Q(origin_pipeline__description__regex='^[JS]') + +3. Queries based on the presence/absence of a related resource:: + + >>> ['Package', 'origin_pipeline', '=', None] + >>> Q(origin_pipeline__isnull==True) + >>> ['Package', 'origin_pipeline', '!=', None] + >>> Q(origin_pipeline__isnull==False) + +4. Queries over scalar attributes of related collections of resources:: + + >>> ['Package', 'replicas', 'uuid', 'in', [uuid1, uuid2]] + >>> Q(replicas__uuid__in=[uuid1, uuid2]))) + +5. Negation:: + + >>> ['not', ['Package', 'description', 'like', '%a%']] + >>> ~Q(description__contains='%a%') + +6. Conjunction:: + + >>> ['and', [['Package', 'description', 'like', '%a%'], + >>> ['Package', 'origin_pipeline', 'description', '=', + >>> 'Well described.']]] + >>> (Q(description__contains='%a%') & + >>> Q(origin_pipeline__description='Well described.')) + +7. Disjunction:: + + >>> ['or', [['Package', 'description', 'like', '%a%'], + >>> ['Package', 'origin_pipeline', 'description', '=', + >>> 'Well described.']]] + >>> (Q(description__contains='%a%') | + >>> Q(origin_pipeline__description='Well described.')) + +8. Complex hierarchy of filters:: + + >>> ['and', [['Package', 'description', 'like', '%a%'], + >>> ['not', ['Package', 'description', 'like', 'T%']], + >>> ['or', [['Package', 'size', '<', 1000], + >>> ['Package', 'size', '>', 512]]]]] + >>> (Q(description__contains='%a%') & + >>> ~Q(description__contains='T%') & + >>> (Q(size__lt=1000) | Q(size__gt=512))) +""" + +from __future__ import absolute_import + +import functools +import logging +import operator + +from django.db.models.fields.related import ( + ForeignKey, + ManyToManyRel, + ManyToManyField, + ManyToOneRel, + OneToOneRel, +) +from django.db.models import DateTimeField, DateField +from django.db.models import Q +from django.utils.dateparse import parse_datetime + +from . import utils + + +logger = logging.getLogger(__name__) + + +class SearchParseError(Exception): + + def __init__(self, errors): + self.errors = errors + super(SearchParseError, self).__init__() + + def __repr__(self): + return '; '.join(['%s: %s' % (k, self.errors[k]) for k in self.errors]) + + def __str__(self): + return self.__repr__() + + def unpack_errors(self): + return self.errors + + +class QueryBuilder(object): + """Generate a query object from a Python dictionary. + + Builds Django ORM queries from Python data structures representing + arbitrarily complex filter expressions. Example usage:: + + query_builder = QueryBuilder(model_name='Package') + python_query = {'filter': [ + 'and', [ + ['Package', 'description', 'like', '%a%'], + ['not', ['Package', 'description', 'regex', '^[Tt]he']], + ['or', [ + ['Package', 'size', '<', 1000], + ['Package', 'size', '>', 512]]]]]} + query_set = query_builder.get_query_set(python_query) + """ + + def __init__(self, model_cls, primary_key='uuid'): + self.errors = {} + self.model_cls = model_cls + self.model_name = self.model_cls.__name__ + self.primary_key = primary_key + self._schemata = None + + def get_query_set(self, query_as_dict): + """Given a dict, return a Django ORM query set.""" + self.clear_errors() + query_expression = self._get_query_expression(query_as_dict.get('filter')) + order_bys = self._get_order_bys( + query_as_dict.get('order_by'), self.primary_key) + self._raise_search_parse_error_if_necessary() + query_set = self._get_model_manager() + query_set = query_set.filter(query_expression) + query_set = query_set.order_by(*order_bys) + return query_set + + def get_query_expression(self, query_data_structure): + """The public method clears the errors and then calls the corresponding + private method. This prevents interference from errors generated by + previous order_by calls. + """ + self.clear_errors() + return self._get_query_expression(query_data_structure) + + def _get_query_expression(self, query_data_structure): + """Return the filter expression generable by the input Python + data structure or raise an SearchParseError if the data structure is + invalid. + """ + if isinstance(query_data_structure, list): + return self._pythonlist2queryexpr(query_data_structure) + return self._pythondict2queryexpr(query_data_structure) + + def get_order_bys(self, inp_order_bys, primary_key='uuid'): + """The public method clears the errors and then calls the private method. + This prevents interference from errors generated by previous order_by calls. + """ + self.clear_errors() + return self._get_order_bys(inp_order_bys, primary_key) + + def _get_order_bys(self, inp_order_bys, primary_key='uuid'): + """Input is a list of lists of the form [], [, + ] or [, , ]; + output is a list of strings that is an acceptable argument to the + Django ORM's ``order_by`` method. + """ + default_order_bys = [primary_key] + if inp_order_bys is None: + return default_order_bys + order_bys = [] + related_attribute_name = None + for inp_order_by in inp_order_bys: + if not isinstance(inp_order_by, list): + self._add_to_errors( + str(inp_order_by), 'Order by elements must be lists') + continue + if len(inp_order_by) == 1: + related_attribute_name, attribute_name, direction = ( + None, inp_order_by[0], '') + if attribute_name and ( + attribute_name in self.order_by_directions): + attribute_name = primary_key + direction = direction + elif len(inp_order_by) == 2: + related_attribute_name, attribute_name, direction = ( + None, inp_order_by[0], inp_order_by[1]) + elif len(inp_order_by) == 3: + related_attribute_name, attribute_name, direction = inp_order_by + else: + self._add_to_errors( + str(inp_order_by), + 'Order by elements must be lists of 1, 2 or 3 elements') + continue + if related_attribute_name: + related_model_name = self._get_attribute_model_name( + related_attribute_name, self.model_name) + attribute_name = self._get_attribute_name( + attribute_name, related_model_name) + related_attribute_name = self._get_attribute_name( + related_attribute_name, self.model_name) + order_by = '{}__{}'.format( + related_attribute_name, attribute_name) + else: + order_by = self._get_attribute_name( + attribute_name, self.model_name) + try: + direction = self.order_by_directions[direction.lower()].get( + 'alias', direction) + except KeyError: + self._add_to_errors( + direction, 'Unrecognized order by direction') + continue + order_bys.append(direction + order_by) + return order_bys + + def clear_errors(self): + self.errors = {} + + def _raise_search_parse_error_if_necessary(self): + if self.errors: + errors = self.errors.copy() + # Clear the errors so the instance can be reused to build further + # queries + self.clear_errors() + raise SearchParseError(errors) + + def _get_model_manager(self): + return self.model_cls.objects + + def _pythonlist2queryexpr(self, query_as_list): + """This is the function that is called recursively (if necessary) to + build the filter expression. + """ + try: + if query_as_list[0] in ('and', 'or'): + op = {'and': operator.and_, 'or': operator.or_}[query_as_list[0]] + return functools.reduce( + op, [self._pythonlist2queryexpr(x) for x in query_as_list[1]]) + if query_as_list[0] == 'not': + return ~(self._pythonlist2queryexpr(query_as_list[1])) + return self._get_simple_Q_expression(*query_as_list) + except (TypeError, IndexError, AttributeError) as exc: + self.errors['Malformed query error'] = 'The submitted query was malformed' + self.errors[str(type(exc))] = str(exc) + + def _pythondict2queryexpr(self, query_as_dict): + try: + conjunction = query_as_dict.get('conjunction') + if conjunction in ('and', 'or'): + op = {'and': operator.and_, 'or': operator.or_}[conjunction] + return functools.reduce( + op, [self._pythondict2queryexpr(x) for x in + query_as_dict['complement']]) + negation = query_as_dict.get('negation') + if negation == 'not': + return ~(self._pythondict2queryexpr(query_as_dict['complement'])) + return self._get_simple_Q_expression_from_dict(query_as_dict) + except (TypeError, IndexError, AttributeError) as exc: + self.errors['Malformed query error'] = 'The submitted query was malformed' + self.errors[str(type(exc))] = str(exc) + + def _add_to_errors(self, key, msg): + self.errors[str(key)] = msg + + ############################################################################ + # Value converters + ############################################################################ + + def _get_date_value(self, date_string): + """Converts ISO 8601 date strings to Python datetime.date objects.""" + if date_string is None: + # None can be used on date comparisons so assume this is what was + # intended + return date_string + date = utils.date_string2date(date_string) + if date is None: + self._add_to_errors( + 'date %s' % str(date_string), + 'Date search parameters must be valid ISO 8601 date strings.') + return date + + def _get_datetime_value(self, datetime_string): + """Converts ISO 8601 datetime strings to Python datetime.datetime objects.""" + if datetime_string is None: + # None can be used on datetime comparisons so assume this is what + # was intended + return datetime_string + datetime_ = parse_datetime(datetime_string) + if datetime_ is None: + self._add_to_errors( + 'datetime %s' % str(datetime_string), + 'Datetime search parameters must be valid ISO 8601 datetime' + ' strings.') + return datetime_ + + ############################################################################ + # Data structures + ############################################################################ + + # Alter the relations and schema dicts in order to change what types of + # input the query builder accepts. + + # The default set of available relations. Relations with aliases are + # treated as their aliases. E.g., a search like + # ['Package', 'source_id' '!=', ...] + # will generate the Q expression Q(source_id=...) + relations = { + 'exact': {}, + '=': {'alias': 'exact'}, + 'ne': {}, + '!=': {'alias': 'ne'}, + 'contains': {}, + 'like': {'alias': 'contains'}, + 'regex': {}, + 'regexp': {'alias': 'regex'}, + 'lt': {}, + '<': {'alias': 'lt'}, + 'gt': {}, + '>': {'alias': 'gt'}, + 'lte': {}, + '<=': {'alias': 'lte'}, + 'gte': {}, + '>=': {'alias': 'gte'}, + 'in': {} + } + + foreign_model_relations = { + 'isnull': {}, + '=': {'alias': 'isnull'}, + '!=': {'alias': 'isnull'} + } + + order_by_directions = { + '': {}, + '-': {}, + 'desc': {'alias': '-'}, + 'descending': {'alias': '-'}, + 'asc': {'alias': ''}, + 'ascending': {'alias': ''} + } + + ############################################################################ + # Model getters + ############################################################################ + + def _get_model_name(self, model_name): + """Always return model_name; store an error if model_name is invalid.""" + if model_name not in self.schemata: + self._add_to_errors( + model_name, + 'Searching on the %s model is not permitted' % model_name) + return model_name + + def _get_attribute_model_name(self, attribute_name, model_name): + """Returns the name of the model X that stores the data for the attribute + A of model M, e.g., the attribute_model_name for model_name='Package' and + attribute_name='origin_pipeline' is 'Pipeline'. + """ + attribute_dict = self._get_attribute_dict(attribute_name, model_name) + try: + return attribute_dict['foreign_model'] + except KeyError: + self._add_to_errors( + '%s.%s' % (model_name, attribute_name), + 'The %s attribute of the %s model does not represent a' + ' many-to-one relation.' % ( + attribute_name, model_name)) + + ############################################################################ + # Attribute getters + ############################################################################ + + def _get_attribute_name(self, attribute_name, model_name): + """Return attribute_name or cache an error if attribute_name is not in + self.schemata[model_name]. + """ + self._get_attribute_dict(attribute_name, model_name, report_error=True) + return attribute_name + + def _get_attribute_dict(self, attribute_name, model_name, report_error=False): + """Return the dict needed to validate a given attribute of a given model, + or return None. Propagate an error (optionally) if the attribute_name is + invalid. + """ + attribute_dict = self.schemata.get(model_name, {}).get( + attribute_name, None) + if attribute_dict is None and report_error: + self._add_to_errors( + '%s.%s' % (model_name, attribute_name), + 'Searching on %s.%s is not permitted' % ( + model_name, attribute_name)) + return attribute_dict + + ############################################################################ + # Relation getters + ############################################################################ + + def _get_relation_name(self, relation_name, model_name, attribute_name): + """Return relation_name or its alias; propagate an error if + relation_name is invalid. + """ + relation_dict = self._get_relation_dict( + relation_name, model_name, attribute_name, report_error=True) + try: + return relation_dict.get('alias', relation_name) + except AttributeError: # relation_dict can be None + return None + + def _get_relation_dict(self, relation_name, model_name, attribute_name, + report_error=False): + attribute_relations = self._get_attribute_relations( + attribute_name, model_name) + try: + relation_dict = attribute_relations.get(relation_name, None) + except AttributeError: + relation_dict = None + if relation_dict is None and report_error: + self._add_to_errors( + '%s.%s.%s' % (model_name, attribute_name, relation_name), + 'The relation %s is not permitted for %s.%s' % ( + relation_name, model_name, attribute_name)) + return relation_dict + + def _get_attribute_relations(self, attribute_name, model_name): + """Return the data structure encoding what relations are valid for the + input attribute name. + """ + attribute_dict = self._get_attribute_dict(attribute_name, model_name) + try: + if attribute_dict.get('foreign_model'): + return self.foreign_model_relations + return self.relations + except AttributeError: # attribute_dict can be None + return None + + ############################################################################ + # Value getters + ############################################################################ + + @staticmethod + def _normalize(value): + def normalize_if_string(value): + if isinstance(value, str): + return utils.normalize(value) + return value + value = normalize_if_string(value) + if isinstance(value, list): + value = [normalize_if_string(i) for i in value] + return value + + def _get_value_converter(self, attribute_name, model_name): + attribute_dict = self._get_attribute_dict(attribute_name, model_name) + try: + value_converter_name = attribute_dict.get('value_converter', '') + return getattr(self, value_converter_name, None) + except AttributeError: # attribute_dict can be None + return None + + def _get_value(self, value, model_name, attribute_name, relation_name=None): + """Unicode normalize & modify the value using a value_converter (if + necessary). + """ + # unicode normalize (NFD) search patterns; we might want to parameterize this + value = self._normalize(value) + value_converter = self._get_value_converter(attribute_name, model_name) + if value_converter is not None: + if isinstance(value, list): + value = [value_converter(li) for li in value] + else: + value = value_converter(value) + attribute_dict = self._get_attribute_dict(attribute_name, model_name) + if (attribute_dict.get('foreign_model') and + relation_name and value is None): + if relation_name == '=': + value = True + if relation_name == '!=': + value = False + return value + + ############################################################################ + # Filter expression getters + ############################################################################ + + @staticmethod + def _get_invalid_filter_expression_message( + model_name, attribute_name, relation_name, value): + return 'Invalid filter expression: %s.%s.%s(%s)' % ( + model_name, attribute_name, relation_name, repr(value)) + + def _get_invalid_model_attribute_errors(self, *args): + """Avoid catching a (costly) RuntimeError by preventing _get_Q_expression + from attempting to build relation(value) or attribute.has(relation(value)). + We do this by returning a non-empty list of error tuples if Model.attribute + errors are present in self.errors. + """ + try: + (value, model_name, attribute_name, relation_name, + attribute_model_name, attribute_model_attribute_name) = args + except ValueError: + raise TypeError( + '_get_invalid_model_attribute_errors() missing 6 required' + ' positional arguments: \'value\', \'model_name\',' + ' \'attribute_name\', \'relation_name\',' + ' \'attribute_model_name\', and' + ' \'attribute_model_attribute_name\'') + e = [] + if attribute_model_name: + error_key = '%s.%s' % ( + attribute_model_name, attribute_model_attribute_name) + if (self.errors.get(error_key) == + 'Searching on the %s is not permitted' % error_key): + e.append( + ('%s.%s.%s' % (attribute_model_name, + attribute_model_attribute_name, + relation_name), + self._get_invalid_filter_expression_message( + attribute_model_name, + attribute_model_attribute_name, + relation_name, + value))) + error_key = '%s.%s' % (model_name, attribute_name) + if (self.errors.get(error_key) == + 'Searching on %s is not permitted' % error_key): + e.append(('%s.%s.%s' % (model_name, attribute_name, relation_name), + self._get_invalid_filter_expression_message( + model_name, attribute_name, relation_name, value))) + return e + + def _get_meta_relation(self, attribute, model_name, attribute_name): + """Return the has() or the any() method of the input attribute, depending + on the value of schema[model_name][attribute_name]['type']. + """ + return getattr(attribute, {'scalar': 'has', 'collection': 'any'}[ + self.schemata[model_name][attribute_name]['type']]) + + @staticmethod + def _get_Q_expression(attribute_name, relation_name, value): + return Q(**{'{}__{}'.format(attribute_name, relation_name): value}) + + def _get_simple_Q_expression(self, *args): + """Build a Q expression. Examples:: + + >>> ['description', '=', 'abc'] + >>> Q(description__exact='abc') + + >>> ['description', '!=', 'abc'] + >>> Q(description__ne='abc') + + >>> ['origin_pipeline', 'description', 'like', 'J%'] + >>> Q(origin_pipeline__description__contains='J%') + + >>> ['replicas', 'uuid', 'contains', 'a%'] + >>> Q(replicas__uuid__contains='a%') + """ + attribute_name = self._get_attribute_name(args[0], self.model_name) + if len(args) == 3: + relation_name = self._get_relation_name( + args[1], self.model_name, attribute_name) + value = self._get_value( + args[2], self.model_name, attribute_name, relation_name=args[1]) + return self._get_Q_expression( + attribute_name, relation_name, value) + attribute_model_name = self._get_attribute_model_name( + attribute_name, self.model_name) + attribute_model_attribute_name = self._get_attribute_name( + args[1], attribute_model_name) + relation_name = self._get_relation_name( + args[2], attribute_model_name, attribute_model_attribute_name) + value = self._get_value( + args[3], attribute_model_name, attribute_model_attribute_name, + relation_name=args[2]) + attribute_name = '{}__{}'.format( + attribute_name, attribute_model_attribute_name) + return self._get_Q_expression( + attribute_name, relation_name, value) + + def _get_simple_Q_expression_from_dict(self, query_as_dict): + subattribute = query_as_dict.get('subattribute') + attribute = query_as_dict['attribute'] + relation = query_as_dict['relation'] + value = query_as_dict['value'] + attribute_name = self._get_attribute_name(attribute, self.model_name) + if subattribute: + attribute_model_name = self._get_attribute_model_name( + attribute_name, self.model_name) + attribute_model_attribute_name = self._get_attribute_name( + subattribute, attribute_model_name) + relation_name = self._get_relation_name( + relation, attribute_model_name, attribute_model_attribute_name) + value = self._get_value( + value, attribute_model_name, attribute_model_attribute_name, + relation_name=relation) + attribute_name = '{}__{}'.format( + attribute_name, attribute_model_attribute_name) + return self._get_Q_expression( + attribute_name, relation_name, value) + relation_name = self._get_relation_name( + relation, self.model_name, attribute_name) + value = self._get_value(value, self.model_name, attribute_name, + relation_name=relation) + return self._get_Q_expression(attribute_name, relation_name, value) + + def get_search_parameters(self): + """Given the view's resource-configured QueryBuilder instance, + return the list of attributes and their aliases and licit relations + relevant to searching. + """ + return { + 'attributes': + self.schemata[self.model_name], + 'relations': self.relations + } + + @property + def schemata(self): + """The schemata attribute describes the database structure in a way + that allows the query builder to properly interpret the list-formatted + queries and generate errors where necessary. It maps model names to + attribute names. Attribute names whose values contain an 'alias' key + are treated as the value of that key, e.g., ['Package', 'enterer' ...] + will be treated as Package.enterer_id... The relations listed in + self.relations above are the default for all attributes. This can be + overridden by specifying a 'relation' key (cf. + schema['Package']['translations'] below). Certain attributes require + value converters -- functions that change the value in some + attribute-specific way, e.g., conversion of ISO 8601 datetimes to + Python datetime objects. + TODO: reconcile above text with actual behaviour + TODO: add value_converters when/if necessary, e.g., 'last_verified': + {'value_converter': '_get_datetime_value'}. + """ + if self._schemata: + return self._schemata + _schemata = {} + model_cls = self.model_cls + model_clses = set([model_cls]) + fields = model_cls._meta.get_fields() + for field in fields: + if isinstance(field, (ForeignKey, OneToOneRel, + ManyToManyField, ManyToManyRel, + ManyToOneRel)): + model_clses.add(field.related_model) + for model_cls in model_clses: + model_schema = {} + fields = model_cls._meta.get_fields() + for field in fields: + field_name = field.name + field_val = {} + if isinstance(field, DateTimeField): + field_val = {'value_converter': '_get_datetime_value'} + elif isinstance(field, DateField): + field_val = {'value_converter': '_get_date_value'} + elif isinstance(field, (ForeignKey, OneToOneRel)): + if isinstance(field, OneToOneRel): + field_name = field.get_accessor_name() + field_val = {'foreign_model': field.related_model.__name__, + 'type': 'scalar'} + elif isinstance(field, (ManyToManyField, ManyToManyRel, + ManyToOneRel)): + if isinstance(field, ManyToOneRel): + field_name = field.get_accessor_name() + field_val = {'foreign_model': field.related_model.__name__, + 'type': 'collection'} + model_schema[field_name] = field_val + _schemata[model_cls.__name__] = model_schema + self._schemata = _schemata + return self._schemata diff --git a/storage_service/locations/api/beta/remple/resources.py b/storage_service/locations/api/beta/remple/resources.py new file mode 100644 index 000000000..b8a27a1e6 --- /dev/null +++ b/storage_service/locations/api/beta/remple/resources.py @@ -0,0 +1,866 @@ +"""Remple Resources + +Defines the following classes for easily creating controller sub-classes that +handle requests to REST resources: + +- ``Resources`` +- ``ReadonlyResources`` +""" + +from __future__ import absolute_import + +from collections import namedtuple +from functools import partial +import json +import logging + +from django.db import OperationalError +from django.db.models.fields.related import ManyToManyField, ForeignKey +from formencode.validators import Invalid, UnicodeString +from formencode.foreach import ForEach +import inflect + +from .constants import ( + BAD_REQUEST_STATUS, + FORBIDDEN_STATUS, + JSONDecodeErrorResponse, + NOT_FOUND_STATUS, + CREATED_STATUS, + OK_STATUS, + READONLY_RSLT, + UNAUTHORIZED_MSG, +) +from .querybuilder import QueryBuilder, SearchParseError +from .schemata import PaginatorSchema, ResourceURI +from .utils import normalize + + +logger = logging.getLogger(__name__) + + +class ReadonlyResources(object): + """Super-class of ``Resources`` and all read-only resource views. + + +-----------------+-------------+--------------------------+--------+ + | Purpose | HTTP Method | Path | Method | + +=================+=============+==========================+========+ + | Create new | POST | / | create | + +-----------------+-------------+--------------------------+--------+ + | Create data | GET | //new | new | + +-----------------+-------------+--------------------------+--------+ + | Read all | GET | / | index | + +-----------------+-------------+--------------------------+--------+ + | Read specific | GET | // | show | + +-----------------+-------------+--------------------------+--------+ + | Update specific | PUT | // | update | + +-----------------+-------------+--------------------------+--------+ + | Update data | GET | ///edit | edit | + +-----------------+-------------+--------------------------+--------+ + | Delete specific | DELETE | // | delete | + +-----------------+-------------+--------------------------+--------+ + | Search | SEARCH | / | search | + +-----------------+-------------+--------------------------+--------+ + + Note: the create, new, update, edit, and delete actions are all exposed via + the REST API; however, they invariably return 404 responses. + """ + + primary_key = 'uuid' + _query_builder = None + + inflect_p = inflect.engine() + inflect_p.classical() + + def __init__(self, request, server_path='/api/0_1_0/', other_resources=None): + self.request = request + self.server_path = server_path + self.other_resources = other_resources or [] + self._logged_in_user = None + self._query_builder = None + # Names + if not getattr(self, 'collection_name', None): + self.collection_name = self.__class__.__name__.lower() + if not getattr(self, 'hmn_collection_name', None): + self.hmn_collection_name = self.collection_name + if not getattr(self, 'member_name', None): + self.member_name = self.inflect_p.singular_noun( + self.collection_name) + if not getattr(self, 'hmn_member_name', None): + self.hmn_member_name = self.member_name + + # RsrcColl is a resource collection object factory. Each instance holds the + # relevant model name and instance getter for a given resource collection + # name. + RsrcColl = namedtuple('RsrcColl', ['model_cls', 'getter']) + + @staticmethod + def get_mini_dicts_getter(model_cls): + def func(): + return [mi.get_mini_dict() for mi in model_cls.objects.all()] + return func + + @property + def query_builder(self): + return self._get_query_builder() + + @classmethod + def _get_query_builder(cls): + if not cls._query_builder: + cls._query_builder = QueryBuilder( + cls.model_cls, + primary_key=cls.primary_key) + return cls._query_builder + + @property + def logged_in_user(self): + """Property to access the logged in user. QUESTION: Is this a db + model? + """ + if not self._logged_in_user: + self._logged_in_user = self.request.user + return self._logged_in_user + + ########################################################################### + # Public CRUD(S) Methods + ########################################################################### + + def create(self): + logger.warning('Failed attempt to create a read-only %s', + self.hmn_member_name) + return READONLY_RSLT, NOT_FOUND_STATUS + + def new(self): + logger.warning('Failed attempt to get data for creating a read-only %s', + self.hmn_member_name) + return READONLY_RSLT, NOT_FOUND_STATUS + + def index(self): + """Get all resources. + + - URL: ``GET /`` with optional query string + parameters for ordering and pagination. + + :returns: a JSON-serialized array of resources objects. + """ + logger.info('Attempting to read all %s', self.hmn_collection_name) + query_set = self.model_cls.objects + get_params = dict(self.request.GET) + try: + query_set = self.add_order_by(query_set, get_params) + query_set = self._filter_query(query_set) + result = self.add_pagination(query_set, get_params) + except Invalid as error: + errors = error.unpack_errors() + logger.warning('Attempt to read all %s resulted in an error(s): %s', + self.hmn_collection_name, errors) + return {'error': errors}, BAD_REQUEST_STATUS + headers_ctl = self._headers_control(result) + if headers_ctl is not False: + return headers_ctl + logger.info('Read all %s', self.hmn_collection_name) + return result, OK_STATUS + + def show(self, pk): + """Return a resource, given its pk. + :URL: ``GET //`` + :param str pk: the ``pk`` value of the resource to be returned. + :returns: a resource model object. + """ + logger.info('Attempting to read a single %s', self.hmn_member_name) + resource_model = self._model_from_pk(pk) + if not resource_model: + msg = self._rsrc_not_exist(pk) + logger.warning(msg) + return {'error': msg}, NOT_FOUND_STATUS + if self._model_access_unauth(resource_model) is not False: + logger.warning(UNAUTHORIZED_MSG) + return UNAUTHORIZED_MSG, FORBIDDEN_STATUS + logger.info('Read a single %s', self.hmn_member_name) + return self._get_show_dict(resource_model), OK_STATUS + + def update(self, pk): + logger.warning('Failed attempt to update a read-only %s', + self.hmn_member_name) + return READONLY_RSLT, NOT_FOUND_STATUS + + def edit(self, pk): + logger.warning('Failed attempt to get data for updating a read-only %s', + self.hmn_member_name) + return READONLY_RSLT, NOT_FOUND_STATUS + + def delete(self, pk): + logger.warning('Failed attempt to delete a read-only %s', + self.hmn_member_name) + return READONLY_RSLT, NOT_FOUND_STATUS + + def search(self): + """Return the list of resources matching the input JSON query. + + - URL: ``SEARCH /`` (or + ``POST //search``) + - request body: A JSON object of the form:: + + {"query": {"filter": [ ... ], "order_by": [ ... ]}, + "paginator": { ... }} + + where the ``order_by`` and ``paginator`` attributes are optional. + """ + logger.info('Attempting to search over %s', self.hmn_collection_name) + try: + python_search_params = json.loads( + self.request.body.decode('utf8')) + except ValueError: + logger.warning('Request body was not valid JSON') + logger.info(self.request.body.decode('utf8')) + return JSONDecodeErrorResponse, BAD_REQUEST_STATUS + try: + query_set = self.query_builder.get_query_set( + python_search_params.get('query')) + except (SearchParseError, Invalid) as error: + errors = error.unpack_errors() + logger.warning( + 'Attempt to search over all %s resulted in an error(s): %s', + self.hmn_collection_name, errors, exc_info=True) + return {'error': errors}, BAD_REQUEST_STATUS + # Might be better to catch (OperationalError, AttributeError, + # InvalidRequestError, RuntimeError): + except Exception as error: # FIX: too general exception + logger.warning('Filter expression (%s) raised an unexpected' + ' exception: %s.', self.request.body, error) + return {'error': 'The specified search parameters generated an' + ' invalid database query'}, BAD_REQUEST_STATUS + query_set = self._eagerload_model(query_set) + query_set = self._filter_query(query_set) + try: + ret = self.add_pagination( + query_set, python_search_params.get('paginator')) + except OperationalError: + msg = ('The specified search parameters generated an invalid' + ' database query') + logger.warning(msg) + return {'error': msg}, BAD_REQUEST_STATUS + except Invalid as error: # For paginator schema errors. + errors = error.unpack_errors() + logger.warning( + 'Attempt to search over all %s resulted in an error(s): %s', + self.hmn_collection_name, errors) + return {'error': errors}, BAD_REQUEST_STATUS + else: + logger.info('Successful search over %s', self.hmn_collection_name) + return ret, OK_STATUS + + def new_search(self): + """Return the data necessary to search over this type of resource. + + - URL: ``GET //new_search`` + + :returns: a JSON object with a ``search_parameters`` attribute which + resolves to an object with attributes ``attributes`` and ``relations``. + """ + logger.info('Returning search parameters for %s', self.hmn_member_name) + return {'search_parameters': + self.query_builder.get_search_parameters()}, OK_STATUS + + def get_paginated_query_results(self, query_set, paginator): + if 'count' not in paginator: + paginator['count'] = query_set.count() + start, end = _get_start_and_end_from_paginator(paginator) + return { + 'paginator': paginator, + 'items': [self._get_show_dict(rsrc_mdl) for rsrc_mdl in + query_set[start:end]] + } + + def add_pagination_OLD(self, query_set, paginator): + if (paginator and paginator.get('page') is not None and + paginator.get('items_per_page') is not None): + # raises formencode.Invalid if paginator is invalid + paginator = PaginatorSchema.to_python(paginator) + return self.get_paginated_query_results(query_set, paginator) + return [self._get_show_dict(rsrc_mdl) for rsrc_mdl in query_set] + + def add_pagination(self, query_set, paginator): + paginator = paginator or {} + try: + page = int(paginator.get('page', [1])[0]) + except TypeError: + page = int(paginator.get('page', [1])) + try: + items_per_page = int(paginator.get('items_per_page', [50])[0]) + except TypeError: + items_per_page = int(paginator.get('items_per_page', [50])) + new_paginator = { + 'page': page, + 'items_per_page': items_per_page, + } + paginator = PaginatorSchema.to_python(new_paginator) + return self.get_paginated_query_results(query_set, paginator) + + ########################################################################### + # Private Methods for Override: redefine in views for custom behaviour + ########################################################################### + + def _get_show_dict(self, resource_model): + """Return the model as a dict for the return value of a successful show + request. This is indirected so that resources like collections can + override and do special things. + """ + try: + return resource_model.get_dict() + except AttributeError: + return self.to_dict(resource_model) + + def _get_create_dict(self, resource_model): + return self._get_show_dict(resource_model) + + def _get_edit_dict(self, resource_model): + return self._get_show_dict(resource_model) + + def _get_update_dict(self, resource_model): + return self._get_create_dict(resource_model) + + def _eagerload_model(self, query_set): + """Override this in a subclass with model-specific eager loading.""" + return get_eagerloader(self.model_cls)(query_set) + + def _filter_query(self, query_set): + """Override this in a subclass with model-specific query filtering. + E.g.,:: + + >>> return filter_restricted_models(self.model_cls, query_set) + """ + return query_set + + def _headers_control(self, result): + """Take actions based on header values and/or modify headers. If + something other than ``False`` is returned, that will be the response. + Useful for Last-Modified/If-Modified-Since caching, e.g., in ``index`` + method of Forms view. + """ + return False + + def _update_unauth(self, resource_model): + """Return ``True`` if update of the resource model cannot proceed.""" + return self._model_access_unauth(resource_model) + + def _update_unauth_msg_obj(self): + """Return the dict that will be returned when ``self._update_unauth()`` + returns ``True``. + """ + return UNAUTHORIZED_MSG + + def _model_access_unauth(self, resource_model): + """Implement resource/model-specific access controls based on + (un-)restricted(-ness) of the current logged in user and the resource + in question. Return something other than ``False`` to trigger a 403 + response. + """ + return False + + def _model_from_pk(self, pk): + """Return a particular model instance, given the model pk.""" + try: + return self.model_cls.objects.get(**{self.primary_key: pk}) + except (self.model_cls.DoesNotExist, + self.model_cls.MultipleObjectsReturned): + return None + + ########################################################################### + # Utilities + ########################################################################### + + def _rsrc_not_exist(self, pk): + return 'There is no %s with %s %s' % (self.hmn_member_name, + self.primary_key, pk) + + def add_order_by(self, query_set, order_by_params, query_builder=None): + """Add an ORDER BY clause to the query_set using the ``get_order_bys`` + method of the instance's query_builder (if possible) or using a default + ORDER BY ASC. + """ + if not query_builder: + query_builder = self.query_builder + inp_order_bys = None + inp_order_by = list(filter( + None, [order_by_params.get('order_by_attribute', [None])[0], + order_by_params.get('order_by_subattribute', [None])[0], + order_by_params.get('order_by_direction', [None])[0]])) + if inp_order_by: + inp_order_bys = [inp_order_by] + order_by = query_builder.get_order_bys( + inp_order_bys, primary_key=self.primary_key) + logger.info('Adding this order_by: "%s"', order_by) + return query_set.order_by(*order_by) + + @staticmethod + def get_resource_uri(server_path, collection_name, primary_key): + return '{server_path}/{collection_name}/{primary_key}/'.format( + server_path=server_path, + collection_name=collection_name, + primary_key=primary_key) + + @staticmethod + def resource_uri2primary_key(resource_uri): + return filter(None, resource_uri.split('/'))[-1] + + def inst2rsrc_uri(self, related_instance): + related_primary_key = related_instance.pk + related_rsrc_name = related_instance.__class__.__name__.lower() + related_coll_name = self.inflect_p.plural(related_rsrc_name) + related_rsrc_cls = self.other_resources.get( + related_rsrc_name, {}).get('resource_cls') + if related_rsrc_cls: + related_primary_key = getattr( + related_instance, related_rsrc_cls.primary_key) + return self.get_resource_uri( + self.server_path, related_coll_name, + related_primary_key) + + def to_dict(self, instance): + opts = instance._meta + data = {'resource_uri': self.get_resource_uri( + self.server_path, self.collection_name, + getattr(instance, self.primary_key))} + for f in opts.concrete_fields + opts.many_to_many: + if isinstance(f, ManyToManyField): + if instance.pk is None: + data[f.name] = [] + else: + data[f.name] = [ + self.inst2rsrc_uri(related_instance) + for related_instance + in f.value_from_object(instance)] + elif isinstance(f, ForeignKey): + data[f.name] = f.value_from_object(instance) + val = None + related_instance = getattr(instance, f.name, None) + if related_instance: + val = self.inst2rsrc_uri(related_instance) + data[f.name] = val + else: + data[f.name] = f.value_from_object(instance) + return data + + +class Resources(ReadonlyResources): + """Abstract base class for all (modifiable) resource views. RESTful + CRUD(S) interface: + + +-----------------+-------------+--------------------------+--------+ + | Purpose | HTTP Method | Path | Method | + +-----------------+-------------+--------------------------+--------+ + | Create new | POST | / | create | + | Create data | GET | //new | new | + | Read all | GET | / | index | + | Read specific | GET | // | show | + | Update specific | PUT | // | update | + | Update data | GET | ///edit | edit | + | Delete specific | DELETE | // | delete | + | Search | SEARCH | / | search | + +-----------------+-------------+--------------------------+--------+ + """ + + @classmethod + def get_create_schema_cls(cls): + return getattr(cls, 'schema_create_cls', getattr(cls, 'schema_cls', None)) + + @classmethod + def get_update_schema_cls(cls): + return getattr(cls, 'schema_update_cls', getattr(cls, 'schema_cls', None)) + + def preprocess_user_data(self, validated_user_data, schema): + """Process the user-provided and validated ``validated_user_data`` + dict, crucially returning a *new* dict created from it which is ready + for construction of a model instance. + """ + processed_data = {} + schema_cls = schema.__class__ + for field_name, field in schema_cls.fields.items(): + value = validated_user_data[field_name] + if isinstance(field, ForEach) and isinstance( + field.validators[0], ResourceURI): + processed_data[field_name] = value + elif isinstance(field, UnicodeString): + processed_data[field_name] = normalize(value) + else: + processed_data[field_name] = value + return processed_data + + ########################################################################### + # Public CRUD(S) Methods + ########################################################################### + + def create(self): + """Create a new resource and return it. + :URL: ``POST /`` + :request body: JSON object representing the resource to create. + :returns: the newly created resource. + """ + logger.info('Attempting to create a new %s.', self.hmn_member_name) + schema_cls = self.get_create_schema_cls() + schema = schema_cls() + try: + user_data = json.loads(self.request.body.decode('utf8')) + except ValueError: + logger.warning('Request body was not valid JSON') + return JSONDecodeErrorResponse, BAD_REQUEST_STATUS + state = self._get_create_state(user_data) + try: + validated_user_data = schema.to_python(user_data, state) + except Invalid as error: + errors = error.unpack_errors() + logger.warning( + 'Attempt to create a(n) %s resulted in an error(s): %s', + self.hmn_member_name, errors) + return {'error': errors}, BAD_REQUEST_STATUS + resource = self._create_new_resource(validated_user_data, schema) + resource.save() + self._post_create(resource) + logger.info('Created a new %s.', self.hmn_member_name) + return self._get_create_dict(resource), CREATED_STATUS + + def new(self): + """Return the data necessary to create a new resource. + + - URL: ``GET //new/``. + + :returns: a dict containing the related resources necessary to create a + new resource of this type. + + .. note:: See :func:`_get_new_edit_data` to understand how the query + string parameters can affect the contents of the lists in the + returned dictionary. + """ + logger.info('Returning the data needed to create a new %s.', + self.hmn_member_name) + return self._get_new_edit_data(self.request.GET), OK_STATUS + + def update(self, pk): + """Update a resource and return it. + + - URL: ``PUT //`` + - Request body: JSON object representing the resource with updated + attribute values. + + :param str pk: the ``pk`` value of the resource to be updated. + :returns: the updated resource model. + """ + resource_model = self._model_from_pk(pk) + logger.info('Attempting to update %s %s.', self.hmn_member_name, pk) + if not resource_model: + msg = self._rsrc_not_exist(pk) + logger.warning(msg) + return {'error': msg}, NOT_FOUND_STATUS + if self._update_unauth(resource_model) is not False: + msg = self._update_unauth_msg_obj() + logger.warning(msg) + return msg, FORBIDDEN_STATUS + schema_cls = self.get_update_schema_cls() + schema = schema_cls() + try: + values = json.loads(self.request.body.decode('utf8')) + except ValueError: + logger.warning(JSONDecodeErrorResponse) + return JSONDecodeErrorResponse, BAD_REQUEST_STATUS + state = self._get_update_state(values, pk, resource_model) + try: + validated_user_data = schema.to_python(values, state) + except Invalid as error: + errors = error.unpack_errors() + logger.warning(errors) + return {'error': errors}, BAD_REQUEST_STATUS + resource_model = self._update_resource_model( + resource_model, validated_user_data, schema) + # resource_model will be False if there are no changes + if not resource_model: + msg = ('The update request failed because the submitted data were' + ' not new.') + logger.warning(msg) + return {'error': msg}, BAD_REQUEST_STATUS + resource_model.save() + self._post_update(resource_model) + logger.info('Updated %s %s.', self.hmn_member_name, pk) + return self._get_update_dict(resource_model), OK_STATUS + + def edit(self, pk): + """Return a resource and the data needed to update it. + :URL: ``GET //edit`` + :param str pk: the ``pk`` value of the resource that will be updated. + :returns: a dictionary of the form:: + + {"": {...}, "data": {...}} + + where the value of the ```` key is a + dictionary representation of the resource and the value of the + ``data`` key is a dictionary containing the data needed to edit an + existing resource of this type. + """ + resource_model = self._model_from_pk(pk) + logger.info('Attempting to return the data needed to update %s %s.', + self.hmn_member_name, pk) + if not resource_model: + msg = self._rsrc_not_exist(pk) + logger.warning(msg) + return {'error': msg}, NOT_FOUND_STATUS + if self._model_access_unauth(resource_model) is not False: + logger.warning('User not authorized to access edit action on model') + return UNAUTHORIZED_MSG, FORBIDDEN_STATUS + logger.info('Returned the data needed to update %s %s.', + self.hmn_member_name, pk) + return { + 'data': self._get_new_edit_data(self.request.GET, mode='edit'), + 'resource': self._get_edit_dict(resource_model) + }, OK_STATUS + + def delete(self, pk): + """Delete an existing resource and return it. + :URL: ``DELETE //`` + :param str pk: the ``pk`` value of the resource to be deleted. + :returns: the deleted resource model. + """ + resource_model = self._model_from_pk(pk) + logger.info('Attempting to delete %s %s.', self.hmn_member_name, pk) + if not resource_model: + msg = self._rsrc_not_exist(pk) + logger.warning(msg) + return {'error': msg}, NOT_FOUND_STATUS + if self._delete_unauth(resource_model) is not False: + msg = self._delete_unauth_msg_obj() + logger.warning(msg) + return msg, FORBIDDEN_STATUS + error_msg = self._delete_impossible(resource_model) + if error_msg: + logger.warning(error_msg) + return {'error': error_msg}, FORBIDDEN_STATUS + resource_dict = self._get_delete_dict(resource_model) + self._pre_delete(resource_model) + resource_model.delete() + self._post_delete(resource_model) + logger.info('Deleted %s %s.', self.hmn_member_name, pk) + return resource_dict, OK_STATUS + + ########################################################################### + # Private methods for write-able resources + ########################################################################### + + def _delete_unauth(self, resource_model): + """Implement resource/model-specific controls over delete requests. + Return something other than ``False`` to trigger a 403 response. + """ + return False + + def _delete_unauth_msg_obj(self): + """Return the dict that will be returned when ``self._delete_unauth()`` + returns ``True``. + """ + return UNAUTHORIZED_MSG + + def _get_delete_dict(self, resource_model): + """Override this in sub-classes for special resource dict creation.""" + return self._get_show_dict(resource_model) + + def _pre_delete(self, resource_model): + """Perform actions prior to deleting ``resource_model`` from the + database. + """ + pass + + def _post_delete(self, resource_model): + """Perform actions after deleting ``resource_model`` from the + database. + """ + pass + + def _delete_impossible(self, resource_model): + """Return something other than false in a sub-class if this particular + resource model cannot be deleted. + """ + return False + + def _create_new_resource(self, validated_user_data, schema): + """Create a new resource. + :param dict validated_user_data: the data for the resource to be + created. + :param formencode.Schema schema: schema object used to validate + user-supplied data. + :returns: an SQLAlchemy model object representing the resource. + """ + data_from_user = self.preprocess_user_data(validated_user_data, schema) + kwargs = {attr: val for attr, val in data_from_user.items() + if not isinstance(self.model_cls._meta.get_field(attr), + ManyToManyField)} + m2m = {attr: val for attr, val in data_from_user.items() + if isinstance(self.model_cls._meta.get_field(attr), + ManyToManyField)} + ret = self.model_cls(**kwargs) + ret.save() + for attr, vals in m2m.items(): + existing_val = getattr(ret, attr) + through = getattr(existing_val, 'through', None) + if through and (not through._meta.auto_created): + this_attr = [f for f in through._meta.get_fields() + if isinstance(f, ForeignKey)][0].name + for rltd_mdl in vals: + through.objects.get_or_create( + **{attr: rltd_mdl, this_attr: ret}) + else: + for val in vals: + existing_val.add(val) + return ret + + def _post_create(self, resource_model): + """Perform some action after creating a new resource model in the + database. E.g., with forms we have to update all of the forms that + contain the newly entered form as a morpheme. + """ + pass + + def _get_create_state(self, values): + """Return a SchemaState instance for validation of the resource during + a create request. + """ + return SchemaState( + full_dict=values, + logged_in_user=self.logged_in_user) + + def _get_update_state(self, values, pk, resource_model): + update_state = self._get_create_state(values) + update_state.pk = pk + return update_state + + def _update_resource_model(self, resource_model, validated_user_data, + schema): + """Update the Django model instance ``resource_model`` with the dict + ``validated_user_data`` and return something other than ``False`` if + ``resource_model`` has changed as a result. + :param resource_model: the resource model to be updated. + :param dict validated_user_data: user-supplied representation of the + updated resource. + :param formencode.Schema schema: schema object used to validate + user-supplied data. + :returns: the updated resource model instance or ``False`` if the data + did not result in an update of the model. + """ + changed = False + for attr, user_val in validated_user_data.items(): + if self._distinct(attr, resource_model, user_val): + changed = True + break + if changed: + for attr, user_val in self.preprocess_user_data( + validated_user_data, schema).items(): + existing_val = getattr(resource_model, attr) + through = getattr(existing_val, 'through', None) + if through and (not through._meta.auto_created): + this_attr = [f for f in through._meta.get_fields() + if isinstance(f, ForeignKey)][0].name + for rltd_mdl in user_val: + through.objects.get_or_create( + **{attr: rltd_mdl, this_attr: resource_model}) + to_delete = through.objects.filter( + **{this_attr: resource_model}).exclude( + **{'{}__in'.format(attr): user_val}) + to_delete.delete() + else: + setattr(resource_model, attr, user_val) + return resource_model + return changed + + def _distinct(self, attr, resource_model, new_val): + """Return true if ``new_val`` is distinct from ``existing_val``. The + ``attr`` value is provided so that certain attributes (e.g., m2m) can + have a special definition of "distinct". + """ + field = resource_model.__class__._meta.get_field(attr) + existing_val = getattr(resource_model, attr) + if isinstance(field, ManyToManyField): + new_val = sorted(m.pk for m in new_val) + existing_val = sorted(m.pk for m in existing_val.all()) + field = resource_model.__class__._meta.get_field(attr) + return new_val != existing_val + + def _post_update(self, resource_model): + """Perform some action after updating an existing resource model in the + database. + """ + pass + + def get_most_recent_modification_datetime(self, model_cls): + """Return the most recent datetime_modified attribute for the model + + .. note:: This method is intended to be called from + ``_get_new_edit_data`` but the relevant functionality is not yet + implemented. + """ + return None + + def _get_new_edit_data(self, get_params, mode='new'): + """Return the data to create/edit this resource. + + .. note:: the request GET params (``get_params``) should be used here + to allow the user to only request fresh data. However, this makes + assumptions about the Django models that cannot be guaranteed right now + so this functionality is not currently used. + """ + result = {} + for collection, getter in self._get_new_edit_collections( + mode=mode).items(): + result[collection] = getter() + return result + + def _get_related_model_getter(self, field): + related_model_cls = field.model_cls + + def getter(model_cls): + return [self.inst2rsrc_uri(mi) for mi in model_cls.objects.all()] + + return partial(getter, related_model_cls) + + def _get_enum_getter(self, field): + def getter(field): + return [c[0] for c in field.list] + return partial(getter, field) + + def _django_model_class_to_plural(self, model_cls): + return self.inflect_p.plural(model_cls.__name__.lower()) + + def _get_new_edit_collections(self, mode='new'): + """Return a dict from collection names (e.g., "users" or "purpose") to + getter functions that will return all instances of that collection, be + they Django models or simple strings. This dict is constructed by + introspecting both ``self.model_cls`` and ``self.schema_cls``. + """ + collections = {} + if mode == 'new': + schema_cls = self.get_create_schema_cls() + else: + schema_cls = self.get_update_schema_cls() + for field_name, field in schema_cls.fields.items(): + if isinstance(field, ResourceURI): + key = self._django_model_class_to_plural(field.model_cls) + collections[key] = self._get_related_model_getter(field) + elif isinstance(field, ForEach): + first_validator = field.validators[0] + if isinstance(first_validator, ResourceURI): + key = self._django_model_class_to_plural( + first_validator.model_cls) + collections[key] = self._get_related_model_getter(first_validator) + return collections + + +class SchemaState(object): + + def __init__(self, full_dict=None, logged_in_user=None, **kwargs): + self.full_dict = full_dict + self.user = logged_in_user + for key, val in kwargs.items(): + setattr(self, key, val) + + +def get_eagerloader(model_cls): + return lambda query_set: query_set + + +def _get_start_and_end_from_paginator(paginator): + start = (paginator['page'] - 1) * paginator['items_per_page'] + return (start, start + paginator['items_per_page']) diff --git a/storage_service/locations/api/beta/remple/routebuilder.py b/storage_service/locations/api/beta/remple/routebuilder.py new file mode 100644 index 000000000..319abd66a --- /dev/null +++ b/storage_service/locations/api/beta/remple/routebuilder.py @@ -0,0 +1,437 @@ +"""Remple Route Builder + +Defines the ``RouteBuilder`` class whose ``register_resources`` method takes a +dict representing a resource configuration. Once resources are registered, the +``get_urlpatterns`` method can be called to return a list of URL patterns that +can be passed to Django's ``django.conf.urls.include``. Example usage:: + + >>> resources = {...} + >>> route_builder = RouteBuilder() + >>> route_builder.register_resources(resources) + >>> urls = route_builder.get_urlpatterns() + >>> urlpatterns = [url(r'v3/', include(urls))] + +All resources registered with the ``RouteBuilder`` are given endpoints that +follow this pattern:: + + +-----------------+-------------+----------------------------+--------+ + | Purpose | HTTP Method | Path | Method | + +-----------------+-------------+----------------------------+--------+ + | Create new | POST | // | create | + | Create data | GET | //new/ | new | + | Read all | GET | // | index | + | Read specific | GET | /// | show | + | Update specific | PUT | /// | update | + | Update data | GET | ///edit/ | edit | + | Delete specific | DELETE | /// | delete | + | Search | SEARCH | // | search | + | Search | POST | //search/ | search | + | Search data | GET | //new_search/ | search | + +-----------------+-------------+----------------------------+--------+ + +Example resource dict:: + + >>> resources = { + ... 'dog': {'resource_cls': Dogs}, + ... 'cat': {'resource_cls': Cats}} + +.. note:: To remove the search-related routes for a given resource, create a + ``'searchable'`` key with value ``False`` in the configuration for the + resource in the ``RESOURCES`` dict. E.g., ``'location': {'searchable': + False}`` will make the /locations/ resource non-searchable. + +.. note:: All resources expose the same endpoints. If a resource needs special + treatment, it should be done at the corresponding class level. E.g., if + ``POST /packages/`` (creating a package) is special, then do special stuff + in ``resources.py::Packages.create``. Similarly, if packages are indelible, + then ``resources.py::Packages.delete`` should return 404. +""" + +from __future__ import absolute_import +from collections import namedtuple +from functools import partial +from itertools import chain +import logging +import os +import pprint +import re +import string + +from django.conf.urls import url +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from django.http import JsonResponse, HttpResponse +from django.shortcuts import render +from django.template import Template, Context +from django.views.decorators.csrf import csrf_exempt +import inflect +from tastypie.models import ApiKey + +from .constants import ( + METHOD_NOT_ALLOWED_STATUS, + UNAUTHORIZED_MSG, + FORBIDDEN_STATUS, +) +from .openapi import OpenAPI, CustomEndPoint + + +logger = logging.getLogger(__name__) + +UUID_PATT = r'\w{8}-\w{4}-\w{4}-\w{4}-\w{12}' +ID_PATT = r'\d+' +ROUTE_NAME_CHARS = string.letters + '_' + +inflp = inflect.engine() +inflp.classical() + + +class RouteBuilder(OpenAPI): + """A builder of routes: register resources and get URL patterns. + + Typical usage involves passing dict of resource configuration to + ``register_resources`` and then calling ``get_urlpatterns`` to retrieve a + list of corresponding Django ``url`` instances:: + + >>> resources = {'user': {'resource_cls': Users}} # Users is a resources.Resources sub-class + >>> api = API(api_version='0.1.0', service_name='User City!') + >>> api.register_resources(resources) + >>> urls = api.get_urlpatterns() # Include these in Django urlpatterns + """ + + inflp = inflp + + RESOURCE_ACTIONS = ('create', + 'delete', + 'edit', + 'index', + 'new', + 'show', + 'update') + MUTATING_ACTIONS = ('create', 'delete', 'edit', 'new', 'update') + COLLECTION_TARGETING = ('create', 'index') + MEMBER_TARGETING = ('delete', 'show', 'update') + DEFAULT_METHOD = 'GET' + ACTIONS2METHODS = {'create': 'POST', + 'delete': 'DELETE', + 'update': 'PUT'} + + def __init__(self, *args, **kwargs): + self.routes = {} + self.resources = None + super(RouteBuilder, self).__init__(*args, **kwargs) + + def register_route(self, route): + """Register a ``Route()`` instance by breaking it apart and storing it + in ``self.routes``, keyed by its regex and then by its HTTP method. + """ + config = self.routes.get(route.regex, {}) + config['route_name'] = route.name + http_methods_config = config.get('http_methods', {}) + http_methods_config[route.http_method] = ( + route.resource_cls, route.method_name) + config['http_methods'] = http_methods_config + self.routes[route.regex] = config + + def is_authenticated(self, request): + """Checks for TastyPie ApiKey HTTP header-based authorization. + + Requires a header named ``'Authorization'`` that valuates to a string + of the form ``'ApiKey :'``. + """ + auth_header = request.META.get('HTTP_AUTHORIZATION') + logger.info('API Beta: authenticating with %s', auth_header) + if not auth_header: + logger.info('API Beta: no Authorization header') + return False + try: + username_key = auth_header.split()[-1] + username, key = username_key.split(':', 1) + except ValueError: + logger.info('API Beta: unable to extract username and key from' + ' Authorization header %s', auth_header) + return False + try: + user = User.objects.get(username=username) + api_key = str(ApiKey.objects.get(user=user)).split()[0] + except (User.DoesNotExist, ApiKey.DoesNotExist): + logger.info('API Beta: unable to find a user and ApiKey model that' + ' match username "%s" and API key "%s"', user, api_key) + return False + if key == api_key: + return True + logger.info('API Beta: supplied key "%s" and known key "%s" are not' + ' equal', key, api_key) + return False + + def get_urlpatterns(self): + """Return ``urlpatterns_``, a list of Django ``url`` instances that + cause the appropriate instance method to be called for a given request. + Because Django does not allow the HTTP method to determine what is + called, we must supply a view to ``url`` as an anonymous function with + a closure over the route "config", which the anonymous function can use + to route the request to the appropriate method call. For example, + ``GET /pipelines/`` and ``POST /pipelines/`` are handled by the same + function, but ultimately the former is routed to ``Pipelines().index`` + and the latter to ``Pipelines().create``. + """ + urlpatterns_ = [] + for regex, config in self.routes.items(): + route_name = config['route_name'] + + def resource_callable(config, request, **kwargs): + http_methods_config = config['http_methods'] + try: + resource_cls, method_name = http_methods_config[ + request.method] + except KeyError: + return JsonResponse( + method_not_allowed( + request.method, list(http_methods_config.keys())), + status=METHOD_NOT_ALLOWED_STATUS) + instance = resource_cls( + request, server_path=self.get_dflt_server_path(), + other_resources=self.resources) + if self.is_authenticated(request): + method = getattr(instance, method_name) + response, status = method(**kwargs) + else: + logger.warning(UNAUTHORIZED_MSG) + response, status = UNAUTHORIZED_MSG, FORBIDDEN_STATUS + return JsonResponse(response, status=status, safe=False) + + urlpatterns_.append(url( + regex, + # Sidestep Python's late binding: + view=csrf_exempt(partial(resource_callable, config)), + name=route_name)) + urlpatterns_ += self.get_schema_doc_urlpatterns() + return urlpatterns_ + + def yield_standard_routes(self, rsrc_member_name, resource_cls): + """Yield the ``Route()``s needed to configure standard CRUD actions on the + resource with member name ``rsrc_member_name``. + """ + pk_patt = {'uuid': UUID_PATT, 'id': ID_PATT}.get( + resource_cls.primary_key, UUID_PATT) + rsrc_collection_name = inflp.plural(rsrc_member_name) + for action in self.RESOURCE_ACTIONS: + method_name = action + http_method = self.ACTIONS2METHODS.get(action, self.DEFAULT_METHOD) + api_v_slug = self.get_api_version_slug() + if action in self.COLLECTION_TARGETING: + route_name = '{}_{}'.format(api_v_slug, rsrc_collection_name) + regex = get_collection_targeting_regex(rsrc_collection_name) + elif action in self.MEMBER_TARGETING: + route_name = '{}_{}'.format(api_v_slug, rsrc_member_name) + regex = get_member_targeting_regex(rsrc_collection_name, pk_patt) + elif action == 'new': + route_name = '{}_{}_new'.format( + api_v_slug, rsrc_collection_name) + regex = get_collection_targeting_regex( + rsrc_collection_name, modifiers=['new']) + else: # edit is default case + route_name = '{}_{}_edit'.format( + api_v_slug, rsrc_member_name) + regex = get_member_targeting_regex( + rsrc_collection_name, pk_patt, modifiers=['edit']) + yield Route(name=route_name, + regex=regex, + http_method=http_method, + resource_cls=resource_cls, + method_name=method_name) + + def yield_custom_routes(self, rsrc_member_name, resource_cls): + api_v_slug = self.get_api_version_slug() + pk_patt = {'uuid': UUID_PATT, 'id': ID_PATT}.get( + resource_cls.primary_key, UUID_PATT) + rsrc_collection_name = self.inflp.plural(rsrc_member_name) + for attr_name in dir(resource_cls): + if attr_name.startswith('_'): + continue + custom_endpoint = getattr(resource_cls, attr_name) + if not isinstance(custom_endpoint, CustomEndPoint): + continue + route_name = '{}_{}_{}'.format( + api_v_slug, rsrc_member_name, custom_endpoint.action) + yield Route(name=route_name, + regex=openapi_path2regex(custom_endpoint.path, + rsrc_collection_name, pk_patt), + http_method=custom_endpoint.http_method.upper(), + resource_cls=resource_cls, + method_name=custom_endpoint.method_name) + + def register_routes_for_resource(self, rsrc_member_name, rsrc_config): + """Register all of the routes generable for the resource with member + name ``rsrc_member_name`` and with configuration ``rsrc_config``. The + ``rsrc_config`` can control whether the resource is searchable. + """ + routes = [] + if rsrc_config.get('searchable', True): + routes.append(self.yield_search_routes( + rsrc_member_name, rsrc_config['resource_cls'])) + routes.append(self.yield_standard_routes( + rsrc_member_name, rsrc_config['resource_cls'])) + routes.append(self.yield_custom_routes( + rsrc_member_name, rsrc_config['resource_cls'])) + for route in chain(*routes): + self.register_route(route) + + def register_resources(self, resources_): + """Register all of the routes generable for each resource configured in + the ``resources_`` dict. + """ + self.resources = resources_ + for rsrc_member_name, rsrc_config in resources_.items(): + self.register_routes_for_resource(rsrc_member_name, rsrc_config) + + def yield_search_routes(self, rsrc_member_name, resource_cls): + """Yield the ``Route()``s needed to configure search across the resource + with member name ``rsrc_member_name``. + """ + rsrc_collection_name = inflp.plural(rsrc_member_name) + api_v_slug = self.get_api_version_slug() + yield Route(name='{}_{}'.format(api_v_slug, rsrc_collection_name), + regex=get_collection_targeting_regex(rsrc_collection_name), + http_method='SEARCH', + resource_cls=resource_cls, + method_name='search') + yield Route(name='{}_{}_search'.format(api_v_slug, rsrc_collection_name), + regex=get_collection_targeting_regex( + rsrc_collection_name, modifiers=['search']), + http_method='POST', + resource_cls=resource_cls, + method_name='search') + yield Route(name='{}_{}_new_search'.format(api_v_slug, rsrc_collection_name), + regex=get_collection_targeting_regex( + rsrc_collection_name, modifiers=['new_search']), + http_method='GET', + resource_cls=resource_cls, + method_name='new_search') + + def schema_view(self, request): + """Serve the OpenAPI YAML that defines this API in an HTML
, i.e.,
+        human-readable.
+        """
+        schema = self.generate_open_api_spec()
+        raw_code = self.to_yaml(schema).encode('utf8')
+        title = '{} ({})'.format(schema['info']['title'],
+                                 schema['info']['version'])
+        return HttpResponse(
+            Template(raw_code_template).render(Context(
+                {'title': title, 'raw_code': raw_code})))
+
+    def yaml_schema_view(self, request):
+        """Serve the raw OpenAPI YAML that defines this API."""
+        schema = self.generate_open_api_spec()
+        schema_yaml = self.to_yaml(schema)
+        return HttpResponse(schema_yaml)
+
+    def doc_view(self, request):
+        """Serve the Swagger UI HTML for this API."""
+        api_v_slug = self.get_api_version_slug()
+        yaml_url = reverse('{}_yaml'.format(api_v_slug))
+        schema_url = reverse('{}_schema'.format(api_v_slug))
+        client_url = reverse('{}_client'.format(api_v_slug))
+        return render(request, 'locations/api/beta/openapi.html', locals())
+
+    def client_view(self, request):
+        """Create the Python client module using the clientbuilder.py module
+        and inserting the OpenAPI spec data structure into the newly created
+        file as a constant. Then serve the script as a string in a JSON dict as
+        the value of the 'client' attribute. Serving the script as JSON gets
+        around encoding complications.
+        """
+        schema = self.generate_open_api_spec()
+        client_builder_path = os.path.join(
+            os.path.dirname(__file__),
+            'clientbuilder.py')
+        client = []
+        with open(client_builder_path) as filei:
+            for line in filei:
+                if line.startswith('OPENAPI_SPEC = None'):
+                    client.append('OPENAPI_SPEC = (\n    {}\n)'.format(
+                        pprint.pformat(schema)))
+                else:
+                    client.append(line)
+        raw_code = ''.join(client)
+        return JsonResponse({'client': raw_code})
+
+    def get_schema_doc_urlpatterns(self):
+        """Return a list of Django ``url`` instances that configure the / and
+        /doc/ paths to return the OpenAPI YAML and the Swagger UI, respectively.
+        """
+        api_v_slug = self.get_api_version_slug()
+        return [
+            url(r'^$', self.schema_view, name='{}_schema'.format(api_v_slug)),
+            url(r'^yaml/$', self.yaml_schema_view,
+                name='{}_yaml'.format(api_v_slug)),
+            url(r'^doc/$', self.doc_view, name='{}_doc'.format(api_v_slug)),
+            url(r'^client/$', self.client_view,
+                name='{}_client'.format(api_v_slug)),
+        ]
+
+
+raw_code_template = '''
+
+
+  
+    {{ title }}
+  
+
+  
+{{ raw_code }}
+  
+ + +''' + + +# A "route" is a unique combination of path regex, route name, HTTP method, and +# class/method to call when the path regex and HTTP method match a request. +# Note that because of how Django's ``url`` works, multiple distinct routes can +# have the same ``url`` instance with the same name; e.g., POST /pipelines/ and +# GET /pipelines/ are both handled by the "pipelines" ``url``. +Route = namedtuple('Route', 'name regex http_method resource_cls method_name') + + +def get_collection_targeting_regex(rsrc_collection_name, modifiers=None): + """Return a regex of the form '^/$' + with optional trailing modifiers, e.g., '^/new/$'. + """ + if modifiers: + return r'^{rsrc_collection_name}/{modifiers}/$'.format( + rsrc_collection_name=rsrc_collection_name, + modifiers='/'.join(modifiers)) + return r'^{rsrc_collection_name}/$'.format( + rsrc_collection_name=rsrc_collection_name) + + +def get_member_targeting_regex(rsrc_collection_name, pk_patt, modifiers=None): + """Return a regex of the form '^//$' + with optional modifiers after the pk, e.g., + '^//edit/$'. + """ + if modifiers: + return (r'^{rsrc_collection_name}/(?P{pk_patt})/' + r'{modifiers}/$'.format( + rsrc_collection_name=rsrc_collection_name, + pk_patt=pk_patt, + modifiers='/'.join(modifiers))) + return r'^{rsrc_collection_name}/(?P{pk_patt})/$'.format( + rsrc_collection_name=rsrc_collection_name, pk_patt=pk_patt) + + +def method_not_allowed(tried_method, accepted_methods): + return {'error': 'The {} method is not allowed for this resources. The' + ' accepted methods are: {}'.format( + tried_method, ', '.join(accepted_methods))} + + +def openapi_path2regex(path, rsrc_collection_name, pk_patt): + path_var_patt = re.compile(r'\{(.+)\}') + + def replacer(match): + return r'(?P<' + match.groups()[0] + r'>' + pk_patt + r')' + + return (r'^' + rsrc_collection_name + r'/' + + path_var_patt.sub(replacer, path) + r'$') diff --git a/storage_service/locations/api/beta/remple/schemata.py b/storage_service/locations/api/beta/remple/schemata.py new file mode 100644 index 000000000..1d63a922e --- /dev/null +++ b/storage_service/locations/api/beta/remple/schemata.py @@ -0,0 +1,120 @@ +from collections import OrderedDict +import re + +from formencode.schema import Schema +from formencode.validators import Int, FancyValidator, Regex, Invalid + +from .constants import formencode_field2openapi_type + + +class NotUsed(Exception): + pass + + +class OpenAPISchema(object): + + def __init__(self, formencode_schema, config): + self.formencode_schema = formencode_schema + self.config = config + + def extract_parameters(self): + """Return a list of OrderedDicts describing this schema as an OpenAPI + parameter. + """ + parameters = [] + for parameter_name, formencode_cls in ( + self.formencode_schema.fields.items()): + config = self.config.get(parameter_name, {}) + parameter = OrderedDict() + schema = OrderedDict() + parameter['in'] = config.get('in', 'query') + parameter['name'] = parameter_name + parameter['required'] = config.get( + 'required', formencode_cls.not_empty) + schema['type'] = formencode_field2openapi_type.get( + type(formencode_cls), 'string') + minimum = formencode_cls.min + if minimum is not None: + schema['minimum'] = minimum + parameter['schema'] = schema + description = config.get('description', None) + if description is not None: + parameter['description'] = description + default = config.get('default', NotUsed) + if default != NotUsed: + schema['default'] = default + parameters.append(parameter) + return parameters + + +class PaginatorSchema(Schema): + allow_extra_fields = True + filter_extra_fields = False + items_per_page = Int(not_empty=True, min=1) + page = Int(not_empty=True, min=1) + + +PaginatorOpenAPISchema = OpenAPISchema( + PaginatorSchema, + { + 'page': { + 'description': 'The page number to return.', + 'required': False, + 'default': 1, + }, + 'items_per_page': { + 'description': 'The maximum number of items to return.', + 'required': False, + 'default': 10, + } + }) + +schemata = (PaginatorOpenAPISchema,) + + +UUID = Regex( + r'^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-' + r'[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$') + + +def camel_case2lower_space(name): + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1 \2', s1).lower() + + +class ResourceURI(FancyValidator): + """Validator for input values that are primary keys of model objects. Value + must be the pk of an existing model of the type specified in the + ``model_cls`` kwarg. If valid, the model object is returned. Example + usage: ResourceURI(model_cls=models.Package). + """ + + messages = { + 'invalid_model': + 'There is no %(model_name_eng)s with pk %(id)s.' + } + + def _convert_to_python(self, value, state): + if value in ('', None): + return None + else: + pk = filter(None, value.split('/'))[-1] + pk_validator = getattr(self, 'pk_validator', UUID) + pk = pk_validator().to_python(pk, state) + pk_attr = getattr(self, 'pk', 'uuid') + model_cls = self.model_cls + try: + model_object = model_cls.objects.get( + **{pk_attr: pk}) + except model_cls.DoesNotExist: + model_name_eng = camel_case2lower_space( + self.model_cls.__name__) + raise Invalid( + self.message('invalid_model', state, id=pk, + model_name_eng=model_name_eng), + value, state) + else: + return model_object + + +__all__ = ('schemata', 'PaginatorSchema', 'ResourceURI') diff --git a/storage_service/locations/api/beta/remple/utils.py b/storage_service/locations/api/beta/remple/utils.py new file mode 100644 index 000000000..13760ca2d --- /dev/null +++ b/storage_service/locations/api/beta/remple/utils.py @@ -0,0 +1,48 @@ +import unicodedata +import datetime + + +def normalize(unistr): + """Return a unistr using canonical decompositional normalization (NFD).""" + try: + return unicodedata.normalize('NFD', unistr) + except TypeError: + return unicodedata.normalize('NFD', unistr.decode('utf8')) + except UnicodeDecodeError: + return unistr + + +def round_datetime(dt): + """Round a datetime to the nearest second.""" + discard = datetime.timedelta(microseconds=dt.microsecond) + dt -= discard + if discard >= datetime.timedelta(microseconds=500000): + dt += datetime.timedelta(seconds=1) + return dt + + +def datetime_string2datetime(datetime_string): + """Parse an ISO 8601-formatted datetime into a Python datetime object. + Cf. http://stackoverflow.com/questions/531157/\ + parsing-datetime-strings-with-microseconds + """ + try: + parts = datetime_string.split('.') + years_to_seconds_string = parts[0] + ' +0000' + datetime_object = datetime.datetime.strptime( + years_to_seconds_string, "%Y-%m-%dT%H:%M:%S %z") + except ValueError: + return None + try: + microseconds = int(parts[1]) + datetime_object = datetime_object.replace(microsecond=microseconds) + except (IndexError, ValueError, OverflowError): + pass + return datetime_object + + +def date_string2date(date_string): + try: + return datetime.datetime.strptime(date_string, "%Y-%m-%d").date() + except ValueError: + return None diff --git a/storage_service/locations/api/beta/resources/__init__.py b/storage_service/locations/api/beta/resources/__init__.py new file mode 100644 index 000000000..09e4ba84d --- /dev/null +++ b/storage_service/locations/api/beta/resources/__init__.py @@ -0,0 +1,244 @@ +"""Resources for Version Beta of the Storage Service API. + +Defines sub-classes of ``remple.Resources`` and ``remple.ReadonlyResources`` in +order to define CRUD class methods for resources including: + +- ``Packages`` +- ``Locations`` +- ``Pipelines`` +- ``Spaces`` (and space sub-types) +- ``Files`` + +.. note:: The simple (model-based) resources are defined here. More complex + resources are defined in their own modules, e.g., + resources/locations.py. + +TODO: + +- package custom endpoints (cf. + https://wiki.archivematica.org/Storage_Service_API#Package) +""" + +from __future__ import absolute_import +import logging + +from django.contrib.auth.models import User, Group, Permission +from django.contrib.contenttypes.models import ContentType + +from locations.api.beta.remple import ( + Resources, + ReadonlyResources, +) +from locations.api.beta.schemata import ( + ArkivumSpaceSchema, + DataverseSpaceSchema, + DSpaceSpaceSchema, + DuracloudSpaceSchema, + FedoraSpaceSchema, + LockssomaticSpaceSchema, + NFSSpaceSchema, + S3SpaceSchema, + SwiftSpaceSchema, + GPGSpaceSchema, + LocalFilesystemSpaceSchema, + PipelineSchema, + PipelineLocalSpaceSchema, + SpaceCreateSchema, + SpaceUpdateSchema, + UserSchema, +) +from locations.models import ( + Arkivum, + Async, + Callback, + Dataverse, + DSpace, + Duracloud, + Event, + Fedora, + FixityLog, + Lockssomatic, + NFS, + S3, + Swift, + File, + GPG, + LocalFilesystem, + Package, + Space, + Pipeline, + PipelineLocalFS, +) +from .locations import Locations + + +logger = logging.getLogger(__name__) + + +class Files(ReadonlyResources): + model_cls = File + + +class Packages(ReadonlyResources): + """TODO: Packages have a lot of special behaviour that should be exposed + via the beta API, but this is being left for later work. + """ + model_cls = Package + + +class Pipelines(Resources): + model_cls = Pipeline + schema_cls = PipelineSchema + + +class Spaces(Resources): + model_cls = Space + schema_create_cls = SpaceCreateSchema + schema_update_cls = SpaceUpdateSchema + + +class LocalFilesystemSpaces(Resources): + primary_key = 'id' + model_cls = LocalFilesystem + schema_cls = LocalFilesystemSpaceSchema + + +class GPGSpaces(Resources): + primary_key = 'id' + model_cls = GPG + schema_cls = GPGSpaceSchema + + +class ArkivumSpaces(Resources): + primary_key = 'id' + model_cls = Arkivum + schema_cls = ArkivumSpaceSchema + + +class DataverseSpaces(Resources): + primary_key = 'id' + model_cls = Dataverse + schema_cls = DataverseSpaceSchema + + +class DSpaceSpaces(Resources): + primary_key = 'id' + model_cls = DSpace + schema_cls = DSpaceSpaceSchema + + +class DuracloudSpaces(Resources): + primary_key = 'id' + model_cls = Duracloud + schema_cls = DuracloudSpaceSchema + + +class FedoraSpaces(Resources): + primary_key = 'id' + model_cls = Fedora + schema_cls = FedoraSpaceSchema + + +class LockssomaticSpaces(Resources): + primary_key = 'id' + model_cls = Lockssomatic + schema_cls = LockssomaticSpaceSchema + + +class NFSSpaces(Resources): + primary_key = 'id' + model_cls = NFS + schema_cls = NFSSpaceSchema + + +class S3Spaces(Resources): + primary_key = 'id' + model_cls = S3 + schema_cls = S3SpaceSchema + + +class SwiftSpaces(Resources): + primary_key = 'id' + model_cls = Swift + schema_cls = SwiftSpaceSchema + + +class PipelineLocalSpaces(Resources): + primary_key = 'id' + model_cls = PipelineLocalFS + schema_cls = PipelineLocalSpaceSchema + + +class Asyncs(ReadonlyResources): + primary_key = 'id' + model_cls = Async + + +class Events(ReadonlyResources): + primary_key = 'id' + model_cls = Event + + +class Callbacks(ReadonlyResources): + primary_key = 'id' + model_cls = Callback + + +class FixityLogs(ReadonlyResources): + primary_key = 'id' + model_cls = FixityLog + + +class Users(Resources): + primary_key = 'id' + model_cls = User + schema_cls = UserSchema + + def _get_show_dict(self, resource_model): + ret = super(Users, self)._get_show_dict(resource_model) + del ret['password'] + return ret + + +class Groups(ReadonlyResources): + primary_key = 'id' + model_cls = Group + + +class Permissions(ReadonlyResources): + primary_key = 'id' + model_cls = Permission + + +class ContentTypes(ReadonlyResources): + primary_key = 'id' + model_cls = ContentType + + +__all__ = ( + 'Locations', + 'Files', + 'Packages', + 'Pipelines', + 'Spaces', + 'LocalFilesystemSpaces', + 'GPGSpaces', + 'ArkivumSpaces', + 'DataverseSpaces', + 'DSpaceSpaces', + 'DuracloudSpaces', + 'FedoraSpaces', + 'LockssomaticSpaces', + 'NFSSpaces', + 'S3Spaces', + 'SwiftSpaces', + 'PipelineLocalSpaces', + 'Asyncs', + 'Events', + 'Callbacks', + 'FixityLogs', + 'Users', + 'Groups', + 'Permissions', + 'ContentTypes', +) diff --git a/storage_service/locations/api/beta/resources/locations.py b/storage_service/locations/api/beta/resources/locations.py new file mode 100644 index 000000000..d09d3ee28 --- /dev/null +++ b/storage_service/locations/api/beta/resources/locations.py @@ -0,0 +1,136 @@ +"""Location Resources for Version Beta of the Storage Service API. + +Defines ``Locations`` (a sub-class of ``remple.Resources``) in order to expose +CRUD operations on locations. Also includes custom endpoints for the following +operations: + +- browse + +""" + +from __future__ import absolute_import +import base64 +from collections import OrderedDict +import logging +import os + +from locations.api.beta.remple import ( + CustomEndPoint, + Resources, + OK_STATUS, + NOT_FOUND_STATUS, + UNAUTHORIZED_MSG, + FORBIDDEN_STATUS, + get_error_schema_path, + get_ref_response, + schema_name2path, +) + +from locations.api.beta.schemata import LocationSchema +from locations.models import Location + +logger = logging.getLogger(__name__) + + +LOCATION_BROWSE_SCHEMA_NAME = 'LocationBrowseSchema' +LOCATION_BROWSE_SCHEMA = OrderedDict([ + ('type', 'object'), + ('properties', OrderedDict([ + ('entries', OrderedDict([ + ('type', 'array'), + ('items', {'type': 'string', 'format': 'byte'}), + ])), + ('directories', OrderedDict([ + ('type', 'array'), + ('items', {'type': 'string', 'format': 'byte'}), + ])), + ('properties', {'type': 'object'}), + ])), + ('required', ['entries', 'directories', 'properties']), +]) + +LOCATION_BROWSE_PARAMS = [ + OrderedDict([ + ('name', 'pk'), + ('in', 'path'), + ('description', 'UUID of the location to browse'), + ('required', True), + ('schema', OrderedDict([ + ('type', 'string'), + ('format', 'UUID of a Location resource'), + ])), + ]), + OrderedDict([ + ('name', 'path'), + ('in', 'query'), + ('default', ''), + ('description', 'Path to browse within the Location (optional)'), + ('schema', {'type': 'string'}), + ]), +] + +LOCATION_BROWSE_RESPONSES = OrderedDict([ + ('200', get_ref_response( + description='Request to browse a location was successful', + ref=schema_name2path(LOCATION_BROWSE_SCHEMA_NAME))), + ('404', get_ref_response( + description='Request to browse a location failed because there is no' + ' location resource with the specified pk.', + ref=get_error_schema_path())), + ('403', get_ref_response( + description='Request to browse location failed because the user is' + ' forbidden from viewing this location.', + ref=get_error_schema_path())), +]) + + +class Locations(Resources): + model_cls = Location + schema_cls = LocationSchema + + # Custom endpoints + browse = CustomEndPoint( + action='browse', # will contribute to operationId=locations.browse + path='{pk}/browse/', + http_method='get', + method_name='browse_method', + tags=['locations'], + summary='Browse a location', + description='Browse a location at the root (default) or at a supplied' + ' path.', + parameters=LOCATION_BROWSE_PARAMS, + responses=LOCATION_BROWSE_RESPONSES, + schemata={LOCATION_BROWSE_SCHEMA_NAME: LOCATION_BROWSE_SCHEMA}) + + def browse_method(self, pk): + """Handler for a GET /locations//browse/ request. + :param str pk (in: path): the UUID value of the resource to be returned. + :param str path (in: query params): the (optional) path to browse the + location at. + :returns: a dict with keys for 'entries', 'directories', and + 'properties'. + """ + logger.info('Attempting to browse a %s', self.hmn_member_name) + location_mdl = self._model_from_pk(pk) + if not location_mdl: + msg = self._rsrc_not_exist(pk) + logger.warning(msg) + return {'error': msg}, NOT_FOUND_STATUS + if self._model_access_unauth(location_mdl) is not False: + logger.warning(UNAUTHORIZED_MSG) + return UNAUTHORIZED_MSG, FORBIDDEN_STATUS + + # TODO remove duplication from api/resources.py + path = self.request.GET.get('path', '') + location_path = location_mdl.full_path + if isinstance(location_path, unicode): + location_path = location_path.encode('utf8') + if not path.startswith(location_path): + path = os.path.join(location_path, path) + # TODO remove duplication from api/v2.py + objects = location_mdl.space.browse(path) + objects['entries'] = map(base64.b64encode, objects['entries']) + objects['directories'] = map(base64.b64encode, objects['directories']) + objects['properties'] = {base64.b64encode(k): v for k, v in + objects.get('properties', {}).items()} + return objects, OK_STATUS diff --git a/storage_service/locations/api/beta/schemata.py b/storage_service/locations/api/beta/schemata.py new file mode 100644 index 000000000..979b93681 --- /dev/null +++ b/storage_service/locations/api/beta/schemata.py @@ -0,0 +1,178 @@ +from __future__ import absolute_import +import logging + +from django.contrib.auth.models import Group, Permission +from formencode.compound import Any +from formencode.foreach import ForEach +from formencode.schema import Schema +from formencode.validators import ( + Bool, + Email, + Int, + IPAddress, + OneOf, + Regex, + UnicodeString, + URL, +) + +from locations import models +from locations.api.beta.remple import ResourceURI + + +logger = logging.getLogger(__name__) + + +def _flatten(choices): + return [ch[0] for ch in choices] + + +class PipelineSchema(Schema): + api_key = UnicodeString(max=256) + api_username = UnicodeString(max=256) + description = UnicodeString(max=256) + enabled = Bool() + remote_name = Any(validators=[IPAddress(), URL()]) + + +class SpaceUpdateSchema(Schema): + size = Int(min=0) + path = UnicodeString(max=256) + staging_path = UnicodeString(max=256) + + +class SpaceCreateSchema(SpaceUpdateSchema): + access_protocol = OneOf( + _flatten(models.Space.ACCESS_PROTOCOL_CHOICES)) + + +class TypeOfSpaceSchema(Schema): + space = ResourceURI(model_cls=models.Space) + + +class LocalFilesystemSpaceSchema(TypeOfSpaceSchema): + pass + + +class GPGSpaceSchema(TypeOfSpaceSchema): + key = UnicodeString(max=256) + + +class ArkivumSpaceSchema(TypeOfSpaceSchema): + host = UnicodeString(max=256) + remote_user = UnicodeString(max=64) + remote_name = UnicodeString(max=256) + + +class DataverseSpaceSchema(TypeOfSpaceSchema): + host = UnicodeString(max=256) + api_key = UnicodeString(max=50) + agent_name = UnicodeString(max=50) + agent_type = UnicodeString(max=50) + agent_identifier = UnicodeString(max=256) + + +class DSpaceSpaceSchema(TypeOfSpaceSchema): + sd_iri = URL(max=256) + user = UnicodeString(max=64) + password = UnicodeString(max=64) + metadata_policy = UnicodeString() # JSONField ... + archive_format = OneOf(_flatten(models.DSpace.ARCHIVE_FORMAT_CHOICES)) + + +class DuracloudSpaceSchema(TypeOfSpaceSchema): + host = UnicodeString(max=256) + user = UnicodeString(max=64) + password = UnicodeString(max=64) + duraspace = UnicodeString(max=64) + + +class FedoraSpaceSchema(TypeOfSpaceSchema): + fedora_user = UnicodeString(max=64) + fedora_password = UnicodeString(max=256) + fedora_name = UnicodeString(max=256) + + +class LockssomaticSpaceSchema(TypeOfSpaceSchema): + collection_iri = UnicodeString(max=256) + content_provider_id = UnicodeString(max=32) + checksum_type = UnicodeString(max=64) + keep_local = Bool() + au_size = Int() + sd_iri = URL(max=256) + external_domain = URL() + + +class NFSSpaceSchema(TypeOfSpaceSchema): + remote_name = UnicodeString(max=256) + remote_path = UnicodeString() + version = UnicodeString(max=64) + manually_mounted = Bool() + + +class S3SpaceSchema(TypeOfSpaceSchema): + endpoint_url = UnicodeString(max=2048) + access_key_id = UnicodeString(max=64) + secret_access_key = UnicodeString(max=256) + region = UnicodeString(max=64) + + +class SwiftSpaceSchema(TypeOfSpaceSchema): + auth_url = UnicodeString(max=256) + auth_version = UnicodeString(max=8) + username = UnicodeString(max=64) + password = UnicodeString(max=256) + container = UnicodeString(max=64) + tenant = UnicodeString(max=64) + region = UnicodeString(max=64) + + +class PipelineLocalSpaceSchema(TypeOfSpaceSchema): + remote_user = UnicodeString(max=64) + remote_name = UnicodeString(max=256) + assume_rsync_daemon = Bool() + rsync_password = UnicodeString(max=64) + + +class LocationSchema(Schema): + description = UnicodeString(max=256) + purpose = OneOf(_flatten(models.Location.PURPOSE_CHOICES)) + relative_path = UnicodeString() + quota = Int(min=0) + enabled = Bool() + space = ResourceURI(model_cls=models.Space) + pipeline = ForEach(ResourceURI(model_cls=models.Pipeline)) + replicators = ForEach(ResourceURI(model_cls=models.Location)) + + +# Note: it does not make sense to have a schema for the package resource since +# it is not truly mutable via an external API. I am leaving this for now in +# case it contains useful information in the future. +class PackageSchema(Schema): + current_location = ResourceURI(model_cls=models.Location) + current_path = UnicodeString() + description = UnicodeString(max=256) + encryption_key_fingerprint = UnicodeString(max=512) + misc_attributes = UnicodeString() + origin_pipeline = ResourceURI(model_cls=models.Pipeline) + package_type = OneOf( + _flatten(models.Package.PACKAGE_TYPE_CHOICES)) + pointer_file_location = ResourceURI(model_cls=models.Location) + pointer_file_path = UnicodeString() + related_packages = ForEach(ResourceURI(model_cls=models.Package)) + replicated_package = ResourceURI(model_cls=models.Package) + size = Int(min=0) + status = OneOf(_flatten(models.Package.STATUS_CHOICES)) + + +class UserSchema(Schema): + username = Regex(r'^[a-zA-Z0-9_@+\.-]+$', max=150) + password = UnicodeString() + first_name = UnicodeString(max=30) + last_name = UnicodeString(max=150) + email = Email() + groups = ForEach(ResourceURI(model_cls=Group)) + user_permissions = ForEach(ResourceURI(model_cls=Permission)) + is_staff = Bool() + is_active = Bool() + is_superuser = Bool() diff --git a/storage_service/locations/api/resources.py b/storage_service/locations/api/resources.py index 15d8b44fc..f38e1dc75 100644 --- a/storage_service/locations/api/resources.py +++ b/storage_service/locations/api/resources.py @@ -664,6 +664,7 @@ def _store_bundle(self, bundle): bundle.obj.store_aip(origin_location, origin_path, related_package_uuid, premis_events=events, premis_agents=agents, aip_subtype=aip_subtype) + bundle.obj.index_file_data_from_aip_mets() elif bundle.obj.package_type in (Package.TRANSFER,) and bundle.obj.current_location.purpose in (Location.BACKLOG,): # Move transfer to backlog bundle.obj.backlog_transfer(origin_location, origin_path) diff --git a/storage_service/locations/api/sword/helpers.py b/storage_service/locations/api/sword/helpers.py index 1dc1315d2..5ad1d9492 100644 --- a/storage_service/locations/api/sword/helpers.py +++ b/storage_service/locations/api/sword/helpers.py @@ -143,7 +143,7 @@ def deposit_downloading_status(deposit): def spawn_download_task(deposit_uuid, objects, subdir=None): """ - Spawn an asynchrnous batch download + Spawn an asynchronous batch download """ p = Process(target=_fetch_content, args=(deposit_uuid, objects, subdir)) p.start() diff --git a/storage_service/locations/api/urls.py b/storage_service/locations/api/urls.py index 9b956f440..f17fa2427 100644 --- a/storage_service/locations/api/urls.py +++ b/storage_service/locations/api/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import include, url from tastypie.api import Api from locations.api import v1, v2 +import locations.api.beta as beta_api from locations.api.sword import views @@ -23,4 +24,5 @@ url(r'v1/sword/$', views.service_document, name='sword_service_document'), url(r'', include(v2_api.urls)), url(r'v2/sword/$', views.service_document, name='sword_service_document'), + url(r'beta/', include(beta_api.urls)), ] diff --git a/storage_service/locations/fixtures/files.json b/storage_service/locations/fixtures/files.json new file mode 100644 index 000000000..06a3f7c10 --- /dev/null +++ b/storage_service/locations/fixtures/files.json @@ -0,0 +1,65 @@ +[ +{ + "pk": 3, + "model": "locations.file", + "fields": { + "uuid": "aad59769-0c6b-48cd-b54a-63092b9718fc", + "package": 3, + "name": "test_sip/objects/file3.pdf", + "source_id": "aa24a977-ad7a-4886-b17c-8b32ab4a7955", + "source_package": "a59033c2-7fa7-41e2-9209-136f07174692", + "checksum": "", + "stored": false, + "accessionid": "", + "origin": "91d13621-d2c1-4c70-a67e-77e96dced036", + "ingestion_time": "2015-12-15T03:00:05.020871+00:00", + "size": 512, + "format_name": "Acrobat PDF 1.5 - Portable Document Format", + "pronom_id": "fmt/19", + "normalized": true, + "valid": true + } +}, +{ + "pk": 4, + "model": "locations.file", + "fields": { + "uuid": "bbd59769-0c6b-48cd-b54a-63092b9718fc", + "package": 7, + "name": "test_sip/objects/file3.pdf", + "source_id": "bb24a977-ad7a-4886-b17c-8b32ab4a7955", + "source_package": "a59033c2-7fa7-41e2-9209-136f07174692", + "checksum": "", + "stored": false, + "accessionid": "", + "origin": "91d13621-d2c1-4c70-a67e-77e96dced036", + "ingestion_time": "2015-12-16T03:00:05.020871+00:00", + "size": 512, + "format_name": "Acrobat PDF 1.5 - Portable Document Format", + "pronom_id": "fmt/19", + "normalized": true, + "valid": true + } +}, +{ + "pk": 5, + "model": "locations.file", + "fields": { + "uuid": "ccd59769-0c6b-48cd-b54a-63092b9718fc", + "package": 7, + "name": "test_sip/objects/file3.pdf", + "source_id": "cc24a977-ad7a-4886-b17c-8b32ab4a7955", + "source_package": "a59033c2-7fa7-41e2-9209-136f07174692", + "checksum": "", + "stored": false, + "accessionid": "", + "origin": "91d13621-d2c1-4c70-a67e-77e96dced036", + "ingestion_time": "2015-12-17T03:00:05.020871+00:00", + "size": 512, + "format_name": "Acrobat PDF 1.5 - Portable Document Format", + "pronom_id": "fmt/19", + "normalized": true, + "valid": true + } +} +] diff --git a/storage_service/locations/fixtures/package.json b/storage_service/locations/fixtures/package.json index c01ddff29..982e9a073 100644 --- a/storage_service/locations/fixtures/package.json +++ b/storage_service/locations/fixtures/package.json @@ -144,7 +144,13 @@ "checksum": "", "stored": false, "accessionid": "", - "origin": "bd17e3cf-afb6-4067-b7d0-472482767ee2" + "origin": "bd17e3cf-afb6-4067-b7d0-472482767ee2", + "ingestion_time": "2016-12-15T03:00:05.020871Z", + "size": 256, + "format_name": "Plain Text File", + "pronom_id": "x-fmt/111", + "normalized": true, + "valid": true } }, { @@ -159,7 +165,13 @@ "checksum": "", "stored": false, "accessionid": "", - "origin": "91d13621-d2c1-4c70-a67e-77e96dced036" + "origin": "91d13621-d2c1-4c70-a67e-77e96dced036", + "ingestion_time": "2015-12-15T03:00:05.020871Z", + "size": 512, + "format_name": "AutoCAD External Database Configuration File", + "pronom_id": "x-fmt/112", + "normalized": false, + "valid": false } } ] diff --git a/storage_service/locations/migrations/0020_add_fields_to_file.py b/storage_service/locations/migrations/0020_add_fields_to_file.py new file mode 100644 index 000000000..170d3f2ed --- /dev/null +++ b/storage_service/locations/migrations/0020_add_fields_to_file.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('locations', '0019_s3'), + ] + + operations = [ + migrations.AddField( + model_name='file', + name='format_name', + field=models.TextField(max_length=128, blank=True), + ), + migrations.AddField( + model_name='file', + name='ingestion_time', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='file', + name='normalized', + field=models.BooleanField( + default=False, + help_text=b'Whether or not file has been normalized'), + ), + migrations.AddField( + model_name='file', + name='pronom_id', + field=models.TextField(max_length=128, blank=True), + ), + migrations.AddField( + model_name='file', + name='size', + field=models.IntegerField( + default=0, + help_text=b'Size in bytes of the file'), + ), + migrations.AddField( + model_name='file', + name='valid', + field=models.NullBooleanField( + default=None, + help_text=b'Indicates whether validation has occurred and, if' + ' so, whether or not the file was assessed as valid'), + ), + migrations.AlterField( + model_name='location', + name='pipeline', + field=models.ManyToManyField( + help_text='The Archivematica instance using this location.', + to='locations.Pipeline', verbose_name='Pipeline', + through='locations.LocationPipeline', blank=True), + ), + ] diff --git a/storage_service/locations/models/event.py b/storage_service/locations/models/event.py index 1f105cad1..4961a9685 100644 --- a/storage_service/locations/models/event.py +++ b/storage_service/locations/models/event.py @@ -135,9 +135,15 @@ class File(models.Model): help_text=_l("Unique identifier")) package = models.ForeignKey('Package', null=True) name = models.TextField(max_length=1000) + ingestion_time = models.DateTimeField(null=True) + source_id = models.TextField(max_length=128) source_package = models.TextField(blank=True, help_text=_l("Unique identifier of originating unit")) + size = models.IntegerField( + default=0, help_text=_l("Size in bytes of the file")) + format_name = models.TextField(blank=True, max_length=128) + pronom_id = models.TextField(blank=True, max_length=128) # Sized to fit sha512 checksum = models.TextField(max_length=128) stored = models.BooleanField(default=False) @@ -145,6 +151,11 @@ class File(models.Model): help_text=_l("Accession ID of originating transfer")) origin = UUIDField(editable=False, unique=False, version=4, blank=True, help_text=_l("Unique identifier of originating Archivematica dashboard")) + normalized = models.BooleanField(blank=False, default=False, + help_text="Whether or not file has been normalized") + valid = models.NullBooleanField(default=None, null=True, + help_text="Indicates whether validation has occurred and, if so," + " whether or not the file was assessed as valid") class Meta: verbose_name = _l("File") diff --git a/storage_service/locations/models/location.py b/storage_service/locations/models/location.py index a2f1d5315..53f5f3692 100644 --- a/storage_service/locations/models/location.py +++ b/storage_service/locations/models/location.py @@ -60,7 +60,7 @@ class Location(models.Model): pipeline = models.ManyToManyField('Pipeline', through='LocationPipeline', blank=True, verbose_name=_l('Pipeline'), - help_text=_l("UUID of the Archivematica instance using this location.")) + help_text=_l("The Archivematica instance using this location.")) relative_path = models.TextField( verbose_name=_l('Relative Path'), diff --git a/storage_service/locations/models/package.py b/storage_service/locations/models/package.py index 4412042b4..d587c2199 100644 --- a/storage_service/locations/models/package.py +++ b/storage_service/locations/models/package.py @@ -20,9 +20,10 @@ # Third party dependencies, alphabetical import bagit -import jsonfield from django_extensions.db.fields import UUIDField +import jsonfield import metsrw +# from metsrw.plugins import premisrw, yapremisrw from metsrw.plugins import premisrw import requests @@ -1246,7 +1247,7 @@ def create_pointer_file(self, LOGGER.info('Pointer file constructed for %s is valid.', self.uuid) else: LOGGER.warning('Pointer file constructed for %s is not valid.\n%s', - self.uuid, metsrw.report_string(report)) + self.uuid, metsrw.report_string(report)) return pointer_file def _create_aip_premis_object(self, @@ -1633,6 +1634,60 @@ def backlog_transfer(self, origin_location, origin_path): self.status = Package.UPLOADED self.save() + def index_file_data_from_aip_mets(self): + """Index file metadata from this package's AIP METS file. + + Attempts to read an Archivematica AIP METS file inside this package, + then uses the retrieved metadata to generate one entry in the File + table in the database for each file inside the package. + + :raises StorageException: if the transfer METS cannot be found, + or if required elements are missing. + TODO/QUESTION: what happens if the file has been placed in backlog and + indexed via ``index_file_data_from_transfer_mets``? Should we + update an existing ``File`` instance if there is one? Otherwise we + may have multiple ``File`` instances for a single file. + TODO/QUESTION: should this be called from within ``store_aip``? If + calling it afterwards as is done now is causing the AIP to be moved + back *to* the SS, then the answer is "yes". + """ + aip_dir_name = os.path.basename(os.path.splitext(self.full_path)[0]) + mets_relative_path = os.path.join('data', 'METS.' + self.uuid + '.xml') + relative_path = os.path.join(aip_dir_name, mets_relative_path) + temp_dir = None + if self.is_compressed: + path_to_mets, temp_dir = self.extract_file( + relative_path=relative_path) + else: + path_to_mets = os.path.join(self.full_path, mets_relative_path) + mw = metsrw.METSDocument.fromfile(path_to_mets) + feature_broker = metsrw.feature_broker + # TODO + # feature_broker.provide('premis_object_class', yapremisrw.Object) + # feature_broker.provide('premis_event_class', yapremisrw.Event) + for fsentry in mw.all_files(): + metadata = _parse_file_metadata(fsentry) + if metadata is not None: + aip_file = File() + aip_file.package = self + aip_file.source_id = metadata['uuid'] + # TODO: seems odd to duplicate this information when it could + # always be retrieved via relations: + # ``file.package.origin_pipeline.uuid`` + aip_file.origin = self.origin_pipeline.uuid + aip_file.name = os.path.join(aip_dir_name, fsentry.path) + aip_file.ingestion_time = mw.createdate + aip_file.format_name = metadata.get('format_name', '') + aip_file.size = int(metadata.get('size', 0)) + aip_file.pronom_id = metadata.get('pronom_id', '') + aip_file.normalized = metadata.get('normalized', False) + aip_file.valid = metadata.get('valid') + aip_file.save() + feature_broker.provide('premis_object_class', premisrw.PREMISObject) + feature_broker.provide('premis_event_class', premisrw.PREMISEvent) + if temp_dir: + shutil.rmtree(temp_dir) + def check_fixity(self, force_local=False, delete_after=True): """ Scans the package to verify its checksums. @@ -2772,3 +2827,63 @@ def write_pointer_file(pointer_file, pointer_file_path): if not os.path.isdir(pointer_dir_path): os.makedirs(pointer_dir_path) pointer_file.write(pointer_file_path, pretty_print=True) + + +def _parse_file_metadata(fsentry): + """Cycle through an FSEntry object's AMDsec subsections and consolidate + PREMIS object/event metadata. + """ + premis_objects = fsentry.get_premis_objects() + if not premis_objects: + return None + metadata = {} + premis_object = premis_objects[0] + + # Don't provide metadata for METS files + if premis_object.characteristics[0]['is_mets']: + return None + + metadata['filename'] = premis_object.original_name + + file_uuid = None + for po_id in premis_object.object_identifiers: + if po_id['type'] == 'UUID': + file_uuid = po_id['value'] + break + if file_uuid: + metadata['uuid'] = file_uuid + else: + LOGGER.warning('Unable to find a UUID for a file (fsentry)' + ' documented in a METS file!') + return None + + if premis_object.characteristics[0]['size'] is not None: + metadata['size'] = premis_object.characteristics[0]['size'] + + # Add file format to metadata + if len(premis_object.characteristics[0]['formats']): + first_format = premis_object.characteristics[0]['formats'][0] + if first_format['name'] is not None: + metadata['format_name'] = first_format['name'] + if first_format['version'] is not None: + metadata['format_version'] = first_format['version'] + if first_format['registry_name'] == 'PRONOM': + metadata['pronom_id'] = first_format['registry_key'] + + # Add normalization status to metadata + if (len(premis_object.relationships) and + premis_object.relationships[0]['type'] == 'derivation'): + if premis_object.relationships[0]['subtype'] == 'has source': + metadata['derivative'] = True + if premis_object.relationships[0]['subtype'] == 'is source of': + metadata['normalized'] = True + + # Cycle through event data to see if file has been validated and if it + # passed + for premis_event in fsentry.get_premis_events(): + # Indicate whether or not a file has been validated in metadata and if + # it passed + if premis_event.event_type == 'validation': + metadata['valid'] = premis_event.outcomes[0]['outcome'] == "pass" + + return metadata diff --git a/storage_service/locations/tests/test_api_beta.py b/storage_service/locations/tests/test_api_beta.py new file mode 100644 index 000000000..37c268e77 --- /dev/null +++ b/storage_service/locations/tests/test_api_beta.py @@ -0,0 +1,407 @@ +import json +import os +from uuid import uuid4 + +from django.contrib.auth.models import User +from django.test import TestCase +from django.utils.dateparse import parse_datetime +from tastypie.models import ApiKey + +from locations import models +from locations.api.beta import api +from locations.api.beta.remple import Resources + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +FIXTURES_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', 'fixtures', '')) + + +SERVER_PATH = api.get_dflt_server_path() +API_PATH_PREFIX = '/api/beta/' + + +class TestBetaAPI(TestCase): + + fixtures = ['base.json', 'pipelines.json', 'package.json', 'files.json'] + + def setUp(self): + user = User.objects.get(username='test') + api_key = str(ApiKey.objects.get(user=user)).split()[0] + self.client.defaults['HTTP_AUTHORIZATION'] = 'ApiKey test:{}'.format(api_key) + + def test_auth(self): + original_auth = self.client.defaults['HTTP_AUTHORIZATION'] + response = self.client.get('{}files/'.format(API_PATH_PREFIX), + content_type='application/json') + assert response.status_code == 200 + self.client.defaults['HTTP_AUTHORIZATION'] = 'monkeys' + response = self.client.get('{}files/'.format(API_PATH_PREFIX), + content_type='application/json') + assert response.status_code == 403 + self.client.defaults['HTTP_AUTHORIZATION'] = original_auth + + def test_get_many_files(self): + + known_files = {f.uuid: f for f in models.File.objects.all()} + assert known_files + known_file_count = len(known_files) + response = self.client.get('{}files/'.format(API_PATH_PREFIX), + content_type='application/json') + assert response.status_code == 200 + fetched_files = json.loads(response.content) + assert fetched_files['paginator']['count'] == known_file_count + fetched_files = {f['uuid']: f for f in fetched_files['items']} + for file_uuid, file_dict in fetched_files.items(): + file_instance = known_files[file_uuid] + for attr in ('accessionid', + 'checksum', + 'id', + 'name', + 'origin', + 'source_id', + 'source_package', + 'stored', + 'uuid', + 'size', + 'format_name', + 'pronom_id', + 'normalized', + 'valid',): + assert file_dict[attr] == getattr(file_instance, attr) + assert file_dict['resource_uri'] == Resources.get_resource_uri( + SERVER_PATH, 'files', file_uuid) + if file_instance.package: + assert file_dict['package'] == Resources.get_resource_uri( + SERVER_PATH, 'packages', file_instance.package.uuid) + + def test_files_search(self): + """Test searching over file resources.""" + + # Search for files with PRONOM id x-fmt/111. + known_files = {f.uuid: f for f in models.File.objects.all()} + known_matches = [f for f in known_files.values() + if f.pronom_id == 'x-fmt/111'] + query = {'query': {'filter': ['pronom_id', '=', 'x-fmt/111']}} + response = self.client.post('{}files/search/'.format(API_PATH_PREFIX), + json.dumps(query), + content_type='application/json') + assert response.status_code == 200 + response = json.loads(response.content) + assert sorted([f['uuid'] for f in response['items']]) == sorted( + [f.uuid for f in known_matches]) + + # Count the number of files with PRONOM id fmt/19 that were ingested + # between 2015-12-16 and 2015-12-17. Use ISO timezones in search terms. + fmt = 'fmt/19' + max_dt_iso = '2015-12-17T11:59:59+00:00' + min_dt_iso = '2015-12-16T00:00:00+00:00' + max_dt = parse_datetime(max_dt_iso) + min_dt = parse_datetime(min_dt_iso) + known_matches = models.File.objects\ + .filter(pronom_id__exact=fmt)\ + .filter(ingestion_time__lte=max_dt)\ + .filter(ingestion_time__gte=min_dt) + assert len(known_matches) == 2 + query = {'query': {'filter': [ + 'and', [['pronom_id', '=', fmt], + ['ingestion_time', '<=', max_dt_iso], + ['ingestion_time', '>=', min_dt_iso]]]}} + response = self.client.post('{}files/search/'.format(API_PATH_PREFIX), + json.dumps(query), + content_type='application/json') + assert response.status_code == 200 + response = json.loads(response.content) + assert response['paginator']['count'] == 2 + assert sorted([f['uuid'] for f in response['items']]) == sorted( + [f.uuid for f in known_matches]) + + # Count the number of files with PRONOM id fmt/19 that were ingested + # between 2015-12-16 and 2015-12-17. Use 'Z' for UTC timezone. + max_dt_iso = '2015-12-17T11:59:59Z' + min_dt_iso = '2015-12-16T00:00:00Z' + max_dt = parse_datetime(max_dt_iso) + min_dt = parse_datetime(min_dt_iso) + query = {'query': {'filter': [ + 'and', [['pronom_id', '=', fmt], + ['ingestion_time', '<=', max_dt_iso], + ['ingestion_time', '>=', min_dt_iso]]]}} + response = self.client.post('{}files/search/'.format(API_PATH_PREFIX), + json.dumps(query), + content_type='application/json') + assert response.status_code == 200 + response = json.loads(response.content) + assert response['paginator']['count'] == 2 + assert sorted([f['uuid'] for f in response['items']]) == sorted( + [f.uuid for f in known_matches]) + + # Search files based on the type of the package that they belong to: + # relational search. + files_in_aips = models.File.objects.filter(package__package_type='AIP') + query = {'query': {'filter': ['package', 'package_type', '=', 'AIP']}} + response = self.client.post('{}files/search/'.format(API_PATH_PREFIX), + json.dumps(query), + content_type='application/json') + assert response.status_code == 200 + response = json.loads(response.content) + assert response['paginator']['count'] == len(files_in_aips) + assert sorted([f['uuid'] for f in response['items']]) == sorted( + [f.uuid for f in files_in_aips]) + + def test_mutate_location(self): + """Test creation, updating and deletion of a location.""" + + # 1. Create a new location + existing_locations = models.Location.objects.all() + response = self.client.get( + '{}locations/'.format(API_PATH_PREFIX), content_type='application/json') + assert response.status_code == 200 + fetched_locations = json.loads(response.content) + assert sorted(l.uuid for l in existing_locations) == sorted( + l['uuid'] for l in fetched_locations['items']) + first_pipeline = models.Pipeline.objects.first() + pipeline_uri = Resources.get_resource_uri( + SERVER_PATH, 'pipelines', first_pipeline.uuid) + first_space = models.Space.objects.first() + space_uri = Resources.get_resource_uri( + SERVER_PATH, 'spaces', first_space.uuid) + new_loc_descr = 'My new location' + new_loc_purp = 'AS' + new_loc_rel_path = ( + 'var/archivematica/sharedDirectory/www/MyNewAIPsStore') + new_location = { + 'description': new_loc_descr, + 'enabled': True, + 'pipeline': [pipeline_uri], # list of AM pipeline URIs + 'purpose': new_loc_purp, + 'quota': None, + 'relative_path': new_loc_rel_path, + 'replicators': [], + 'space': space_uri # URI of a Space + } + response = self.client.post('{}locations/'.format(API_PATH_PREFIX), + json.dumps(new_location), + content_type='application/json') + assert response.status_code == 201 + new_location = json.loads(response.content) + assert new_location['description'] == new_loc_descr + assert new_location['purpose'] == new_loc_purp + assert new_location['relative_path'] == new_loc_rel_path + assert new_location['replicators'] == [] + for thing in new_location['pipeline']: + assert_is_resource_uri(thing, 'pipelines') + new_loc_uri = new_location['resource_uri'] + assert_is_resource_uri(new_loc_uri, 'locations') + assert_is_resource_uri(new_location['space'], 'spaces') + + # 2.a. Update the location. + updated_loc_descr = 'My new awesome transfer source location' + updated_loc_purp = 'TS' + updated_loc_rel_path = 'home' + updated_location = { + 'description': updated_loc_descr, + 'enabled': True, + 'pipeline': [pipeline_uri], + 'purpose': updated_loc_purp, + 'quota': None, + 'relative_path': updated_loc_rel_path, + 'replicators': [], + 'space': space_uri + } + response = self.client.put(new_loc_uri, + json.dumps(updated_location), + content_type='application/json') + assert response.status_code == 200 + updated_location = json.loads(response.content) + assert updated_location['description'] == updated_loc_descr + assert updated_location['purpose'] == updated_loc_purp + assert updated_location['relative_path'] == updated_loc_rel_path + assert updated_location['replicators'] == [] + assert updated_location['resource_uri'] == new_loc_uri + for thing in updated_location['pipeline']: + assert_is_resource_uri(thing, 'pipelines') + assert_is_resource_uri(updated_location['resource_uri'], 'locations') + assert_is_resource_uri(updated_location['space'], 'spaces') + + # 2.b. Invalid update attempt + bad_loc_purp = 'QQ' + bad_space_uuid = str(uuid4()) + bad_space_uri = Resources.get_resource_uri( + SERVER_PATH, 'spaces', bad_space_uuid) + bad_updated_location = { + 'description': updated_loc_descr, + 'enabled': True, + 'pipeline': [pipeline_uri], + 'purpose': bad_loc_purp, + 'quota': None, + 'relative_path': updated_loc_rel_path, + 'replicators': [], + 'space': bad_space_uri + } + bad_update_resp = self.client.put(new_loc_uri, + json.dumps(bad_updated_location), + content_type='application/json') + assert bad_update_resp.status_code == 400 + bad_update_resp = json.loads(bad_update_resp.content) + assert bad_update_resp['error']['purpose'].startswith( + 'Value must be one of: ') + assert bad_update_resp['error']['space'] == ( + 'There is no space with pk {}.'.format(bad_space_uuid)) + + # 3. Delete the location + response = self.client.delete( + new_loc_uri, content_type='application/json') + deleted_location = json.loads(response.content) + assert response.status_code == 200 + assert deleted_location == updated_location + response = self.client.get( + updated_location['resource_uri'], + content_type='application/json') + assert response.status_code == 404 + nonexistent_location = json.loads(response.content) + assert nonexistent_location['error'].startswith( + 'There is no location with uuid ') + + def test_mutate_space(self): + """Test creation, updating and deletion of a space. + + Space is somewhat special in that its access protocol should be + immutable after creation. + """ + + # 1. Create a new space + existing_spaces = models.Space.objects.all() + response = self.client.get( + '{}spaces/'.format(API_PATH_PREFIX), content_type='application/json') + assert response.status_code == 200 + fetched_spaces = json.loads(response.content) + assert sorted(l.uuid for l in existing_spaces) == sorted( + l['uuid'] for l in fetched_spaces['items']) + new_space_acc_prot = 'FS' + new_space_path = '/' + new_space_staging_path = '/var/archivematica/storage_service' + new_space = { + 'access_protocol': new_space_acc_prot, + 'path': new_space_path, + 'staging_path': new_space_staging_path, + 'size': None, + } + resp = self.client.post('{}spaces/'.format(API_PATH_PREFIX), + json.dumps(new_space), + content_type='application/json') + assert resp.status_code == 201 + new_space = json.loads(resp.content) + assert new_space['access_protocol'] == new_space_acc_prot + assert new_space['path'] == new_space_path + assert new_space['staging_path'] == new_space_staging_path + new_space_uri = new_space['resource_uri'] + assert_is_resource_uri(new_space_uri, 'spaces') + + # 2.a. Update the space + updated_space_path = '/abc' + updated_space = { + 'path': updated_space_path, + 'staging_path': new_space_staging_path, + 'size': None, + } + response = self.client.put(new_space_uri, + json.dumps(updated_space), + content_type='application/json') + assert response.status_code == 200 + updated_space = json.loads(response.content) + assert updated_space['access_protocol'] == new_space_acc_prot + assert updated_space['path'] == updated_space_path + assert updated_space['staging_path'] == new_space_staging_path + + # 2.b. Can't update a space's access protocol + updated_space = { + 'access_protocol': 'GPG', # This is BAD + 'path': updated_space_path, + 'staging_path': new_space_staging_path, + 'size': 100, + } + resp = self.client.put(new_space_uri, + json.dumps(updated_space), + content_type='application/json') + assert resp.status_code == 400 + resp = json.loads(resp.content) + assert resp['error'] == ( + 'The input field u\'access_protocol\' was not expected.') + + def test_get_create_update_data(self): + """Test that the GET //new/ and ///edit/ requests + return the data needed to create new and edit existing resources. + """ + + # GET locations/new/ should return a dict containing resource URIs for + # all locations, pipelines and spaces + response = self.client.get('{}locations/new/'.format(API_PATH_PREFIX), + content_type='application/json') + create_data = json.loads(response.content) + assert sorted(create_data.keys()) == sorted( + ['locations', 'pipelines', 'spaces']) + for resource_coll_name, value_list in create_data.items(): + for element in value_list: + assert_is_resource_uri(element, resource_coll_name) + + # GET locations//edit/ should return a dict containing resource + # URIs for all locations, pipelines and spaces + aloc = models.Location.objects.first() + response = self.client.get( + '{}locations/{}/edit/'.format(API_PATH_PREFIX, aloc.uuid), + content_type='application/json') + edit_data = json.loads(response.content) + assert edit_data['resource']['uuid'] == aloc.uuid + assert sorted(edit_data['data'].keys()) == sorted( + ['locations', 'pipelines', 'spaces']) + for resource_coll_name, value_list in edit_data['data'].items(): + for element in value_list: + assert_is_resource_uri(element, resource_coll_name) + + def test_new_search(self): + """Test that the GET //new_search/ request return the data needed + to perform a new search. + """ + response = self.client.get( + '{}locations/new_search/'.format(API_PATH_PREFIX), + content_type='application/json') + search_data = json.loads(response.content) + search_params = search_data['search_parameters'] + assert 'attributes' in search_params + assert 'relations' in search_params + assert search_params['attributes']['pipeline'][ + 'foreign_model'] == 'Pipeline' + assert search_params['attributes']['pipeline'][ + 'type'] == 'collection' + assert search_params['attributes']['space'][ + 'foreign_model'] == 'Space' + assert search_params['attributes']['space'][ + 'type'] == 'scalar' + assert search_params['attributes']['quota'] == {} + assert '=' in search_params['relations'] + assert 'regex' in search_params['relations'] + assert 'regexp' in search_params['relations'] + assert 'like' in search_params['relations'] + assert 'contains' in search_params['relations'] + assert '<=' in search_params['relations'] + + def test_read_only_resources(self): + for rsrc_coll in ('files', 'packages'): + response = self.client.post( + '{}{}/'.format(API_PATH_PREFIX, rsrc_coll), + json.dumps({'foo': 'bar'}), + content_type='application/json') + payload = json.loads(response.content) + assert response.status_code == 404 + assert payload['error'] == 'This resource is read-only.' + + +def assert_is_resource_uri(string, resource): + _, _, xtrctd_rsrc, xtrctd_uuid = list(filter(None, string.split('/'))) + assert resource == xtrctd_rsrc + recomposed = [] + parts = xtrctd_uuid.split('-') + assert [len(p) for p in parts] == [8, 4, 4, 4, 12] + for part in parts: + new_part = ''.join(c for c in part if c in '0123456789abcdef') + recomposed.append(new_part) + recomposed = '-'.join(recomposed) + assert recomposed == xtrctd_uuid diff --git a/storage_service/locations/tests/test_api_beta_querybuilder.py b/storage_service/locations/tests/test_api_beta_querybuilder.py new file mode 100644 index 000000000..74663a75e --- /dev/null +++ b/storage_service/locations/tests/test_api_beta_querybuilder.py @@ -0,0 +1,387 @@ +# -*- coding: utf8 -*- +"""Tests for the querybuilder module.""" + +from __future__ import print_function +from datetime import datetime + +from django.db.models import Q + +from locations.models import Package, File +from locations.api.beta.remple import QueryBuilder + + +def test_query_expression_construction_dates(): + qb = QueryBuilder(File, primary_key='uuid') + FILTER_1 = ['ingestion_time', '<=', '2015-12-17T11:59:59'] + qe = qb.get_query_expression(FILTER_1) + filter_obj = qe.children[0] + assert qb.errors == {} + assert isinstance(qe, Q) + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'ingestion_time__lte' + assert isinstance(filter_obj[1], datetime) + FILTER_2 = [ + 'and', [['pronom_id', '=', 'fmt/19'], + ['ingestion_time', '<=', '2015-12-17T11:59:59'], + ['ingestion_time', '>=', '2015-12-16T00:00:01']]] + qe = qb.get_query_expression(FILTER_2) + assert qe.connector == 'AND' + assert isinstance(qe, Q) + assert qb.errors == {} + assert len(qe.children) == 3 + + +def test_query_expression_construction(): + """Test that the ``get_query_expression`` method can convert Python lists + to the corresponding Django Q expression. + """ + qb = QueryBuilder(Package, primary_key='uuid') + assert qb.model_name == 'Package' + + FILTER_1 = ['description', 'like', '%a%'] + qe = qb.get_query_expression(FILTER_1) + filter_obj = qe.children[0] + assert qb.errors == {} + assert isinstance(qe, Q) + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'description__contains' + assert filter_obj[1] == '%a%' + + FILTER_2 = ['origin_pipeline', 'description', 'regex', '^[JS]'] + qe = qb.get_query_expression(FILTER_2) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'origin_pipeline__description__regex' + assert filter_obj[1] == '^[JS]' + + FILTER_3A = ['origin_pipeline', '=', None] + qe = qb.get_query_expression(FILTER_3A) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'origin_pipeline__isnull' + assert filter_obj[1] is True + + FILTER_3B = ['origin_pipeline', '!=', None] + qe = qb.get_query_expression(FILTER_3B) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'origin_pipeline__isnull' + assert filter_obj[1] is False + + UUID_1 = '75a481ea-6e56-4800-81b2-6679d1e8f5ea' + UUID_2 = '90bd9d01-22f5-447c-8b4b-15578c6b8f37' + FILTER_4 = ['replicas', 'uuid', 'in', [UUID_1, UUID_2]] + qe = qb.get_query_expression(FILTER_4) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'replicas__uuid__in' + assert sorted(filter_obj[1]) == sorted([UUID_1, UUID_2]) + + FILTER_5 = ['not', ['description', 'like', '%a%']] + qe = qb.get_query_expression(FILTER_5) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.negated is True + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'description__contains' + assert filter_obj[1] == '%a%' + + FILTER_6 = ['and', [['description', 'like', '%a%'], + ['origin_pipeline', 'description', '=', + 'Well described.']]] + qe = qb.get_query_expression(FILTER_6) + assert qb.errors == {} + filter_obj_1 = qe.children[0] + filter_obj_2 = qe.children[1] + assert len(qe.children) == 2 + assert qe.negated is False + assert qe.connector == 'AND' + assert filter_obj_1[0] == 'description__contains' + assert filter_obj_1[1] == '%a%' + assert filter_obj_2[0] == 'origin_pipeline__description__exact' + assert filter_obj_2[1] == 'Well described.' + + FILTER_7 = ['or', [['description', 'like', '%a%'], + ['origin_pipeline', 'description', '=', + 'Well described.']]] + qe = qb.get_query_expression(FILTER_7) + assert qb.errors == {} + filter_obj_1 = qe.children[0] + filter_obj_2 = qe.children[1] + assert len(qe.children) == 2 + assert qe.negated is False + assert qe.connector == 'OR' + assert filter_obj_1[0] == 'description__contains' + assert filter_obj_1[1] == '%a%' + assert filter_obj_2[0] == 'origin_pipeline__description__exact' + assert filter_obj_2[1] == 'Well described.' + + FILTER_8 = ['and', [['description', 'like', '%a%'], + ['not', ['description', 'like', 'T%']], + ['or', [['size', '<', 1000], + ['size', '>', 512]]]]] + qe = qb.get_query_expression(FILTER_8) + assert qb.errors == {} + filter_obj_1 = qe.children[0] + filter_obj_2 = qe.children[1] + filter_obj_3 = qe.children[2] + assert len(qe.children) == 3 + assert qe.negated is False + assert qe.connector == 'AND' + assert filter_obj_1[0] == 'description__contains' + assert filter_obj_1[1] == '%a%' + assert filter_obj_2.negated is True + assert filter_obj_2.children[0][0] == 'description__contains' + assert filter_obj_2.children[0][1] == 'T%' + assert filter_obj_3.negated is False + assert filter_obj_3.connector == 'OR' + assert len(filter_obj_3.children) == 2 + subchild_1 = filter_obj_3.children[0] + subchild_2 = filter_obj_3.children[1] + assert subchild_1[0] == 'size__lt' + assert subchild_1[1] == 1000 + assert subchild_2[0] == 'size__gt' + assert subchild_2[1] == 512 + + qb.clear_errors() + + BAD_FILTER_1 = ['gonzo', 'like', '%a%'] + qe = qb.get_query_expression(BAD_FILTER_1) + assert qe is None + assert qb.errors['Package.gonzo'] == ( + 'Searching on Package.gonzo is not permitted') + assert qb.errors['Malformed query error'] == ( + 'The submitted query was malformed') + + qb.clear_errors() + + BAD_FILTER_2 = ['origin_pipeline', '<', 2] + qe = qb.get_query_expression(BAD_FILTER_2) + # Note: ``qe`` will be the nonsensical + # ``(AND: ('origin_pipeline__None', 2))`` here. This is ok, since the + # public method ``get_query_set`` will raise an error before executing this + # query against the db. + assert qb.errors['Package.origin_pipeline.<'] == ( + 'The relation < is not permitted for Package.origin_pipeline') + + +def test_order_by_expression_construction(): + """Test that the ``get_order_bys`` method can convert Python lists of lists + to a list of strings that the Django ORM's ``order_by`` method can use to + creat an SQL ``ORDER BY`` clause. + """ + + qb = QueryBuilder(Package) + assert qb.model_name == 'Package' + assert qb.primary_key == 'uuid' + + ORDER_BYS_1 = [['description']] + order_bys = qb.get_order_bys(ORDER_BYS_1) + assert order_bys == ['description'] + + ORDER_BYS_2 = [['description', 'desc']] + order_bys = qb.get_order_bys(ORDER_BYS_2) + assert order_bys == ['-description'] + + ORDER_BYS_3 = [['origin_pipeline', 'uuid', 'desc']] + order_bys = qb.get_order_bys(ORDER_BYS_3) + assert order_bys == ['-origin_pipeline__uuid'] + + ORDER_BYS_4 = [['origin_pipeline', 'uuid', 'asc']] + order_bys = qb.get_order_bys(ORDER_BYS_4) + assert order_bys == ['origin_pipeline__uuid'] + + ORDER_BYS_5 = [['origin_pipeline', 'monkey', 'asc']] + order_bys = qb.get_order_bys(ORDER_BYS_5) + assert qb.errors['Pipeline.monkey'] == ( + 'Searching on Pipeline.monkey is not permitted') + + ORDER_BYS_6 = [['origin_pipeline', 'uuid', 'asc'], ['description', 'desc']] + order_bys = qb.get_order_bys(ORDER_BYS_6) + assert order_bys == ['origin_pipeline__uuid', '-description'] + + +def test_query_dict_expression_construction(): + """Test that the ``get_query_expression`` method can convert Python dicts + to the corresponding Django Q expression. + """ + qb = QueryBuilder(Package, primary_key='uuid') + assert qb.model_name == 'Package' + + FILTER_1 = {'attribute': 'description', 'relation': 'like', 'value': '%a%'} + qe = qb.get_query_expression(FILTER_1) + filter_obj = qe.children[0] + assert qb.errors == {} + assert isinstance(qe, Q) + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'description__contains' + assert filter_obj[1] == '%a%' + + FILTER_2 = {'attribute': 'origin_pipeline', + 'subattribute': 'description', + 'relation': 'regex', + 'value': '^[JS]'} + qe = qb.get_query_expression(FILTER_2) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'origin_pipeline__description__regex' + assert filter_obj[1] == '^[JS]' + + FILTER_3A = {'attribute': 'origin_pipeline', 'relation': '=', 'value': None} + qe = qb.get_query_expression(FILTER_3A) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'origin_pipeline__isnull' + assert filter_obj[1] is True + + FILTER_3B = {'attribute': 'origin_pipeline', 'relation': '!=', + 'value': None} + qe = qb.get_query_expression(FILTER_3B) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'origin_pipeline__isnull' + assert filter_obj[1] is False + + UUID_1 = '75a481ea-6e56-4800-81b2-6679d1e8f5ea' + UUID_2 = '90bd9d01-22f5-447c-8b4b-15578c6b8f37' + FILTER_4 = {'attribute': 'replicas', + 'subattribute': 'uuid', + 'relation': 'in', + 'value': [UUID_1, UUID_2]} + qe = qb.get_query_expression(FILTER_4) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'replicas__uuid__in' + assert sorted(filter_obj[1]) == sorted([UUID_1, UUID_2]) + + FILTER_5 = {'negation': 'not', + 'complement': {'attribute': 'description', + 'relation': 'like', + 'value': '%a%'}} + qe = qb.get_query_expression(FILTER_5) + filter_obj = qe.children[0] + assert qb.errors == {} + assert qe.negated is True + assert qe.connector == 'AND' + assert len(qe.children) == 1 + assert filter_obj[0] == 'description__contains' + assert filter_obj[1] == '%a%' + + FILTER_6 = {'conjunction': 'and', + 'complement': [{'attribute': 'description', + 'relation': 'like', + 'value': '%a%'}, + {'attribute': 'origin_pipeline', + 'subattribute': 'description', + 'relation': '=', + 'value': 'Well described.'}]} + qe = qb.get_query_expression(FILTER_6) + assert qb.errors == {} + filter_obj_1 = qe.children[0] + filter_obj_2 = qe.children[1] + assert len(qe.children) == 2 + assert qe.negated is False + assert qe.connector == 'AND' + assert filter_obj_1[0] == 'description__contains' + assert filter_obj_1[1] == '%a%' + assert filter_obj_2[0] == 'origin_pipeline__description__exact' + assert filter_obj_2[1] == 'Well described.' + + FILTER_7 = {'conjunction': 'or', + 'complement': [{'attribute': 'description', + 'relation': 'like', + 'value': '%a%'}, + {'attribute': 'origin_pipeline', + 'subattribute': 'description', + 'relation': '=', + 'value': 'Well described.'}]} + qe = qb.get_query_expression(FILTER_7) + assert qb.errors == {} + filter_obj_1 = qe.children[0] + filter_obj_2 = qe.children[1] + assert len(qe.children) == 2 + assert qe.negated is False + assert qe.connector == 'OR' + assert filter_obj_1[0] == 'description__contains' + assert filter_obj_1[1] == '%a%' + assert filter_obj_2[0] == 'origin_pipeline__description__exact' + assert filter_obj_2[1] == 'Well described.' + + FILTER_8 = {'conjunction': 'and', + 'complement': [{'attribute': 'description', + 'relation': 'like', + 'value': '%a%'}, + {'negation': 'not', + 'complement': {'attribute': 'description', + 'relation': 'like', + 'value': 'T%'}}, + {'conjunction': 'or', + 'complement': [{'attribute': 'size', + 'relation': '<', + 'value': 1000}, + {'attribute': 'size', + 'relation': '>', + 'value': 512}]}]} + qe = qb.get_query_expression(FILTER_8) + assert qb.errors == {} + filter_obj_1 = qe.children[0] + filter_obj_2 = qe.children[1] + filter_obj_3 = qe.children[2] + assert len(qe.children) == 3 + assert qe.negated is False + assert qe.connector == 'AND' + assert filter_obj_1[0] == 'description__contains' + assert filter_obj_1[1] == '%a%' + assert filter_obj_2.negated is True + assert filter_obj_2.children[0][0] == 'description__contains' + assert filter_obj_2.children[0][1] == 'T%' + assert filter_obj_3.negated is False + assert filter_obj_3.connector == 'OR' + assert len(filter_obj_3.children) == 2 + subchild_1 = filter_obj_3.children[0] + subchild_2 = filter_obj_3.children[1] + assert subchild_1[0] == 'size__lt' + assert subchild_1[1] == 1000 + assert subchild_2[0] == 'size__gt' + assert subchild_2[1] == 512 + + qb.clear_errors() + + BAD_FILTER_1 = {'attribute': 'gonzo', 'relation': 'like', 'value': '%a%'} + qe = qb.get_query_expression(BAD_FILTER_1) + assert qe is None + assert qb.errors['Package.gonzo'] == ( + 'Searching on Package.gonzo is not permitted') + assert qb.errors['Malformed query error'] == ( + 'The submitted query was malformed') + + qb.clear_errors() + + BAD_FILTER_2 = {'attribute': 'origin_pipeline', 'relation': '<', 'value': 2} + qe = qb.get_query_expression(BAD_FILTER_2) + # Note: ``qe`` will be the nonsensical + # ``(AND: ('origin_pipeline__None', 2))`` here. This is ok, since the + # public method ``get_query_set`` will raise an error before executing this + # query against the db. + assert qb.errors['Package.origin_pipeline.<'] == ( + 'The relation < is not permitted for Package.origin_pipeline') diff --git a/storage_service/locations/tests/test_api_beta_urls.py b/storage_service/locations/tests/test_api_beta_urls.py new file mode 100644 index 000000000..2b4f29f65 --- /dev/null +++ b/storage_service/locations/tests/test_api_beta_urls.py @@ -0,0 +1,243 @@ +# -*- coding: utf8 -*- +"""Tests for the api.beta.urls module.""" + +from __future__ import print_function + +import pytest + +from locations.api.beta.remple import ( + API, + UUID_PATT, + ID_PATT, + get_collection_targeting_regex, + get_member_targeting_regex, + Resources, +) +from locations.api.beta.resources import ( + Locations, + Packages, + Spaces, + Pipelines, +) + + +class MyPackages(Packages): + # Pretend pk for /packages/ is int id + primary_key = 'id' + + +def test_urls_construction(): + """Tests that we can configure URL routing for RESTful resources using a + simple dict of resource member names. + """ + # Configure routing and get a list of corresponding Django ``url`` + # instances as ``urlpatterns`` + resources = { + 'location': {'resource_cls': Locations}, + 'package': {'resource_cls': MyPackages}, + # Make /spaces/ non-searchable + 'space': {'resource_cls': Spaces, 'searchable': False}, + 'pipeline': {'resource_cls': Pipelines}, + } + + api = API(api_version='0.1.0', service_name='Monkey') + api.register_resources(resources) + urlpatterns = api.get_urlpatterns() + + # Make assertions about ``urlpatterns`` + urlpatterns_names_regexes = sorted( + [(up.name, up.regex.pattern) for up in urlpatterns]) + api_v_slug = 'v0_1' + expecteds = ( + ('{}_location'.format(api_v_slug), '^locations/(?P{})/$'.format(UUID_PATT)), + ('{}_location_edit'.format(api_v_slug), + '^locations/(?P{})/edit/$'.format(UUID_PATT)), + ('{}_locations'.format(api_v_slug), '^locations/$'), + ('{}_locations_new'.format(api_v_slug), '^locations/new/$'), + ('{}_locations_new_search'.format(api_v_slug), '^locations/new_search/$'), + ('{}_locations_search'.format(api_v_slug), '^locations/search/$'), + # Note the ID_PATT for /packages/ because of pk_patt above + ('{}_package'.format(api_v_slug), '^packages/(?P{})/$'.format(ID_PATT)), + ('{}_package_edit'.format(api_v_slug), + '^packages/(?P{})/edit/$'.format(ID_PATT)), + ('{}_packages'.format(api_v_slug), '^packages/$'), + ('{}_packages_new'.format(api_v_slug), '^packages/new/$'), + ('{}_packages_new_search'.format(api_v_slug), '^packages/new_search/$'), + ('{}_packages_search'.format(api_v_slug), '^packages/search/$'), + ('{}_pipeline'.format(api_v_slug), '^pipelines/(?P{})/$'.format(UUID_PATT)), + ('{}_pipeline_edit'.format(api_v_slug), + '^pipelines/(?P{})/edit/$'.format(UUID_PATT)), + ('{}_pipelines'.format(api_v_slug), '^pipelines/$'), + ('{}_pipelines_new'.format(api_v_slug), '^pipelines/new/$'), + ('{}_pipelines_new_search'.format(api_v_slug), '^pipelines/new_search/$'), + ('{}_pipelines_search'.format(api_v_slug), '^pipelines/search/$'), + # Note that the /spaces/ resource has no search-related routes. + ('{}_space'.format(api_v_slug), '^spaces/(?P{})/$'.format(UUID_PATT)), + ('{}_space_edit'.format(api_v_slug), '^spaces/(?P{})/edit/$'.format(UUID_PATT)), + ('{}_spaces'.format(api_v_slug), '^spaces/$'), + ('{}_spaces_new'.format(api_v_slug), '^spaces/new/$'), + ('{}_schema'.format(api_v_slug), r'^$'), + ('{}_yaml'.format(api_v_slug), r'^yaml/$'), + ('{}_doc'.format(api_v_slug), r'^doc/$'), + ('{}_client'.format(api_v_slug), r'^client/$'), + ) + for expected in expecteds: + assert expected in urlpatterns_names_regexes + + # Make assertions about ``api.routes`` + assert api.routes[r'^locations/$'] == { + 'http_methods': {'GET': (Locations, 'index'), + 'POST': (Locations, 'create'), + 'SEARCH': (Locations, 'search')}, + 'route_name': '{}_locations'.format(api_v_slug)} + assert api.routes[ + r'^locations/(?P{})/$'.format(UUID_PATT)] == { + 'http_methods': {'DELETE': (Locations, 'delete'), + 'GET': (Locations, 'show'), + 'PUT': (Locations, 'update')}, + 'route_name': '{}_location'.format(api_v_slug)} + assert api.routes[ + r'^locations/(?P{})/edit/$'.format(UUID_PATT)] == { + 'http_methods': {'GET': (Locations, 'edit')}, + 'route_name': '{}_location_edit'.format(api_v_slug)} + assert api.routes['^locations/new/$'] == { + 'http_methods': {'GET': (Locations, 'new')}, + 'route_name': '{}_locations_new'.format(api_v_slug)} + assert api.routes['^locations/new_search/$'] == { + 'http_methods': {'GET': (Locations, 'new_search')}, + 'route_name': '{}_locations_new_search'.format(api_v_slug)} + assert api.routes['^locations/search/$'] == { + 'http_methods': {'POST': (Locations, 'search')}, + 'route_name': '{}_locations_search'.format(api_v_slug)} + assert '^spaces/search/$' not in api.routes + assert '^pipelines/search/$' in api.routes + assert '^packages/search/$' in api.routes + assert r'^packages/(?P{})/$'.format(ID_PATT) in api.routes + assert r'^packages/(?P{})/$'.format( + UUID_PATT) not in api.routes + + +def test_regex_builders(): + """Test that the regex-building functions can build the correct regexes + given resource names as input. + """ + # Collection-targeting regex builder + assert r'^frogs/$' == get_collection_targeting_regex('frogs') + assert r'^frogs/legs/$' == get_collection_targeting_regex( + 'frogs', modifiers=['legs']) + assert r'^frogs/legs/toes/$' == get_collection_targeting_regex( + 'frogs', modifiers=['legs', 'toes']) + assert r'^frogs/l/e/g/s/$' == get_collection_targeting_regex( + 'frogs', modifiers='legs') + with pytest.raises(TypeError): + get_collection_targeting_regex('frogs', modifiers=1) + + # Member-targeting regex builder + assert r'^frogs/(?P{})/$'.format( + UUID_PATT) == get_member_targeting_regex( + 'frogs', UUID_PATT) + assert r'^frogs/(?P{})/legs/$'.format( + ID_PATT) == get_member_targeting_regex( + 'frogs', ID_PATT, modifiers=['legs']) + assert r'^frogs/(?P{})/legs/toes/$'.format( + UUID_PATT) == get_member_targeting_regex( + 'frogs', UUID_PATT, modifiers=['legs', 'toes']) + assert r'^frogs/(?P{})/l/e/g/s/$'.format( + UUID_PATT) == get_member_targeting_regex( + 'frogs', UUID_PATT, modifiers='legs') + with pytest.raises(TypeError): + get_member_targeting_regex('frogs', UUID_PATT, modifiers=1) + + +def test_standard_routes(): + """Test that standard REST ``Route()``s are yielded from the aptly-named + func. + """ + api = API(api_version='0.1.0', service_name='Elements') + + class Skies(Resources): + pass + + cr, dr, er, ir, nr, sr, ur = api.yield_standard_routes('sky', Skies) + + # POST /skies/ + assert cr.regex == '^skies/$' + assert cr.name == 'v0_1_skies' + assert cr.http_method == 'POST' + assert cr.resource_cls == Skies + assert cr.method_name == 'create' + + # DELETE /skies// + assert dr.regex == r'^skies/(?P{})/$'.format(UUID_PATT) + assert dr.name == 'v0_1_sky' + assert dr.http_method == 'DELETE' + assert dr.resource_cls == Skies + assert dr.method_name == 'delete' + + # GET /skies//edit/ + assert er.regex == r'^skies/(?P{})/edit/$'.format(UUID_PATT) + assert er.name == 'v0_1_sky_edit' + assert er.http_method == 'GET' + assert er.resource_cls == Skies + assert er.method_name == 'edit' + + # GET /skies/ + assert ir.regex == '^skies/$' + assert ir.name == 'v0_1_skies' + assert ir.http_method == 'GET' + assert ir.resource_cls == Skies + assert ir.method_name == 'index' + + # GET /skies/new + assert nr.regex == '^skies/new/$' + assert nr.name == 'v0_1_skies_new' + assert nr.http_method == 'GET' + assert nr.resource_cls == Skies + assert nr.method_name == 'new' + + # GET /skies// + assert sr.regex == r'^skies/(?P{})/$'.format(UUID_PATT) + assert sr.name == 'v0_1_sky' + assert sr.http_method == 'GET' + assert sr.resource_cls == Skies + assert sr.method_name == 'show' + + # PUT /skies// + assert ur.regex == r'^skies/(?P{})/$'.format(UUID_PATT) + assert ur.name == 'v0_1_sky' + assert ur.http_method == 'PUT' + assert ur.resource_cls == Skies + assert ur.method_name == 'update' + + +def test_search_routes(): + """Test that search-related ``Route()``s are yielded from the aptly-named + func. + """ + api = API(api_version='0.1.0', service_name='Animals') + + class Octopodes(Resources): + pass + + r1, r2, r3 = api.yield_search_routes('octopus', Octopodes) + + # SEARCH /octopodes/ + assert r1.regex == '^octopodes/$' + assert r1.name == 'v0_1_octopodes' + assert r1.http_method == 'SEARCH' + assert r1.resource_cls == Octopodes + assert r1.method_name == 'search' + + # POST /octopodes/search/ + assert r2.regex == '^octopodes/search/$' + assert r2.name == 'v0_1_octopodes_search' + assert r2.http_method == 'POST' + assert r2.resource_cls == Octopodes + assert r2.method_name == 'search' + + # GET /octopodes/new_search/ + assert r3.regex == '^octopodes/new_search/$' + assert r3.name == 'v0_1_octopodes_new_search' + assert r3.http_method == 'GET' + assert r3.resource_cls == Octopodes + assert r3.method_name == 'new_search' diff --git a/storage_service/locations/tests/test_z_api_beta_client.py b/storage_service/locations/tests/test_z_api_beta_client.py new file mode 100644 index 000000000..8a2cbfa73 --- /dev/null +++ b/storage_service/locations/tests/test_z_api_beta_client.py @@ -0,0 +1,293 @@ +import imp +import json +import os +import pydoc + +from django.contrib.auth.models import User +from django.contrib.staticfiles.testing import LiveServerTestCase +from tastypie.models import ApiKey + +from locations import models +from locations.api.beta import api + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +FIXTURES_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', 'fixtures', '')) + +SERVER_PATH = api.get_dflt_server_path() +API_PATH_PREFIX = '/api/beta/' + + +class TestBetaAPIClient(LiveServerTestCase): + """Test the Beta API using the Python client module that is dynamically + generated and served by the Beta API. + """ + + fixtures = ['base.json', 'pipelines.json', 'package.json', 'files.json'] + + def setUp(self): + self.username = 'test' + user = User.objects.get(username=self.username) + self.api_key = str(ApiKey.objects.get(user=user)).split()[0] + self.client.defaults['HTTP_AUTHORIZATION'] = 'ApiKey test:{}'.format( + self.api_key) + + # Get the Python client code from the Storage Service API and load it + # into a module dynamically + response = self.client.get('{}client/'.format(API_PATH_PREFIX), + content_type='application/json') + client_dict = json.loads(response.content) + client_python = client_dict['client'] + client_module = imp.new_module('client') + exec client_python in client_module.__dict__ + self.client_module = client_module + + @property + def url(self): + return self.live_server_url.rstrip('/') + '/' + + def _test_space_client(self): + client = self.client_module.ArchivematicaStorageServiceApiClient( + self.username, self.api_key, self.url) + + # Get all spaces + ret = client.space.get_many() + assert ret['paginator']['count'] == 1 + space = ret['items'][0] + assert space['resource_uri'] == ( + '/api/beta/spaces/7d20c992-bc92-4f92-a794-7161ff2cc08b/') + assert space['verified'] is False + assert space['id'] == 1 + assert space['staging_path'] == '' + assert space['uuid'] == '7d20c992-bc92-4f92-a794-7161ff2cc08b' + assert space['access_protocol'] == u'FS' + assert space['used'] == 0 + assert space['last_verified'] is None + assert space['size'] is None + assert space['path'] == '/' + + space_docs = get_docs(client.space) + assert 'class SpaceClient(BaseClient)' in space_docs + assert 'Access to the space resource' in space_docs + # Get a single space + ret = client.space.get(space['uuid']) + assert ret == space + + def _test_client_docs(self): + client_cls = self.client_module.ArchivematicaStorageServiceApiClient + client = client_cls(self.username, self.api_key, self.url) + client_docs = get_docs(client_cls) + assert ('class ArchivematicaStorageServiceApiClient(BaseClient)' in + client_docs) + assert ('Archivematica Storage Service API version beta client' in + client_docs) + assert ('An API for the Archivematica Storage Service.' in + client_docs) + assert ('The following instance attributes allow interaction with the' + ' resources that the' in client_docs) + + space_docs = get_docs(client.space) + assert 'Access to the space resource' in space_docs + assert ('create(self, access_protocol, staging_path, path=None,' + ' size=None)' in space_docs) + assert ('get_many(self, items_per_page=10, order_by_attribute=None,' + ' order_by_direction=None, order_by_subattribute=None, page=1)' + in space_docs) + assert 'search(self, query, paginator=None)' in space_docs + assert 'update(self, pk, staging_path, path=None, size=None)' in space_docs + + package_docs = get_docs(client.package) + assert 'get_many(self, ' in package_docs + assert 'get(self, ' in package_docs + assert 'search(self, ' in package_docs + # Package resource is read-only so client has no mutating methods: + assert 'update(self, ' not in package_docs + assert 'create(self, ' not in package_docs + assert 'delete(self, ' not in package_docs + + def _test_arkivum_client(self): + """Test creating, updating and deleting an Arkivum Space using the API + client. + """ + client = self.client_module.ArchivematicaStorageServiceApiClient( + self.username, self.api_key, self.url) + + # Get all spaces (hint: there are none) + ret = client.arkivum_space.get_many() + assert ret['paginator']['count'] == 0 + assert ret['items'] == [] + + # Create a new space + new_space = client.space.create( + 'FS', '/var/archivematica/storage_service', path='/') + assert new_space['access_protocol'] == 'FS' + assert new_space['path'] == '/' + new_space_uri = new_space['resource_uri'] + + # Get the client docs for creating a new Arkivum space + arkivum_space_create_docs = get_docs(client.arkivum_space.create) + assert ('create(self, host, space, remote_name=None, remote_user=None)' + in arkivum_space_create_docs) + assert ('host (str or unicode): Hostname of the Arkivum web instance.' + in arkivum_space_create_docs) + assert ('space (str or unicode; URI of a space resource)' + in arkivum_space_create_docs) + assert ('remote_name (str or unicode): Optional: Name or IP of the' + ' remote machine' in arkivum_space_create_docs) + assert ('remote_user (str or unicode): Optional: Username on the remote' + ' machine' in arkivum_space_create_docs) + + # Create a new Arkivum space + new_arkivum_space = client.arkivum_space.create( + host='arkivum.example.com:8443', + space=new_space_uri) + assert new_arkivum_space['host'] == 'arkivum.example.com:8443' + assert new_arkivum_space['space'] == new_space_uri + # Note even though we passed None/null to the server, we get back the + # empty string. This is maybe undesirable and is mixed up in Django's + # behaviour related to ``null`` and ``blank`` on model fields. + assert new_arkivum_space['remote_user'] == '' + assert new_arkivum_space['remote_name'] == '' + + # Get the client docs for updating an Arkivum space. + arkivum_space_update_docs = get_docs(client.arkivum_space.update) + assert ('update(self, pk, host, space, remote_name=None,' + ' remote_user=None)' in arkivum_space_update_docs) + assert ('pk (int; database id): The primary key of the' + ' arkivum_space' in arkivum_space_update_docs) + assert ('host (str or unicode): Hostname of the Arkivum web instance' + in arkivum_space_update_docs) + assert ('space (str or unicode; URI of a space resource)' + in arkivum_space_update_docs) + assert ('remote_name (str or unicode): Optional: Name or IP of the' + ' remote machine' in arkivum_space_update_docs) + assert ('remote_user (str or unicode): Optional: Username on the remote' + ' machine' in arkivum_space_update_docs) + + # Update the Arkivum space. + updated_arkivum_space = client.arkivum_space.update( + pk=new_arkivum_space['id'], + host='arkivum.example.org:8889', + space=new_space_uri, + remote_user='jimmyjames') + assert updated_arkivum_space['resource_uri'].startswith( + '{}arkivumspaces/'.format(API_PATH_PREFIX)) + assert updated_arkivum_space['host'] == 'arkivum.example.org:8889' + assert updated_arkivum_space['space'] == new_space_uri + assert updated_arkivum_space['remote_user'] == 'jimmyjames' + assert updated_arkivum_space['remote_name'] == '' + + # Get the clientn docs for deleting an Arkivum space. + arkivum_space_delete_docs = get_docs(client.arkivum_space.delete) + assert 'delete(self, pk)' in arkivum_space_delete_docs + assert 'Delete an existing arkivum_space' in arkivum_space_delete_docs + assert ('pk (int; database id): The primary key of the arkivum_space' + in arkivum_space_delete_docs) + + # Delete the Arkivum space using the client. + arkivum_space_orm_before = models.Arkivum.objects.filter( + pk=new_arkivum_space['id']).first() + assert arkivum_space_orm_before + deleted_arkivum_space = client.arkivum_space.delete( + pk=new_arkivum_space['id']) + assert deleted_arkivum_space == updated_arkivum_space + arkivum_space_orm_after = models.Arkivum.objects.filter( + pk=new_arkivum_space['id']).first() + assert not arkivum_space_orm_after + + def _test_other_client_subclasses(self): + client = self.client_module.ArchivematicaStorageServiceApiClient( + self.username, self.api_key, self.url) + + # Event resource + event_docs = get_docs(client.event) + assert ('Stores requests to modify packages that need admin approval.' + in event_docs) + assert 'get_many(' in event_docs + assert 'create(' not in event_docs # read-only + + # Callback resource + callback_docs = get_docs(client.callback) + assert ('Allows REST callbacks to be associated with specific Storage' + ' Service events.' in callback_docs) + assert 'get_many(' in callback_docs + assert 'create(' not in callback_docs # read-only + + # Fixity_log resource + fixity_log_docs = get_docs(client.fixity_log) + assert ('Stores fixity check success/failure and error details' in + fixity_log_docs) + assert 'get(' in fixity_log_docs + assert 'get_many(' in fixity_log_docs + assert 'search(' in fixity_log_docs + assert 'create(' not in fixity_log_docs # read-only + + def _test_location_client(self): + """Test the location client, in particular its browse custom endpoint. + """ + client = self.client_module.ArchivematicaStorageServiceApiClient( + self.username, self.api_key, self.url) + + location_docs = get_docs(client.location) + assert 'browse(self, pk, path=None)' in location_docs + assert 'Browse a location' in location_docs + assert ('Browse a location at the root (default) or at a supplied' + ' path.' in location_docs) + assert ('pk (str or unicode; UUID of a Location resource): UUID of the' + ' location to' in location_docs) + assert ('path (str or unicode): Path to browse within the Location' + ' (optional).' in location_docs) + assert ('dict: with key(s): "entries", "directories", "properties", if' + ' request to' in location_docs) + assert 'browse a location was successful.' in location_docs + + # Browse an AIP Storage location and assert that browsing it with the + # beta client returns the same results as browsing via the V2 API. + aip_store_loc = client.location.search( + query={'filter': ['purpose', '=', 'AS']})['items'][0] + beta_browse_ret = client.location.browse(aip_store_loc['uuid']) + v2_browse_ret = self.client.get( + '/api/v2/location/{}/browse/'.format(aip_store_loc['uuid']), + content_type='application/json') + v2_browse_ret = json.loads(v2_browse_ret.content) + assert beta_browse_ret['entries'] == v2_browse_ret['entries'] + assert beta_browse_ret['directories'] == v2_browse_ret['directories'] + assert sorted(beta_browse_ret['properties'].keys()) == sorted( + v2_browse_ret['properties'].keys()) + + def _test_user_client(self): + client = self.client_module.ArchivematicaStorageServiceApiClient( + self.username, self.api_key, self.url) + + user_docs = get_docs(client.user) + print(user_docs) + assert ('create(self, password, username, email=None, first_name=None,' + ' groups=None, is_active=None, is_staff=None,' + ' is_superuser=None, last_name=None, user_permissions=None)' + in user_docs) + + users = client.user.get_many() + assert users['items'][0]['username'] == 'test' + + created_user = client.user.create( + password='monkey123', + username='monkey', + email='monkey@gmail.com', + first_name='Mon', + last_name='Key') + assert created_user['resource_uri'].startswith( + '{}users/'.format(API_PATH_PREFIX)) + assert created_user['username'] == 'monkey' + assert 'password' not in created_user['username'] + + def test_client(self): + self._test_client_docs() + self._test_space_client() + self._test_arkivum_client() + self._test_other_client_subclasses() + self._test_location_client() + self._test_user_client() + + +def get_docs(thing): + """Return a string representation of the output of ``help(thing)``.""" + return pydoc.plain(pydoc.render_doc(thing)) diff --git a/storage_service/static/css/swagger-ui.css b/storage_service/static/css/swagger-ui.css new file mode 100644 index 000000000..ae22f0edc --- /dev/null +++ b/storage_service/static/css/swagger-ui.css @@ -0,0 +1,3 @@ +.swagger-ui{ + /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{background-color:transparent;-webkit-text-decoration-skip:objects}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) repeat 0 0}.swagger-ui .debug-grid-8-solid{background:#fff url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) repeat 0 0}.swagger-ui .debug-grid-16-solid{background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) repeat 0 0}.swagger-ui .border-box,.swagger-ui a,.swagger-ui article,.swagger-ui body,.swagger-ui code,.swagger-ui dd,.swagger-ui div,.swagger-ui dl,.swagger-ui dt,.swagger-ui fieldset,.swagger-ui footer,.swagger-ui form,.swagger-ui h1,.swagger-ui h2,.swagger-ui h3,.swagger-ui h4,.swagger-ui h5,.swagger-ui h6,.swagger-ui header,.swagger-ui html,.swagger-ui input[type=email],.swagger-ui input[type=number],.swagger-ui input[type=password],.swagger-ui input[type=tel],.swagger-ui input[type=text],.swagger-ui input[type=url],.swagger-ui legend,.swagger-ui li,.swagger-ui main,.swagger-ui ol,.swagger-ui p,.swagger-ui pre,.swagger-ui section,.swagger-ui table,.swagger-ui td,.swagger-ui textarea,.swagger-ui th,.swagger-ui tr,.swagger-ui ul{-webkit-box-sizing:border-box;box-sizing:border-box}.swagger-ui .aspect-ratio{height:0;position:relative}.swagger-ui .aspect-ratio--16x9{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1{padding-bottom:100%}.swagger-ui .aspect-ratio--object{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}@media screen and (min-width:30em){.swagger-ui .aspect-ratio-ns{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-ns{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-ns{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-ns{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-ns{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-ns{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-ns{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-ns{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-ns{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-ns{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-ns{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-ns{padding-bottom:100%}.swagger-ui .aspect-ratio--object-ns{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .aspect-ratio-m{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-m{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-m{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-m{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-m{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-m{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-m{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-m{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-m{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-m{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-m{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-m{padding-bottom:100%}.swagger-ui .aspect-ratio--object-m{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}}@media screen and (min-width:60em){.swagger-ui .aspect-ratio-l{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-l{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-l{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-l{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-l{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-l{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-l{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-l{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-l{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-l{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-l{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-l{padding-bottom:100%}.swagger-ui .aspect-ratio--object-l{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}}.swagger-ui img{max-width:100%}.swagger-ui .cover{background-size:cover!important}.swagger-ui .contain{background-size:contain!important}@media screen and (min-width:30em){.swagger-ui .cover-ns{background-size:cover!important}.swagger-ui .contain-ns{background-size:contain!important}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .cover-m{background-size:cover!important}.swagger-ui .contain-m{background-size:contain!important}}@media screen and (min-width:60em){.swagger-ui .cover-l{background-size:cover!important}.swagger-ui .contain-l{background-size:contain!important}}.swagger-ui .bg-center{background-repeat:no-repeat;background-position:50%}.swagger-ui .bg-top{background-repeat:no-repeat;background-position:top}.swagger-ui .bg-right{background-repeat:no-repeat;background-position:100%}.swagger-ui .bg-bottom{background-repeat:no-repeat;background-position:bottom}.swagger-ui .bg-left{background-repeat:no-repeat;background-position:0}@media screen and (min-width:30em){.swagger-ui .bg-center-ns{background-repeat:no-repeat;background-position:50%}.swagger-ui .bg-top-ns{background-repeat:no-repeat;background-position:top}.swagger-ui .bg-right-ns{background-repeat:no-repeat;background-position:100%}.swagger-ui .bg-bottom-ns{background-repeat:no-repeat;background-position:bottom}.swagger-ui .bg-left-ns{background-repeat:no-repeat;background-position:0}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .bg-center-m{background-repeat:no-repeat;background-position:50%}.swagger-ui .bg-top-m{background-repeat:no-repeat;background-position:top}.swagger-ui .bg-right-m{background-repeat:no-repeat;background-position:100%}.swagger-ui .bg-bottom-m{background-repeat:no-repeat;background-position:bottom}.swagger-ui .bg-left-m{background-repeat:no-repeat;background-position:0}}@media screen and (min-width:60em){.swagger-ui .bg-center-l{background-repeat:no-repeat;background-position:50%}.swagger-ui .bg-top-l{background-repeat:no-repeat;background-position:top}.swagger-ui .bg-right-l{background-repeat:no-repeat;background-position:100%}.swagger-ui .bg-bottom-l{background-repeat:no-repeat;background-position:bottom}.swagger-ui .bg-left-l{background-repeat:no-repeat;background-position:0}}.swagger-ui .outline{outline:1px solid}.swagger-ui .outline-transparent{outline:1px solid transparent}.swagger-ui .outline-0{outline:0}@media screen and (min-width:30em){.swagger-ui .outline-ns{outline:1px solid}.swagger-ui .outline-transparent-ns{outline:1px solid transparent}.swagger-ui .outline-0-ns{outline:0}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .outline-m{outline:1px solid}.swagger-ui .outline-transparent-m{outline:1px solid transparent}.swagger-ui .outline-0-m{outline:0}}@media screen and (min-width:60em){.swagger-ui .outline-l{outline:1px solid}.swagger-ui .outline-transparent-l{outline:1px solid transparent}.swagger-ui .outline-0-l{outline:0}}.swagger-ui .ba{border-style:solid;border-width:1px}.swagger-ui .bt{border-top-style:solid;border-top-width:1px}.swagger-ui .br{border-right-style:solid;border-right-width:1px}.swagger-ui .bb{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl{border-left-style:solid;border-left-width:1px}.swagger-ui .bn{border-style:none;border-width:0}@media screen and (min-width:30em){.swagger-ui .ba-ns{border-style:solid;border-width:1px}.swagger-ui .bt-ns{border-top-style:solid;border-top-width:1px}.swagger-ui .br-ns{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-ns{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-ns{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-ns{border-style:none;border-width:0}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .ba-m{border-style:solid;border-width:1px}.swagger-ui .bt-m{border-top-style:solid;border-top-width:1px}.swagger-ui .br-m{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-m{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-m{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-m{border-style:none;border-width:0}}@media screen and (min-width:60em){.swagger-ui .ba-l{border-style:solid;border-width:1px}.swagger-ui .bt-l{border-top-style:solid;border-top-width:1px}.swagger-ui .br-l{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-l{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-l{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-l{border-style:none;border-width:0}}.swagger-ui .b--black{border-color:#000}.swagger-ui .b--near-black{border-color:#111}.swagger-ui .b--dark-gray{border-color:#333}.swagger-ui .b--mid-gray{border-color:#555}.swagger-ui .b--gray{border-color:#777}.swagger-ui .b--silver{border-color:#999}.swagger-ui .b--light-silver{border-color:#aaa}.swagger-ui .b--moon-gray{border-color:#ccc}.swagger-ui .b--light-gray{border-color:#eee}.swagger-ui .b--near-white{border-color:#f4f4f4}.swagger-ui .b--white{border-color:#fff}.swagger-ui .b--white-90{border-color:hsla(0,0%,100%,.9)}.swagger-ui .b--white-80{border-color:hsla(0,0%,100%,.8)}.swagger-ui .b--white-70{border-color:hsla(0,0%,100%,.7)}.swagger-ui .b--white-60{border-color:hsla(0,0%,100%,.6)}.swagger-ui .b--white-50{border-color:hsla(0,0%,100%,.5)}.swagger-ui .b--white-40{border-color:hsla(0,0%,100%,.4)}.swagger-ui .b--white-30{border-color:hsla(0,0%,100%,.3)}.swagger-ui .b--white-20{border-color:hsla(0,0%,100%,.2)}.swagger-ui .b--white-10{border-color:hsla(0,0%,100%,.1)}.swagger-ui .b--white-05{border-color:hsla(0,0%,100%,.05)}.swagger-ui .b--white-025{border-color:hsla(0,0%,100%,.025)}.swagger-ui .b--white-0125{border-color:hsla(0,0%,100%,.0125)}.swagger-ui .b--black-90{border-color:rgba(0,0,0,.9)}.swagger-ui .b--black-80{border-color:rgba(0,0,0,.8)}.swagger-ui .b--black-70{border-color:rgba(0,0,0,.7)}.swagger-ui .b--black-60{border-color:rgba(0,0,0,.6)}.swagger-ui .b--black-50{border-color:rgba(0,0,0,.5)}.swagger-ui .b--black-40{border-color:rgba(0,0,0,.4)}.swagger-ui .b--black-30{border-color:rgba(0,0,0,.3)}.swagger-ui .b--black-20{border-color:rgba(0,0,0,.2)}.swagger-ui .b--black-10{border-color:rgba(0,0,0,.1)}.swagger-ui .b--black-05{border-color:rgba(0,0,0,.05)}.swagger-ui .b--black-025{border-color:rgba(0,0,0,.025)}.swagger-ui .b--black-0125{border-color:rgba(0,0,0,.0125)}.swagger-ui .b--dark-red{border-color:#e7040f}.swagger-ui .b--red{border-color:#ff4136}.swagger-ui .b--light-red{border-color:#ff725c}.swagger-ui .b--orange{border-color:#ff6300}.swagger-ui .b--gold{border-color:#ffb700}.swagger-ui .b--yellow{border-color:gold}.swagger-ui .b--light-yellow{border-color:#fbf1a9}.swagger-ui .b--purple{border-color:#5e2ca5}.swagger-ui .b--light-purple{border-color:#a463f2}.swagger-ui .b--dark-pink{border-color:#d5008f}.swagger-ui .b--hot-pink{border-color:#ff41b4}.swagger-ui .b--pink{border-color:#ff80cc}.swagger-ui .b--light-pink{border-color:#ffa3d7}.swagger-ui .b--dark-green{border-color:#137752}.swagger-ui .b--green{border-color:#19a974}.swagger-ui .b--light-green{border-color:#9eebcf}.swagger-ui .b--navy{border-color:#001b44}.swagger-ui .b--dark-blue{border-color:#00449e}.swagger-ui .b--blue{border-color:#357edd}.swagger-ui .b--light-blue{border-color:#96ccff}.swagger-ui .b--lightest-blue{border-color:#cdecff}.swagger-ui .b--washed-blue{border-color:#f6fffe}.swagger-ui .b--washed-green{border-color:#e8fdf5}.swagger-ui .b--washed-yellow{border-color:#fffceb}.swagger-ui .b--washed-red{border-color:#ffdfdf}.swagger-ui .b--transparent{border-color:transparent}.swagger-ui .b--inherit{border-color:inherit}.swagger-ui .br0{border-radius:0}.swagger-ui .br1{border-radius:.125rem}.swagger-ui .br2{border-radius:.25rem}.swagger-ui .br3{border-radius:.5rem}.swagger-ui .br4{border-radius:1rem}.swagger-ui .br-100{border-radius:100%}.swagger-ui .br-pill{border-radius:9999px}.swagger-ui .br--bottom{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right{border-top-left-radius:0;border-bottom-left-radius:0}.swagger-ui .br--left{border-top-right-radius:0;border-bottom-right-radius:0}@media screen and (min-width:30em){.swagger-ui .br0-ns{border-radius:0}.swagger-ui .br1-ns{border-radius:.125rem}.swagger-ui .br2-ns{border-radius:.25rem}.swagger-ui .br3-ns{border-radius:.5rem}.swagger-ui .br4-ns{border-radius:1rem}.swagger-ui .br-100-ns{border-radius:100%}.swagger-ui .br-pill-ns{border-radius:9999px}.swagger-ui .br--bottom-ns{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-ns{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-ns{border-top-left-radius:0;border-bottom-left-radius:0}.swagger-ui .br--left-ns{border-top-right-radius:0;border-bottom-right-radius:0}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .br0-m{border-radius:0}.swagger-ui .br1-m{border-radius:.125rem}.swagger-ui .br2-m{border-radius:.25rem}.swagger-ui .br3-m{border-radius:.5rem}.swagger-ui .br4-m{border-radius:1rem}.swagger-ui .br-100-m{border-radius:100%}.swagger-ui .br-pill-m{border-radius:9999px}.swagger-ui .br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-m{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-m{border-top-left-radius:0;border-bottom-left-radius:0}.swagger-ui .br--left-m{border-top-right-radius:0;border-bottom-right-radius:0}}@media screen and (min-width:60em){.swagger-ui .br0-l{border-radius:0}.swagger-ui .br1-l{border-radius:.125rem}.swagger-ui .br2-l{border-radius:.25rem}.swagger-ui .br3-l{border-radius:.5rem}.swagger-ui .br4-l{border-radius:1rem}.swagger-ui .br-100-l{border-radius:100%}.swagger-ui .br-pill-l{border-radius:9999px}.swagger-ui .br--bottom-l{border-radius-top-left:0;border-radius-top-right:0}.swagger-ui .br--top-l{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-l{border-top-left-radius:0;border-bottom-left-radius:0}.swagger-ui .br--left-l{border-top-right-radius:0;border-bottom-right-radius:0}}.swagger-ui .b--dotted{border-style:dotted}.swagger-ui .b--dashed{border-style:dashed}.swagger-ui .b--solid{border-style:solid}.swagger-ui .b--none{border-style:none}@media screen and (min-width:30em){.swagger-ui .b--dotted-ns{border-style:dotted}.swagger-ui .b--dashed-ns{border-style:dashed}.swagger-ui .b--solid-ns{border-style:solid}.swagger-ui .b--none-ns{border-style:none}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .b--dotted-m{border-style:dotted}.swagger-ui .b--dashed-m{border-style:dashed}.swagger-ui .b--solid-m{border-style:solid}.swagger-ui .b--none-m{border-style:none}}@media screen and (min-width:60em){.swagger-ui .b--dotted-l{border-style:dotted}.swagger-ui .b--dashed-l{border-style:dashed}.swagger-ui .b--solid-l{border-style:solid}.swagger-ui .b--none-l{border-style:none}}.swagger-ui .bw0{border-width:0}.swagger-ui .bw1{border-width:.125rem}.swagger-ui .bw2{border-width:.25rem}.swagger-ui .bw3{border-width:.5rem}.swagger-ui .bw4{border-width:1rem}.swagger-ui .bw5{border-width:2rem}.swagger-ui .bt-0{border-top-width:0}.swagger-ui .br-0{border-right-width:0}.swagger-ui .bb-0{border-bottom-width:0}.swagger-ui .bl-0{border-left-width:0}@media screen and (min-width:30em){.swagger-ui .bw0-ns{border-width:0}.swagger-ui .bw1-ns{border-width:.125rem}.swagger-ui .bw2-ns{border-width:.25rem}.swagger-ui .bw3-ns{border-width:.5rem}.swagger-ui .bw4-ns{border-width:1rem}.swagger-ui .bw5-ns{border-width:2rem}.swagger-ui .bt-0-ns{border-top-width:0}.swagger-ui .br-0-ns{border-right-width:0}.swagger-ui .bb-0-ns{border-bottom-width:0}.swagger-ui .bl-0-ns{border-left-width:0}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .bw0-m{border-width:0}.swagger-ui .bw1-m{border-width:.125rem}.swagger-ui .bw2-m{border-width:.25rem}.swagger-ui .bw3-m{border-width:.5rem}.swagger-ui .bw4-m{border-width:1rem}.swagger-ui .bw5-m{border-width:2rem}.swagger-ui .bt-0-m{border-top-width:0}.swagger-ui .br-0-m{border-right-width:0}.swagger-ui .bb-0-m{border-bottom-width:0}.swagger-ui .bl-0-m{border-left-width:0}}@media screen and (min-width:60em){.swagger-ui .bw0-l{border-width:0}.swagger-ui .bw1-l{border-width:.125rem}.swagger-ui .bw2-l{border-width:.25rem}.swagger-ui .bw3-l{border-width:.5rem}.swagger-ui .bw4-l{border-width:1rem}.swagger-ui .bw5-l{border-width:2rem}.swagger-ui .bt-0-l{border-top-width:0}.swagger-ui .br-0-l{border-right-width:0}.swagger-ui .bb-0-l{border-bottom-width:0}.swagger-ui .bl-0-l{border-left-width:0}}.swagger-ui .shadow-1{-webkit-box-shadow:0 0 4px 2px rgba(0,0,0,.2);box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2{-webkit-box-shadow:0 0 8px 2px rgba(0,0,0,.2);box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3{-webkit-box-shadow:2px 2px 4px 2px rgba(0,0,0,.2);box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4{-webkit-box-shadow:2px 2px 8px 0 rgba(0,0,0,.2);box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5{-webkit-box-shadow:4px 4px 8px 0 rgba(0,0,0,.2);box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}@media screen and (min-width:30em){.swagger-ui .shadow-1-ns{-webkit-box-shadow:0 0 4px 2px rgba(0,0,0,.2);box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-ns{-webkit-box-shadow:0 0 8px 2px rgba(0,0,0,.2);box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-ns{-webkit-box-shadow:2px 2px 4px 2px rgba(0,0,0,.2);box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-ns{-webkit-box-shadow:2px 2px 8px 0 rgba(0,0,0,.2);box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-ns{-webkit-box-shadow:4px 4px 8px 0 rgba(0,0,0,.2);box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .shadow-1-m{-webkit-box-shadow:0 0 4px 2px rgba(0,0,0,.2);box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-m{-webkit-box-shadow:0 0 8px 2px rgba(0,0,0,.2);box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-m{-webkit-box-shadow:2px 2px 4px 2px rgba(0,0,0,.2);box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-m{-webkit-box-shadow:2px 2px 8px 0 rgba(0,0,0,.2);box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-m{-webkit-box-shadow:4px 4px 8px 0 rgba(0,0,0,.2);box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:60em){.swagger-ui .shadow-1-l{-webkit-box-shadow:0 0 4px 2px rgba(0,0,0,.2);box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-l{-webkit-box-shadow:0 0 8px 2px rgba(0,0,0,.2);box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-l{-webkit-box-shadow:2px 2px 4px 2px rgba(0,0,0,.2);box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-l{-webkit-box-shadow:2px 2px 8px 0 rgba(0,0,0,.2);box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-l{-webkit-box-shadow:4px 4px 8px 0 rgba(0,0,0,.2);box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}.swagger-ui .pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.swagger-ui .top-0{top:0}.swagger-ui .right-0{right:0}.swagger-ui .bottom-0{bottom:0}.swagger-ui .left-0{left:0}.swagger-ui .top-1{top:1rem}.swagger-ui .right-1{right:1rem}.swagger-ui .bottom-1{bottom:1rem}.swagger-ui .left-1{left:1rem}.swagger-ui .top-2{top:2rem}.swagger-ui .right-2{right:2rem}.swagger-ui .bottom-2{bottom:2rem}.swagger-ui .left-2{left:2rem}.swagger-ui .top--1{top:-1rem}.swagger-ui .right--1{right:-1rem}.swagger-ui .bottom--1{bottom:-1rem}.swagger-ui .left--1{left:-1rem}.swagger-ui .top--2{top:-2rem}.swagger-ui .right--2{right:-2rem}.swagger-ui .bottom--2{bottom:-2rem}.swagger-ui .left--2{left:-2rem}.swagger-ui .absolute--fill{top:0;right:0;bottom:0;left:0}@media screen and (min-width:30em){.swagger-ui .top-0-ns{top:0}.swagger-ui .left-0-ns{left:0}.swagger-ui .right-0-ns{right:0}.swagger-ui .bottom-0-ns{bottom:0}.swagger-ui .top-1-ns{top:1rem}.swagger-ui .left-1-ns{left:1rem}.swagger-ui .right-1-ns{right:1rem}.swagger-ui .bottom-1-ns{bottom:1rem}.swagger-ui .top-2-ns{top:2rem}.swagger-ui .left-2-ns{left:2rem}.swagger-ui .right-2-ns{right:2rem}.swagger-ui .bottom-2-ns{bottom:2rem}.swagger-ui .top--1-ns{top:-1rem}.swagger-ui .right--1-ns{right:-1rem}.swagger-ui .bottom--1-ns{bottom:-1rem}.swagger-ui .left--1-ns{left:-1rem}.swagger-ui .top--2-ns{top:-2rem}.swagger-ui .right--2-ns{right:-2rem}.swagger-ui .bottom--2-ns{bottom:-2rem}.swagger-ui .left--2-ns{left:-2rem}.swagger-ui .absolute--fill-ns{top:0;right:0;bottom:0;left:0}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .top-0-m{top:0}.swagger-ui .left-0-m{left:0}.swagger-ui .right-0-m{right:0}.swagger-ui .bottom-0-m{bottom:0}.swagger-ui .top-1-m{top:1rem}.swagger-ui .left-1-m{left:1rem}.swagger-ui .right-1-m{right:1rem}.swagger-ui .bottom-1-m{bottom:1rem}.swagger-ui .top-2-m{top:2rem}.swagger-ui .left-2-m{left:2rem}.swagger-ui .right-2-m{right:2rem}.swagger-ui .bottom-2-m{bottom:2rem}.swagger-ui .top--1-m{top:-1rem}.swagger-ui .right--1-m{right:-1rem}.swagger-ui .bottom--1-m{bottom:-1rem}.swagger-ui .left--1-m{left:-1rem}.swagger-ui .top--2-m{top:-2rem}.swagger-ui .right--2-m{right:-2rem}.swagger-ui .bottom--2-m{bottom:-2rem}.swagger-ui .left--2-m{left:-2rem}.swagger-ui .absolute--fill-m{top:0;right:0;bottom:0;left:0}}@media screen and (min-width:60em){.swagger-ui .top-0-l{top:0}.swagger-ui .left-0-l{left:0}.swagger-ui .right-0-l{right:0}.swagger-ui .bottom-0-l{bottom:0}.swagger-ui .top-1-l{top:1rem}.swagger-ui .left-1-l{left:1rem}.swagger-ui .right-1-l{right:1rem}.swagger-ui .bottom-1-l{bottom:1rem}.swagger-ui .top-2-l{top:2rem}.swagger-ui .left-2-l{left:2rem}.swagger-ui .right-2-l{right:2rem}.swagger-ui .bottom-2-l{bottom:2rem}.swagger-ui .top--1-l{top:-1rem}.swagger-ui .right--1-l{right:-1rem}.swagger-ui .bottom--1-l{bottom:-1rem}.swagger-ui .left--1-l{left:-1rem}.swagger-ui .top--2-l{top:-2rem}.swagger-ui .right--2-l{right:-2rem}.swagger-ui .bottom--2-l{bottom:-2rem}.swagger-ui .left--2-l{left:-2rem}.swagger-ui .absolute--fill-l{top:0;right:0;bottom:0;left:0}}.swagger-ui .cf:after,.swagger-ui .cf:before{content:" ";display:table}.swagger-ui .cf:after{clear:both}.swagger-ui .cf{*zoom:1}.swagger-ui .cl{clear:left}.swagger-ui .cr{clear:right}.swagger-ui .cb{clear:both}.swagger-ui .cn{clear:none}@media screen and (min-width:30em){.swagger-ui .cl-ns{clear:left}.swagger-ui .cr-ns{clear:right}.swagger-ui .cb-ns{clear:both}.swagger-ui .cn-ns{clear:none}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .cl-m{clear:left}.swagger-ui .cr-m{clear:right}.swagger-ui .cb-m{clear:both}.swagger-ui .cn-m{clear:none}}@media screen and (min-width:60em){.swagger-ui .cl-l{clear:left}.swagger-ui .cr-l{clear:right}.swagger-ui .cb-l{clear:both}.swagger-ui .cn-l{clear:none}}.swagger-ui .dn{display:none}.swagger-ui .di{display:inline}.swagger-ui .db{display:block}.swagger-ui .dib{display:inline-block}.swagger-ui .dit{display:inline-table}.swagger-ui .dt{display:table}.swagger-ui .dtc{display:table-cell}.swagger-ui .dt-row{display:table-row}.swagger-ui .dt-row-group{display:table-row-group}.swagger-ui .dt-column{display:table-column}.swagger-ui .dt-column-group{display:table-column-group}.swagger-ui .dt--fixed{table-layout:fixed;width:100%}@media screen and (min-width:30em){.swagger-ui .dn-ns{display:none}.swagger-ui .di-ns{display:inline}.swagger-ui .db-ns{display:block}.swagger-ui .dib-ns{display:inline-block}.swagger-ui .dit-ns{display:inline-table}.swagger-ui .dt-ns{display:table}.swagger-ui .dtc-ns{display:table-cell}.swagger-ui .dt-row-ns{display:table-row}.swagger-ui .dt-row-group-ns{display:table-row-group}.swagger-ui .dt-column-ns{display:table-column}.swagger-ui .dt-column-group-ns{display:table-column-group}.swagger-ui .dt--fixed-ns{table-layout:fixed;width:100%}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .dn-m{display:none}.swagger-ui .di-m{display:inline}.swagger-ui .db-m{display:block}.swagger-ui .dib-m{display:inline-block}.swagger-ui .dit-m{display:inline-table}.swagger-ui .dt-m{display:table}.swagger-ui .dtc-m{display:table-cell}.swagger-ui .dt-row-m{display:table-row}.swagger-ui .dt-row-group-m{display:table-row-group}.swagger-ui .dt-column-m{display:table-column}.swagger-ui .dt-column-group-m{display:table-column-group}.swagger-ui .dt--fixed-m{table-layout:fixed;width:100%}}@media screen and (min-width:60em){.swagger-ui .dn-l{display:none}.swagger-ui .di-l{display:inline}.swagger-ui .db-l{display:block}.swagger-ui .dib-l{display:inline-block}.swagger-ui .dit-l{display:inline-table}.swagger-ui .dt-l{display:table}.swagger-ui .dtc-l{display:table-cell}.swagger-ui .dt-row-l{display:table-row}.swagger-ui .dt-row-group-l{display:table-row-group}.swagger-ui .dt-column-l{display:table-column}.swagger-ui .dt-column-group-l{display:table-column-group}.swagger-ui .dt--fixed-l{table-layout:fixed;width:100%}}.swagger-ui .flex{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .inline-flex{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.swagger-ui .flex-auto{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;min-width:0;min-height:0}.swagger-ui .flex-none{-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .flex-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.swagger-ui .flex-wrap{-ms-flex-wrap:wrap;flex-wrap:wrap}.swagger-ui .flex-nowrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse{-ms-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.swagger-ui .flex-row-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.swagger-ui .items-start{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.swagger-ui .items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.swagger-ui .items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .items-baseline{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.swagger-ui .items-stretch{-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.swagger-ui .self-start{-ms-flex-item-align:start;align-self:flex-start}.swagger-ui .self-end{-ms-flex-item-align:end;align-self:flex-end}.swagger-ui .self-center{-ms-flex-item-align:center;align-self:center}.swagger-ui .self-baseline{-ms-flex-item-align:baseline;align-self:baseline}.swagger-ui .self-stretch{-ms-flex-item-align:stretch;align-self:stretch}.swagger-ui .justify-start{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.swagger-ui .justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .justify-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.swagger-ui .justify-around{-ms-flex-pack:distribute;justify-content:space-around}.swagger-ui .content-start{-ms-flex-line-pack:start;align-content:flex-start}.swagger-ui .content-end{-ms-flex-line-pack:end;align-content:flex-end}.swagger-ui .content-center{-ms-flex-line-pack:center;align-content:center}.swagger-ui .content-between{-ms-flex-line-pack:justify;align-content:space-between}.swagger-ui .content-around{-ms-flex-line-pack:distribute;align-content:space-around}.swagger-ui .content-stretch{-ms-flex-line-pack:stretch;align-content:stretch}.swagger-ui .order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.swagger-ui .order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.swagger-ui .order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.swagger-ui .order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.swagger-ui .order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.swagger-ui .order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.swagger-ui .order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.swagger-ui .order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.swagger-ui .order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.swagger-ui .order-last{-webkit-box-ordinal-group:100000;-ms-flex-order:99999;order:99999}.swagger-ui .flex-grow-0{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.swagger-ui .flex-grow-1{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.swagger-ui .flex-shrink-0{-ms-flex-negative:0;flex-shrink:0}.swagger-ui .flex-shrink-1{-ms-flex-negative:1;flex-shrink:1}@media screen and (min-width:30em){.swagger-ui .flex-ns{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .inline-flex-ns{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.swagger-ui .flex-auto-ns{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;min-width:0;min-height:0}.swagger-ui .flex-none-ns{-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .flex-column-ns{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .flex-row-ns{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.swagger-ui .flex-wrap-ns{-ms-flex-wrap:wrap;flex-wrap:wrap}.swagger-ui .flex-nowrap-ns{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-ns{-ms-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-ns{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.swagger-ui .flex-row-reverse-ns{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.swagger-ui .items-start-ns{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.swagger-ui .items-end-ns{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.swagger-ui .items-center-ns{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .items-baseline-ns{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.swagger-ui .items-stretch-ns{-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.swagger-ui .self-start-ns{-ms-flex-item-align:start;align-self:flex-start}.swagger-ui .self-end-ns{-ms-flex-item-align:end;align-self:flex-end}.swagger-ui .self-center-ns{-ms-flex-item-align:center;align-self:center}.swagger-ui .self-baseline-ns{-ms-flex-item-align:baseline;align-self:baseline}.swagger-ui .self-stretch-ns{-ms-flex-item-align:stretch;align-self:stretch}.swagger-ui .justify-start-ns{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.swagger-ui .justify-end-ns{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .justify-center-ns{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .justify-between-ns{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.swagger-ui .justify-around-ns{-ms-flex-pack:distribute;justify-content:space-around}.swagger-ui .content-start-ns{-ms-flex-line-pack:start;align-content:flex-start}.swagger-ui .content-end-ns{-ms-flex-line-pack:end;align-content:flex-end}.swagger-ui .content-center-ns{-ms-flex-line-pack:center;align-content:center}.swagger-ui .content-between-ns{-ms-flex-line-pack:justify;align-content:space-between}.swagger-ui .content-around-ns{-ms-flex-line-pack:distribute;align-content:space-around}.swagger-ui .content-stretch-ns{-ms-flex-line-pack:stretch;align-content:stretch}.swagger-ui .order-0-ns{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.swagger-ui .order-1-ns{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.swagger-ui .order-2-ns{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.swagger-ui .order-3-ns{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.swagger-ui .order-4-ns{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.swagger-ui .order-5-ns{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.swagger-ui .order-6-ns{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.swagger-ui .order-7-ns{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.swagger-ui .order-8-ns{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.swagger-ui .order-last-ns{-webkit-box-ordinal-group:100000;-ms-flex-order:99999;order:99999}.swagger-ui .flex-grow-0-ns{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.swagger-ui .flex-grow-1-ns{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.swagger-ui .flex-shrink-0-ns{-ms-flex-negative:0;flex-shrink:0}.swagger-ui .flex-shrink-1-ns{-ms-flex-negative:1;flex-shrink:1}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .flex-m{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .inline-flex-m{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.swagger-ui .flex-auto-m{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;min-width:0;min-height:0}.swagger-ui .flex-none-m{-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .flex-column-m{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .flex-row-m{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.swagger-ui .flex-wrap-m{-ms-flex-wrap:wrap;flex-wrap:wrap}.swagger-ui .flex-nowrap-m{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-m{-ms-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-m{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.swagger-ui .flex-row-reverse-m{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.swagger-ui .items-start-m{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.swagger-ui .items-end-m{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.swagger-ui .items-center-m{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .items-baseline-m{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.swagger-ui .items-stretch-m{-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.swagger-ui .self-start-m{-ms-flex-item-align:start;align-self:flex-start}.swagger-ui .self-end-m{-ms-flex-item-align:end;align-self:flex-end}.swagger-ui .self-center-m{-ms-flex-item-align:center;align-self:center}.swagger-ui .self-baseline-m{-ms-flex-item-align:baseline;align-self:baseline}.swagger-ui .self-stretch-m{-ms-flex-item-align:stretch;align-self:stretch}.swagger-ui .justify-start-m{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.swagger-ui .justify-end-m{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .justify-center-m{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .justify-between-m{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.swagger-ui .justify-around-m{-ms-flex-pack:distribute;justify-content:space-around}.swagger-ui .content-start-m{-ms-flex-line-pack:start;align-content:flex-start}.swagger-ui .content-end-m{-ms-flex-line-pack:end;align-content:flex-end}.swagger-ui .content-center-m{-ms-flex-line-pack:center;align-content:center}.swagger-ui .content-between-m{-ms-flex-line-pack:justify;align-content:space-between}.swagger-ui .content-around-m{-ms-flex-line-pack:distribute;align-content:space-around}.swagger-ui .content-stretch-m{-ms-flex-line-pack:stretch;align-content:stretch}.swagger-ui .order-0-m{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.swagger-ui .order-1-m{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.swagger-ui .order-2-m{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.swagger-ui .order-3-m{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.swagger-ui .order-4-m{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.swagger-ui .order-5-m{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.swagger-ui .order-6-m{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.swagger-ui .order-7-m{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.swagger-ui .order-8-m{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.swagger-ui .order-last-m{-webkit-box-ordinal-group:100000;-ms-flex-order:99999;order:99999}.swagger-ui .flex-grow-0-m{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.swagger-ui .flex-grow-1-m{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.swagger-ui .flex-shrink-0-m{-ms-flex-negative:0;flex-shrink:0}.swagger-ui .flex-shrink-1-m{-ms-flex-negative:1;flex-shrink:1}}@media screen and (min-width:60em){.swagger-ui .flex-l{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .inline-flex-l{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.swagger-ui .flex-auto-l{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;min-width:0;min-height:0}.swagger-ui .flex-none-l{-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .flex-column-l{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .flex-row-l{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.swagger-ui .flex-wrap-l{-ms-flex-wrap:wrap;flex-wrap:wrap}.swagger-ui .flex-nowrap-l{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-l{-ms-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-l{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.swagger-ui .flex-row-reverse-l{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.swagger-ui .items-start-l{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.swagger-ui .items-end-l{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.swagger-ui .items-center-l{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .items-baseline-l{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.swagger-ui .items-stretch-l{-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.swagger-ui .self-start-l{-ms-flex-item-align:start;align-self:flex-start}.swagger-ui .self-end-l{-ms-flex-item-align:end;align-self:flex-end}.swagger-ui .self-center-l{-ms-flex-item-align:center;align-self:center}.swagger-ui .self-baseline-l{-ms-flex-item-align:baseline;align-self:baseline}.swagger-ui .self-stretch-l{-ms-flex-item-align:stretch;align-self:stretch}.swagger-ui .justify-start-l{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.swagger-ui .justify-end-l{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .justify-center-l{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .justify-between-l{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.swagger-ui .justify-around-l{-ms-flex-pack:distribute;justify-content:space-around}.swagger-ui .content-start-l{-ms-flex-line-pack:start;align-content:flex-start}.swagger-ui .content-end-l{-ms-flex-line-pack:end;align-content:flex-end}.swagger-ui .content-center-l{-ms-flex-line-pack:center;align-content:center}.swagger-ui .content-between-l{-ms-flex-line-pack:justify;align-content:space-between}.swagger-ui .content-around-l{-ms-flex-line-pack:distribute;align-content:space-around}.swagger-ui .content-stretch-l{-ms-flex-line-pack:stretch;align-content:stretch}.swagger-ui .order-0-l{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.swagger-ui .order-1-l{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.swagger-ui .order-2-l{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.swagger-ui .order-3-l{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.swagger-ui .order-4-l{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.swagger-ui .order-5-l{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.swagger-ui .order-6-l{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.swagger-ui .order-7-l{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.swagger-ui .order-8-l{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.swagger-ui .order-last-l{-webkit-box-ordinal-group:100000;-ms-flex-order:99999;order:99999}.swagger-ui .flex-grow-0-l{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.swagger-ui .flex-grow-1-l{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.swagger-ui .flex-shrink-0-l{-ms-flex-negative:0;flex-shrink:0}.swagger-ui .flex-shrink-1-l{-ms-flex-negative:1;flex-shrink:1}}.swagger-ui .fl{float:left;_display:inline}.swagger-ui .fr{float:right;_display:inline}.swagger-ui .fn{float:none}@media screen and (min-width:30em){.swagger-ui .fl-ns{float:left;_display:inline}.swagger-ui .fr-ns{float:right;_display:inline}.swagger-ui .fn-ns{float:none}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .fl-m{float:left;_display:inline}.swagger-ui .fr-m{float:right;_display:inline}.swagger-ui .fn-m{float:none}}@media screen and (min-width:60em){.swagger-ui .fl-l{float:left;_display:inline}.swagger-ui .fr-l{float:right;_display:inline}.swagger-ui .fn-l{float:none}}.swagger-ui .sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica,helvetica neue,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.swagger-ui .serif{font-family:georgia,serif}.swagger-ui .system-sans-serif{font-family:sans-serif}.swagger-ui .system-serif{font-family:serif}.swagger-ui .code,.swagger-ui code{font-family:Consolas,monaco,monospace}.swagger-ui .courier{font-family:Courier Next,courier,monospace}.swagger-ui .helvetica{font-family:helvetica neue,helvetica,sans-serif}.swagger-ui .avenir{font-family:avenir next,avenir,sans-serif}.swagger-ui .athelas{font-family:athelas,georgia,serif}.swagger-ui .georgia{font-family:georgia,serif}.swagger-ui .times{font-family:times,serif}.swagger-ui .bodoni{font-family:Bodoni MT,serif}.swagger-ui .calisto{font-family:Calisto MT,serif}.swagger-ui .garamond{font-family:garamond,serif}.swagger-ui .baskerville{font-family:baskerville,serif}.swagger-ui .i{font-style:italic}.swagger-ui .fs-normal{font-style:normal}@media screen and (min-width:30em){.swagger-ui .i-ns{font-style:italic}.swagger-ui .fs-normal-ns{font-style:normal}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .i-m{font-style:italic}.swagger-ui .fs-normal-m{font-style:normal}}@media screen and (min-width:60em){.swagger-ui .i-l{font-style:italic}.swagger-ui .fs-normal-l{font-style:normal}}.swagger-ui .normal{font-weight:400}.swagger-ui .b{font-weight:700}.swagger-ui .fw1{font-weight:100}.swagger-ui .fw2{font-weight:200}.swagger-ui .fw3{font-weight:300}.swagger-ui .fw4{font-weight:400}.swagger-ui .fw5{font-weight:500}.swagger-ui .fw6{font-weight:600}.swagger-ui .fw7{font-weight:700}.swagger-ui .fw8{font-weight:800}.swagger-ui .fw9{font-weight:900}@media screen and (min-width:30em){.swagger-ui .normal-ns{font-weight:400}.swagger-ui .b-ns{font-weight:700}.swagger-ui .fw1-ns{font-weight:100}.swagger-ui .fw2-ns{font-weight:200}.swagger-ui .fw3-ns{font-weight:300}.swagger-ui .fw4-ns{font-weight:400}.swagger-ui .fw5-ns{font-weight:500}.swagger-ui .fw6-ns{font-weight:600}.swagger-ui .fw7-ns{font-weight:700}.swagger-ui .fw8-ns{font-weight:800}.swagger-ui .fw9-ns{font-weight:900}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .normal-m{font-weight:400}.swagger-ui .b-m{font-weight:700}.swagger-ui .fw1-m{font-weight:100}.swagger-ui .fw2-m{font-weight:200}.swagger-ui .fw3-m{font-weight:300}.swagger-ui .fw4-m{font-weight:400}.swagger-ui .fw5-m{font-weight:500}.swagger-ui .fw6-m{font-weight:600}.swagger-ui .fw7-m{font-weight:700}.swagger-ui .fw8-m{font-weight:800}.swagger-ui .fw9-m{font-weight:900}}@media screen and (min-width:60em){.swagger-ui .normal-l{font-weight:400}.swagger-ui .b-l{font-weight:700}.swagger-ui .fw1-l{font-weight:100}.swagger-ui .fw2-l{font-weight:200}.swagger-ui .fw3-l{font-weight:300}.swagger-ui .fw4-l{font-weight:400}.swagger-ui .fw5-l{font-weight:500}.swagger-ui .fw6-l{font-weight:600}.swagger-ui .fw7-l{font-weight:700}.swagger-ui .fw8-l{font-weight:800}.swagger-ui .fw9-l{font-weight:900}}.swagger-ui .input-reset{-webkit-appearance:none;-moz-appearance:none}.swagger-ui .button-reset::-moz-focus-inner,.swagger-ui .input-reset::-moz-focus-inner{border:0;padding:0}.swagger-ui .h1{height:1rem}.swagger-ui .h2{height:2rem}.swagger-ui .h3{height:4rem}.swagger-ui .h4{height:8rem}.swagger-ui .h5{height:16rem}.swagger-ui .h-25{height:25%}.swagger-ui .h-50{height:50%}.swagger-ui .h-75{height:75%}.swagger-ui .h-100{height:100%}.swagger-ui .min-h-100{min-height:100%}.swagger-ui .vh-25{height:25vh}.swagger-ui .vh-50{height:50vh}.swagger-ui .vh-75{height:75vh}.swagger-ui .vh-100{height:100vh}.swagger-ui .min-vh-100{min-height:100vh}.swagger-ui .h-auto{height:auto}.swagger-ui .h-inherit{height:inherit}@media screen and (min-width:30em){.swagger-ui .h1-ns{height:1rem}.swagger-ui .h2-ns{height:2rem}.swagger-ui .h3-ns{height:4rem}.swagger-ui .h4-ns{height:8rem}.swagger-ui .h5-ns{height:16rem}.swagger-ui .h-25-ns{height:25%}.swagger-ui .h-50-ns{height:50%}.swagger-ui .h-75-ns{height:75%}.swagger-ui .h-100-ns{height:100%}.swagger-ui .min-h-100-ns{min-height:100%}.swagger-ui .vh-25-ns{height:25vh}.swagger-ui .vh-50-ns{height:50vh}.swagger-ui .vh-75-ns{height:75vh}.swagger-ui .vh-100-ns{height:100vh}.swagger-ui .min-vh-100-ns{min-height:100vh}.swagger-ui .h-auto-ns{height:auto}.swagger-ui .h-inherit-ns{height:inherit}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .h1-m{height:1rem}.swagger-ui .h2-m{height:2rem}.swagger-ui .h3-m{height:4rem}.swagger-ui .h4-m{height:8rem}.swagger-ui .h5-m{height:16rem}.swagger-ui .h-25-m{height:25%}.swagger-ui .h-50-m{height:50%}.swagger-ui .h-75-m{height:75%}.swagger-ui .h-100-m{height:100%}.swagger-ui .min-h-100-m{min-height:100%}.swagger-ui .vh-25-m{height:25vh}.swagger-ui .vh-50-m{height:50vh}.swagger-ui .vh-75-m{height:75vh}.swagger-ui .vh-100-m{height:100vh}.swagger-ui .min-vh-100-m{min-height:100vh}.swagger-ui .h-auto-m{height:auto}.swagger-ui .h-inherit-m{height:inherit}}@media screen and (min-width:60em){.swagger-ui .h1-l{height:1rem}.swagger-ui .h2-l{height:2rem}.swagger-ui .h3-l{height:4rem}.swagger-ui .h4-l{height:8rem}.swagger-ui .h5-l{height:16rem}.swagger-ui .h-25-l{height:25%}.swagger-ui .h-50-l{height:50%}.swagger-ui .h-75-l{height:75%}.swagger-ui .h-100-l{height:100%}.swagger-ui .min-h-100-l{min-height:100%}.swagger-ui .vh-25-l{height:25vh}.swagger-ui .vh-50-l{height:50vh}.swagger-ui .vh-75-l{height:75vh}.swagger-ui .vh-100-l{height:100vh}.swagger-ui .min-vh-100-l{min-height:100vh}.swagger-ui .h-auto-l{height:auto}.swagger-ui .h-inherit-l{height:inherit}}.swagger-ui .tracked{letter-spacing:.1em}.swagger-ui .tracked-tight{letter-spacing:-.05em}.swagger-ui .tracked-mega{letter-spacing:.25em}@media screen and (min-width:30em){.swagger-ui .tracked-ns{letter-spacing:.1em}.swagger-ui .tracked-tight-ns{letter-spacing:-.05em}.swagger-ui .tracked-mega-ns{letter-spacing:.25em}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .tracked-m{letter-spacing:.1em}.swagger-ui .tracked-tight-m{letter-spacing:-.05em}.swagger-ui .tracked-mega-m{letter-spacing:.25em}}@media screen and (min-width:60em){.swagger-ui .tracked-l{letter-spacing:.1em}.swagger-ui .tracked-tight-l{letter-spacing:-.05em}.swagger-ui .tracked-mega-l{letter-spacing:.25em}}.swagger-ui .lh-solid{line-height:1}.swagger-ui .lh-title{line-height:1.25}.swagger-ui .lh-copy{line-height:1.5}@media screen and (min-width:30em){.swagger-ui .lh-solid-ns{line-height:1}.swagger-ui .lh-title-ns{line-height:1.25}.swagger-ui .lh-copy-ns{line-height:1.5}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .lh-solid-m{line-height:1}.swagger-ui .lh-title-m{line-height:1.25}.swagger-ui .lh-copy-m{line-height:1.5}}@media screen and (min-width:60em){.swagger-ui .lh-solid-l{line-height:1}.swagger-ui .lh-title-l{line-height:1.25}.swagger-ui .lh-copy-l{line-height:1.5}}.swagger-ui .link{text-decoration:none}.swagger-ui .link,.swagger-ui .link:active,.swagger-ui .link:focus,.swagger-ui .link:hover,.swagger-ui .link:link,.swagger-ui .link:visited{-webkit-transition:color .15s ease-in;transition:color .15s ease-in}.swagger-ui .link:focus{outline:1px dotted currentColor}.swagger-ui .list{list-style-type:none}.swagger-ui .mw-100{max-width:100%}.swagger-ui .mw1{max-width:1rem}.swagger-ui .mw2{max-width:2rem}.swagger-ui .mw3{max-width:4rem}.swagger-ui .mw4{max-width:8rem}.swagger-ui .mw5{max-width:16rem}.swagger-ui .mw6{max-width:32rem}.swagger-ui .mw7{max-width:48rem}.swagger-ui .mw8{max-width:64rem}.swagger-ui .mw9{max-width:96rem}.swagger-ui .mw-none{max-width:none}@media screen and (min-width:30em){.swagger-ui .mw-100-ns{max-width:100%}.swagger-ui .mw1-ns{max-width:1rem}.swagger-ui .mw2-ns{max-width:2rem}.swagger-ui .mw3-ns{max-width:4rem}.swagger-ui .mw4-ns{max-width:8rem}.swagger-ui .mw5-ns{max-width:16rem}.swagger-ui .mw6-ns{max-width:32rem}.swagger-ui .mw7-ns{max-width:48rem}.swagger-ui .mw8-ns{max-width:64rem}.swagger-ui .mw9-ns{max-width:96rem}.swagger-ui .mw-none-ns{max-width:none}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .mw-100-m{max-width:100%}.swagger-ui .mw1-m{max-width:1rem}.swagger-ui .mw2-m{max-width:2rem}.swagger-ui .mw3-m{max-width:4rem}.swagger-ui .mw4-m{max-width:8rem}.swagger-ui .mw5-m{max-width:16rem}.swagger-ui .mw6-m{max-width:32rem}.swagger-ui .mw7-m{max-width:48rem}.swagger-ui .mw8-m{max-width:64rem}.swagger-ui .mw9-m{max-width:96rem}.swagger-ui .mw-none-m{max-width:none}}@media screen and (min-width:60em){.swagger-ui .mw-100-l{max-width:100%}.swagger-ui .mw1-l{max-width:1rem}.swagger-ui .mw2-l{max-width:2rem}.swagger-ui .mw3-l{max-width:4rem}.swagger-ui .mw4-l{max-width:8rem}.swagger-ui .mw5-l{max-width:16rem}.swagger-ui .mw6-l{max-width:32rem}.swagger-ui .mw7-l{max-width:48rem}.swagger-ui .mw8-l{max-width:64rem}.swagger-ui .mw9-l{max-width:96rem}.swagger-ui .mw-none-l{max-width:none}}.swagger-ui .w1{width:1rem}.swagger-ui .w2{width:2rem}.swagger-ui .w3{width:4rem}.swagger-ui .w4{width:8rem}.swagger-ui .w5{width:16rem}.swagger-ui .w-10{width:10%}.swagger-ui .w-20{width:20%}.swagger-ui .w-25{width:25%}.swagger-ui .w-30{width:30%}.swagger-ui .w-33{width:33%}.swagger-ui .w-34{width:34%}.swagger-ui .w-40{width:40%}.swagger-ui .w-50{width:50%}.swagger-ui .w-60{width:60%}.swagger-ui .w-70{width:70%}.swagger-ui .w-75{width:75%}.swagger-ui .w-80{width:80%}.swagger-ui .w-90{width:90%}.swagger-ui .w-100{width:100%}.swagger-ui .w-third{width:33.33333%}.swagger-ui .w-two-thirds{width:66.66667%}.swagger-ui .w-auto{width:auto}@media screen and (min-width:30em){.swagger-ui .w1-ns{width:1rem}.swagger-ui .w2-ns{width:2rem}.swagger-ui .w3-ns{width:4rem}.swagger-ui .w4-ns{width:8rem}.swagger-ui .w5-ns{width:16rem}.swagger-ui .w-10-ns{width:10%}.swagger-ui .w-20-ns{width:20%}.swagger-ui .w-25-ns{width:25%}.swagger-ui .w-30-ns{width:30%}.swagger-ui .w-33-ns{width:33%}.swagger-ui .w-34-ns{width:34%}.swagger-ui .w-40-ns{width:40%}.swagger-ui .w-50-ns{width:50%}.swagger-ui .w-60-ns{width:60%}.swagger-ui .w-70-ns{width:70%}.swagger-ui .w-75-ns{width:75%}.swagger-ui .w-80-ns{width:80%}.swagger-ui .w-90-ns{width:90%}.swagger-ui .w-100-ns{width:100%}.swagger-ui .w-third-ns{width:33.33333%}.swagger-ui .w-two-thirds-ns{width:66.66667%}.swagger-ui .w-auto-ns{width:auto}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .w1-m{width:1rem}.swagger-ui .w2-m{width:2rem}.swagger-ui .w3-m{width:4rem}.swagger-ui .w4-m{width:8rem}.swagger-ui .w5-m{width:16rem}.swagger-ui .w-10-m{width:10%}.swagger-ui .w-20-m{width:20%}.swagger-ui .w-25-m{width:25%}.swagger-ui .w-30-m{width:30%}.swagger-ui .w-33-m{width:33%}.swagger-ui .w-34-m{width:34%}.swagger-ui .w-40-m{width:40%}.swagger-ui .w-50-m{width:50%}.swagger-ui .w-60-m{width:60%}.swagger-ui .w-70-m{width:70%}.swagger-ui .w-75-m{width:75%}.swagger-ui .w-80-m{width:80%}.swagger-ui .w-90-m{width:90%}.swagger-ui .w-100-m{width:100%}.swagger-ui .w-third-m{width:33.33333%}.swagger-ui .w-two-thirds-m{width:66.66667%}.swagger-ui .w-auto-m{width:auto}}@media screen and (min-width:60em){.swagger-ui .w1-l{width:1rem}.swagger-ui .w2-l{width:2rem}.swagger-ui .w3-l{width:4rem}.swagger-ui .w4-l{width:8rem}.swagger-ui .w5-l{width:16rem}.swagger-ui .w-10-l{width:10%}.swagger-ui .w-20-l{width:20%}.swagger-ui .w-25-l{width:25%}.swagger-ui .w-30-l{width:30%}.swagger-ui .w-33-l{width:33%}.swagger-ui .w-34-l{width:34%}.swagger-ui .w-40-l{width:40%}.swagger-ui .w-50-l{width:50%}.swagger-ui .w-60-l{width:60%}.swagger-ui .w-70-l{width:70%}.swagger-ui .w-75-l{width:75%}.swagger-ui .w-80-l{width:80%}.swagger-ui .w-90-l{width:90%}.swagger-ui .w-100-l{width:100%}.swagger-ui .w-third-l{width:33.33333%}.swagger-ui .w-two-thirds-l{width:66.66667%}.swagger-ui .w-auto-l{width:auto}}.swagger-ui .overflow-visible{overflow:visible}.swagger-ui .overflow-hidden{overflow:hidden}.swagger-ui .overflow-scroll{overflow:scroll}.swagger-ui .overflow-auto{overflow:auto}.swagger-ui .overflow-x-visible{overflow-x:visible}.swagger-ui .overflow-x-hidden{overflow-x:hidden}.swagger-ui .overflow-x-scroll{overflow-x:scroll}.swagger-ui .overflow-x-auto{overflow-x:auto}.swagger-ui .overflow-y-visible{overflow-y:visible}.swagger-ui .overflow-y-hidden{overflow-y:hidden}.swagger-ui .overflow-y-scroll{overflow-y:scroll}.swagger-ui .overflow-y-auto{overflow-y:auto}@media screen and (min-width:30em){.swagger-ui .overflow-visible-ns{overflow:visible}.swagger-ui .overflow-hidden-ns{overflow:hidden}.swagger-ui .overflow-scroll-ns{overflow:scroll}.swagger-ui .overflow-auto-ns{overflow:auto}.swagger-ui .overflow-x-visible-ns{overflow-x:visible}.swagger-ui .overflow-x-hidden-ns{overflow-x:hidden}.swagger-ui .overflow-x-scroll-ns{overflow-x:scroll}.swagger-ui .overflow-x-auto-ns{overflow-x:auto}.swagger-ui .overflow-y-visible-ns{overflow-y:visible}.swagger-ui .overflow-y-hidden-ns{overflow-y:hidden}.swagger-ui .overflow-y-scroll-ns{overflow-y:scroll}.swagger-ui .overflow-y-auto-ns{overflow-y:auto}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .overflow-visible-m{overflow:visible}.swagger-ui .overflow-hidden-m{overflow:hidden}.swagger-ui .overflow-scroll-m{overflow:scroll}.swagger-ui .overflow-auto-m{overflow:auto}.swagger-ui .overflow-x-visible-m{overflow-x:visible}.swagger-ui .overflow-x-hidden-m{overflow-x:hidden}.swagger-ui .overflow-x-scroll-m{overflow-x:scroll}.swagger-ui .overflow-x-auto-m{overflow-x:auto}.swagger-ui .overflow-y-visible-m{overflow-y:visible}.swagger-ui .overflow-y-hidden-m{overflow-y:hidden}.swagger-ui .overflow-y-scroll-m{overflow-y:scroll}.swagger-ui .overflow-y-auto-m{overflow-y:auto}}@media screen and (min-width:60em){.swagger-ui .overflow-visible-l{overflow:visible}.swagger-ui .overflow-hidden-l{overflow:hidden}.swagger-ui .overflow-scroll-l{overflow:scroll}.swagger-ui .overflow-auto-l{overflow:auto}.swagger-ui .overflow-x-visible-l{overflow-x:visible}.swagger-ui .overflow-x-hidden-l{overflow-x:hidden}.swagger-ui .overflow-x-scroll-l{overflow-x:scroll}.swagger-ui .overflow-x-auto-l{overflow-x:auto}.swagger-ui .overflow-y-visible-l{overflow-y:visible}.swagger-ui .overflow-y-hidden-l{overflow-y:hidden}.swagger-ui .overflow-y-scroll-l{overflow-y:scroll}.swagger-ui .overflow-y-auto-l{overflow-y:auto}}.swagger-ui .static{position:static}.swagger-ui .relative{position:relative}.swagger-ui .absolute{position:absolute}.swagger-ui .fixed{position:fixed}@media screen and (min-width:30em){.swagger-ui .static-ns{position:static}.swagger-ui .relative-ns{position:relative}.swagger-ui .absolute-ns{position:absolute}.swagger-ui .fixed-ns{position:fixed}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .static-m{position:static}.swagger-ui .relative-m{position:relative}.swagger-ui .absolute-m{position:absolute}.swagger-ui .fixed-m{position:fixed}}@media screen and (min-width:60em){.swagger-ui .static-l{position:static}.swagger-ui .relative-l{position:relative}.swagger-ui .absolute-l{position:absolute}.swagger-ui .fixed-l{position:fixed}}.swagger-ui .o-100{opacity:1}.swagger-ui .o-90{opacity:.9}.swagger-ui .o-80{opacity:.8}.swagger-ui .o-70{opacity:.7}.swagger-ui .o-60{opacity:.6}.swagger-ui .o-50{opacity:.5}.swagger-ui .o-40{opacity:.4}.swagger-ui .o-30{opacity:.3}.swagger-ui .o-20{opacity:.2}.swagger-ui .o-10{opacity:.1}.swagger-ui .o-05{opacity:.05}.swagger-ui .o-025{opacity:.025}.swagger-ui .o-0{opacity:0}.swagger-ui .rotate-45{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swagger-ui .rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui .rotate-135{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.swagger-ui .rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.swagger-ui .rotate-225{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.swagger-ui .rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.swagger-ui .rotate-315{-webkit-transform:rotate(315deg);transform:rotate(315deg)}@media screen and (min-width:30em){.swagger-ui .rotate-45-ns{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swagger-ui .rotate-90-ns{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui .rotate-135-ns{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.swagger-ui .rotate-180-ns{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.swagger-ui .rotate-225-ns{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.swagger-ui .rotate-270-ns{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.swagger-ui .rotate-315-ns{-webkit-transform:rotate(315deg);transform:rotate(315deg)}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .rotate-45-m{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swagger-ui .rotate-90-m{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui .rotate-135-m{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.swagger-ui .rotate-180-m{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.swagger-ui .rotate-225-m{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.swagger-ui .rotate-270-m{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.swagger-ui .rotate-315-m{-webkit-transform:rotate(315deg);transform:rotate(315deg)}}@media screen and (min-width:60em){.swagger-ui .rotate-45-l{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swagger-ui .rotate-90-l{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui .rotate-135-l{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.swagger-ui .rotate-180-l{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.swagger-ui .rotate-225-l{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.swagger-ui .rotate-270-l{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.swagger-ui .rotate-315-l{-webkit-transform:rotate(315deg);transform:rotate(315deg)}}.swagger-ui .black-90{color:rgba(0,0,0,.9)}.swagger-ui .black-80{color:rgba(0,0,0,.8)}.swagger-ui .black-70{color:rgba(0,0,0,.7)}.swagger-ui .black-60{color:rgba(0,0,0,.6)}.swagger-ui .black-50{color:rgba(0,0,0,.5)}.swagger-ui .black-40{color:rgba(0,0,0,.4)}.swagger-ui .black-30{color:rgba(0,0,0,.3)}.swagger-ui .black-20{color:rgba(0,0,0,.2)}.swagger-ui .black-10{color:rgba(0,0,0,.1)}.swagger-ui .black-05{color:rgba(0,0,0,.05)}.swagger-ui .white-90{color:hsla(0,0%,100%,.9)}.swagger-ui .white-80{color:hsla(0,0%,100%,.8)}.swagger-ui .white-70{color:hsla(0,0%,100%,.7)}.swagger-ui .white-60{color:hsla(0,0%,100%,.6)}.swagger-ui .white-50{color:hsla(0,0%,100%,.5)}.swagger-ui .white-40{color:hsla(0,0%,100%,.4)}.swagger-ui .white-30{color:hsla(0,0%,100%,.3)}.swagger-ui .white-20{color:hsla(0,0%,100%,.2)}.swagger-ui .white-10{color:hsla(0,0%,100%,.1)}.swagger-ui .black{color:#000}.swagger-ui .near-black{color:#111}.swagger-ui .dark-gray{color:#333}.swagger-ui .mid-gray{color:#555}.swagger-ui .gray{color:#777}.swagger-ui .silver{color:#999}.swagger-ui .light-silver{color:#aaa}.swagger-ui .moon-gray{color:#ccc}.swagger-ui .light-gray{color:#eee}.swagger-ui .near-white{color:#f4f4f4}.swagger-ui .white{color:#fff}.swagger-ui .dark-red{color:#e7040f}.swagger-ui .red{color:#ff4136}.swagger-ui .light-red{color:#ff725c}.swagger-ui .orange{color:#ff6300}.swagger-ui .gold{color:#ffb700}.swagger-ui .yellow{color:gold}.swagger-ui .light-yellow{color:#fbf1a9}.swagger-ui .purple{color:#5e2ca5}.swagger-ui .light-purple{color:#a463f2}.swagger-ui .dark-pink{color:#d5008f}.swagger-ui .hot-pink{color:#ff41b4}.swagger-ui .pink{color:#ff80cc}.swagger-ui .light-pink{color:#ffa3d7}.swagger-ui .dark-green{color:#137752}.swagger-ui .green{color:#19a974}.swagger-ui .light-green{color:#9eebcf}.swagger-ui .navy{color:#001b44}.swagger-ui .dark-blue{color:#00449e}.swagger-ui .blue{color:#357edd}.swagger-ui .light-blue{color:#96ccff}.swagger-ui .lightest-blue{color:#cdecff}.swagger-ui .washed-blue{color:#f6fffe}.swagger-ui .washed-green{color:#e8fdf5}.swagger-ui .washed-yellow{color:#fffceb}.swagger-ui .washed-red{color:#ffdfdf}.swagger-ui .color-inherit{color:inherit}.swagger-ui .bg-black-90{background-color:rgba(0,0,0,.9)}.swagger-ui .bg-black-80{background-color:rgba(0,0,0,.8)}.swagger-ui .bg-black-70{background-color:rgba(0,0,0,.7)}.swagger-ui .bg-black-60{background-color:rgba(0,0,0,.6)}.swagger-ui .bg-black-50{background-color:rgba(0,0,0,.5)}.swagger-ui .bg-black-40{background-color:rgba(0,0,0,.4)}.swagger-ui .bg-black-30{background-color:rgba(0,0,0,.3)}.swagger-ui .bg-black-20{background-color:rgba(0,0,0,.2)}.swagger-ui .bg-black-10{background-color:rgba(0,0,0,.1)}.swagger-ui .bg-black-05{background-color:rgba(0,0,0,.05)}.swagger-ui .bg-white-90{background-color:hsla(0,0%,100%,.9)}.swagger-ui .bg-white-80{background-color:hsla(0,0%,100%,.8)}.swagger-ui .bg-white-70{background-color:hsla(0,0%,100%,.7)}.swagger-ui .bg-white-60{background-color:hsla(0,0%,100%,.6)}.swagger-ui .bg-white-50{background-color:hsla(0,0%,100%,.5)}.swagger-ui .bg-white-40{background-color:hsla(0,0%,100%,.4)}.swagger-ui .bg-white-30{background-color:hsla(0,0%,100%,.3)}.swagger-ui .bg-white-20{background-color:hsla(0,0%,100%,.2)}.swagger-ui .bg-white-10{background-color:hsla(0,0%,100%,.1)}.swagger-ui .bg-black{background-color:#000}.swagger-ui .bg-near-black{background-color:#111}.swagger-ui .bg-dark-gray{background-color:#333}.swagger-ui .bg-mid-gray{background-color:#555}.swagger-ui .bg-gray{background-color:#777}.swagger-ui .bg-silver{background-color:#999}.swagger-ui .bg-light-silver{background-color:#aaa}.swagger-ui .bg-moon-gray{background-color:#ccc}.swagger-ui .bg-light-gray{background-color:#eee}.swagger-ui .bg-near-white{background-color:#f4f4f4}.swagger-ui .bg-white{background-color:#fff}.swagger-ui .bg-transparent{background-color:transparent}.swagger-ui .bg-dark-red{background-color:#e7040f}.swagger-ui .bg-red{background-color:#ff4136}.swagger-ui .bg-light-red{background-color:#ff725c}.swagger-ui .bg-orange{background-color:#ff6300}.swagger-ui .bg-gold{background-color:#ffb700}.swagger-ui .bg-yellow{background-color:gold}.swagger-ui .bg-light-yellow{background-color:#fbf1a9}.swagger-ui .bg-purple{background-color:#5e2ca5}.swagger-ui .bg-light-purple{background-color:#a463f2}.swagger-ui .bg-dark-pink{background-color:#d5008f}.swagger-ui .bg-hot-pink{background-color:#ff41b4}.swagger-ui .bg-pink{background-color:#ff80cc}.swagger-ui .bg-light-pink{background-color:#ffa3d7}.swagger-ui .bg-dark-green{background-color:#137752}.swagger-ui .bg-green{background-color:#19a974}.swagger-ui .bg-light-green{background-color:#9eebcf}.swagger-ui .bg-navy{background-color:#001b44}.swagger-ui .bg-dark-blue{background-color:#00449e}.swagger-ui .bg-blue{background-color:#357edd}.swagger-ui .bg-light-blue{background-color:#96ccff}.swagger-ui .bg-lightest-blue{background-color:#cdecff}.swagger-ui .bg-washed-blue{background-color:#f6fffe}.swagger-ui .bg-washed-green{background-color:#e8fdf5}.swagger-ui .bg-washed-yellow{background-color:#fffceb}.swagger-ui .bg-washed-red{background-color:#ffdfdf}.swagger-ui .bg-inherit{background-color:inherit}.swagger-ui .hover-black:focus,.swagger-ui .hover-black:hover{color:#000}.swagger-ui .hover-near-black:focus,.swagger-ui .hover-near-black:hover{color:#111}.swagger-ui .hover-dark-gray:focus,.swagger-ui .hover-dark-gray:hover{color:#333}.swagger-ui .hover-mid-gray:focus,.swagger-ui .hover-mid-gray:hover{color:#555}.swagger-ui .hover-gray:focus,.swagger-ui .hover-gray:hover{color:#777}.swagger-ui .hover-silver:focus,.swagger-ui .hover-silver:hover{color:#999}.swagger-ui .hover-light-silver:focus,.swagger-ui .hover-light-silver:hover{color:#aaa}.swagger-ui .hover-moon-gray:focus,.swagger-ui .hover-moon-gray:hover{color:#ccc}.swagger-ui .hover-light-gray:focus,.swagger-ui .hover-light-gray:hover{color:#eee}.swagger-ui .hover-near-white:focus,.swagger-ui .hover-near-white:hover{color:#f4f4f4}.swagger-ui .hover-white:focus,.swagger-ui .hover-white:hover{color:#fff}.swagger-ui .hover-black-90:focus,.swagger-ui .hover-black-90:hover{color:rgba(0,0,0,.9)}.swagger-ui .hover-black-80:focus,.swagger-ui .hover-black-80:hover{color:rgba(0,0,0,.8)}.swagger-ui .hover-black-70:focus,.swagger-ui .hover-black-70:hover{color:rgba(0,0,0,.7)}.swagger-ui .hover-black-60:focus,.swagger-ui .hover-black-60:hover{color:rgba(0,0,0,.6)}.swagger-ui .hover-black-50:focus,.swagger-ui .hover-black-50:hover{color:rgba(0,0,0,.5)}.swagger-ui .hover-black-40:focus,.swagger-ui .hover-black-40:hover{color:rgba(0,0,0,.4)}.swagger-ui .hover-black-30:focus,.swagger-ui .hover-black-30:hover{color:rgba(0,0,0,.3)}.swagger-ui .hover-black-20:focus,.swagger-ui .hover-black-20:hover{color:rgba(0,0,0,.2)}.swagger-ui .hover-black-10:focus,.swagger-ui .hover-black-10:hover{color:rgba(0,0,0,.1)}.swagger-ui .hover-white-90:focus,.swagger-ui .hover-white-90:hover{color:hsla(0,0%,100%,.9)}.swagger-ui .hover-white-80:focus,.swagger-ui .hover-white-80:hover{color:hsla(0,0%,100%,.8)}.swagger-ui .hover-white-70:focus,.swagger-ui .hover-white-70:hover{color:hsla(0,0%,100%,.7)}.swagger-ui .hover-white-60:focus,.swagger-ui .hover-white-60:hover{color:hsla(0,0%,100%,.6)}.swagger-ui .hover-white-50:focus,.swagger-ui .hover-white-50:hover{color:hsla(0,0%,100%,.5)}.swagger-ui .hover-white-40:focus,.swagger-ui .hover-white-40:hover{color:hsla(0,0%,100%,.4)}.swagger-ui .hover-white-30:focus,.swagger-ui .hover-white-30:hover{color:hsla(0,0%,100%,.3)}.swagger-ui .hover-white-20:focus,.swagger-ui .hover-white-20:hover{color:hsla(0,0%,100%,.2)}.swagger-ui .hover-white-10:focus,.swagger-ui .hover-white-10:hover{color:hsla(0,0%,100%,.1)}.swagger-ui .hover-inherit:focus,.swagger-ui .hover-inherit:hover{color:inherit}.swagger-ui .hover-bg-black:focus,.swagger-ui .hover-bg-black:hover{background-color:#000}.swagger-ui .hover-bg-near-black:focus,.swagger-ui .hover-bg-near-black:hover{background-color:#111}.swagger-ui .hover-bg-dark-gray:focus,.swagger-ui .hover-bg-dark-gray:hover{background-color:#333}.swagger-ui .hover-bg-mid-gray:focus,.swagger-ui .hover-bg-mid-gray:hover{background-color:#555}.swagger-ui .hover-bg-gray:focus,.swagger-ui .hover-bg-gray:hover{background-color:#777}.swagger-ui .hover-bg-silver:focus,.swagger-ui .hover-bg-silver:hover{background-color:#999}.swagger-ui .hover-bg-light-silver:focus,.swagger-ui .hover-bg-light-silver:hover{background-color:#aaa}.swagger-ui .hover-bg-moon-gray:focus,.swagger-ui .hover-bg-moon-gray:hover{background-color:#ccc}.swagger-ui .hover-bg-light-gray:focus,.swagger-ui .hover-bg-light-gray:hover{background-color:#eee}.swagger-ui .hover-bg-near-white:focus,.swagger-ui .hover-bg-near-white:hover{background-color:#f4f4f4}.swagger-ui .hover-bg-white:focus,.swagger-ui .hover-bg-white:hover{background-color:#fff}.swagger-ui .hover-bg-transparent:focus,.swagger-ui .hover-bg-transparent:hover{background-color:transparent}.swagger-ui .hover-bg-black-90:focus,.swagger-ui .hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.swagger-ui .hover-bg-black-80:focus,.swagger-ui .hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.swagger-ui .hover-bg-black-70:focus,.swagger-ui .hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.swagger-ui .hover-bg-black-60:focus,.swagger-ui .hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.swagger-ui .hover-bg-black-50:focus,.swagger-ui .hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.swagger-ui .hover-bg-black-40:focus,.swagger-ui .hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.swagger-ui .hover-bg-black-30:focus,.swagger-ui .hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.swagger-ui .hover-bg-black-20:focus,.swagger-ui .hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.swagger-ui .hover-bg-black-10:focus,.swagger-ui .hover-bg-black-10:hover{background-color:rgba(0,0,0,.1)}.swagger-ui .hover-bg-white-90:focus,.swagger-ui .hover-bg-white-90:hover{background-color:hsla(0,0%,100%,.9)}.swagger-ui .hover-bg-white-80:focus,.swagger-ui .hover-bg-white-80:hover{background-color:hsla(0,0%,100%,.8)}.swagger-ui .hover-bg-white-70:focus,.swagger-ui .hover-bg-white-70:hover{background-color:hsla(0,0%,100%,.7)}.swagger-ui .hover-bg-white-60:focus,.swagger-ui .hover-bg-white-60:hover{background-color:hsla(0,0%,100%,.6)}.swagger-ui .hover-bg-white-50:focus,.swagger-ui .hover-bg-white-50:hover{background-color:hsla(0,0%,100%,.5)}.swagger-ui .hover-bg-white-40:focus,.swagger-ui .hover-bg-white-40:hover{background-color:hsla(0,0%,100%,.4)}.swagger-ui .hover-bg-white-30:focus,.swagger-ui .hover-bg-white-30:hover{background-color:hsla(0,0%,100%,.3)}.swagger-ui .hover-bg-white-20:focus,.swagger-ui .hover-bg-white-20:hover{background-color:hsla(0,0%,100%,.2)}.swagger-ui .hover-bg-white-10:focus,.swagger-ui .hover-bg-white-10:hover{background-color:hsla(0,0%,100%,.1)}.swagger-ui .hover-dark-red:focus,.swagger-ui .hover-dark-red:hover{color:#e7040f}.swagger-ui .hover-red:focus,.swagger-ui .hover-red:hover{color:#ff4136}.swagger-ui .hover-light-red:focus,.swagger-ui .hover-light-red:hover{color:#ff725c}.swagger-ui .hover-orange:focus,.swagger-ui .hover-orange:hover{color:#ff6300}.swagger-ui .hover-gold:focus,.swagger-ui .hover-gold:hover{color:#ffb700}.swagger-ui .hover-yellow:focus,.swagger-ui .hover-yellow:hover{color:gold}.swagger-ui .hover-light-yellow:focus,.swagger-ui .hover-light-yellow:hover{color:#fbf1a9}.swagger-ui .hover-purple:focus,.swagger-ui .hover-purple:hover{color:#5e2ca5}.swagger-ui .hover-light-purple:focus,.swagger-ui .hover-light-purple:hover{color:#a463f2}.swagger-ui .hover-dark-pink:focus,.swagger-ui .hover-dark-pink:hover{color:#d5008f}.swagger-ui .hover-hot-pink:focus,.swagger-ui .hover-hot-pink:hover{color:#ff41b4}.swagger-ui .hover-pink:focus,.swagger-ui .hover-pink:hover{color:#ff80cc}.swagger-ui .hover-light-pink:focus,.swagger-ui .hover-light-pink:hover{color:#ffa3d7}.swagger-ui .hover-dark-green:focus,.swagger-ui .hover-dark-green:hover{color:#137752}.swagger-ui .hover-green:focus,.swagger-ui .hover-green:hover{color:#19a974}.swagger-ui .hover-light-green:focus,.swagger-ui .hover-light-green:hover{color:#9eebcf}.swagger-ui .hover-navy:focus,.swagger-ui .hover-navy:hover{color:#001b44}.swagger-ui .hover-dark-blue:focus,.swagger-ui .hover-dark-blue:hover{color:#00449e}.swagger-ui .hover-blue:focus,.swagger-ui .hover-blue:hover{color:#357edd}.swagger-ui .hover-light-blue:focus,.swagger-ui .hover-light-blue:hover{color:#96ccff}.swagger-ui .hover-lightest-blue:focus,.swagger-ui .hover-lightest-blue:hover{color:#cdecff}.swagger-ui .hover-washed-blue:focus,.swagger-ui .hover-washed-blue:hover{color:#f6fffe}.swagger-ui .hover-washed-green:focus,.swagger-ui .hover-washed-green:hover{color:#e8fdf5}.swagger-ui .hover-washed-yellow:focus,.swagger-ui .hover-washed-yellow:hover{color:#fffceb}.swagger-ui .hover-washed-red:focus,.swagger-ui .hover-washed-red:hover{color:#ffdfdf}.swagger-ui .hover-bg-dark-red:focus,.swagger-ui .hover-bg-dark-red:hover{background-color:#e7040f}.swagger-ui .hover-bg-red:focus,.swagger-ui .hover-bg-red:hover{background-color:#ff4136}.swagger-ui .hover-bg-light-red:focus,.swagger-ui .hover-bg-light-red:hover{background-color:#ff725c}.swagger-ui .hover-bg-orange:focus,.swagger-ui .hover-bg-orange:hover{background-color:#ff6300}.swagger-ui .hover-bg-gold:focus,.swagger-ui .hover-bg-gold:hover{background-color:#ffb700}.swagger-ui .hover-bg-yellow:focus,.swagger-ui .hover-bg-yellow:hover{background-color:gold}.swagger-ui .hover-bg-light-yellow:focus,.swagger-ui .hover-bg-light-yellow:hover{background-color:#fbf1a9}.swagger-ui .hover-bg-purple:focus,.swagger-ui .hover-bg-purple:hover{background-color:#5e2ca5}.swagger-ui .hover-bg-light-purple:focus,.swagger-ui .hover-bg-light-purple:hover{background-color:#a463f2}.swagger-ui .hover-bg-dark-pink:focus,.swagger-ui .hover-bg-dark-pink:hover{background-color:#d5008f}.swagger-ui .hover-bg-hot-pink:focus,.swagger-ui .hover-bg-hot-pink:hover{background-color:#ff41b4}.swagger-ui .hover-bg-pink:focus,.swagger-ui .hover-bg-pink:hover{background-color:#ff80cc}.swagger-ui .hover-bg-light-pink:focus,.swagger-ui .hover-bg-light-pink:hover{background-color:#ffa3d7}.swagger-ui .hover-bg-dark-green:focus,.swagger-ui .hover-bg-dark-green:hover{background-color:#137752}.swagger-ui .hover-bg-green:focus,.swagger-ui .hover-bg-green:hover{background-color:#19a974}.swagger-ui .hover-bg-light-green:focus,.swagger-ui .hover-bg-light-green:hover{background-color:#9eebcf}.swagger-ui .hover-bg-navy:focus,.swagger-ui .hover-bg-navy:hover{background-color:#001b44}.swagger-ui .hover-bg-dark-blue:focus,.swagger-ui .hover-bg-dark-blue:hover{background-color:#00449e}.swagger-ui .hover-bg-blue:focus,.swagger-ui .hover-bg-blue:hover{background-color:#357edd}.swagger-ui .hover-bg-light-blue:focus,.swagger-ui .hover-bg-light-blue:hover{background-color:#96ccff}.swagger-ui .hover-bg-lightest-blue:focus,.swagger-ui .hover-bg-lightest-blue:hover{background-color:#cdecff}.swagger-ui .hover-bg-washed-blue:focus,.swagger-ui .hover-bg-washed-blue:hover{background-color:#f6fffe}.swagger-ui .hover-bg-washed-green:focus,.swagger-ui .hover-bg-washed-green:hover{background-color:#e8fdf5}.swagger-ui .hover-bg-washed-yellow:focus,.swagger-ui .hover-bg-washed-yellow:hover{background-color:#fffceb}.swagger-ui .hover-bg-washed-red:focus,.swagger-ui .hover-bg-washed-red:hover{background-color:#ffdfdf}.swagger-ui .hover-bg-inherit:focus,.swagger-ui .hover-bg-inherit:hover{background-color:inherit}.swagger-ui .pa0{padding:0}.swagger-ui .pa1{padding:.25rem}.swagger-ui .pa2{padding:.5rem}.swagger-ui .pa3{padding:1rem}.swagger-ui .pa4{padding:2rem}.swagger-ui .pa5{padding:4rem}.swagger-ui .pa6{padding:8rem}.swagger-ui .pa7{padding:16rem}.swagger-ui .pl0{padding-left:0}.swagger-ui .pl1{padding-left:.25rem}.swagger-ui .pl2{padding-left:.5rem}.swagger-ui .pl3{padding-left:1rem}.swagger-ui .pl4{padding-left:2rem}.swagger-ui .pl5{padding-left:4rem}.swagger-ui .pl6{padding-left:8rem}.swagger-ui .pl7{padding-left:16rem}.swagger-ui .pr0{padding-right:0}.swagger-ui .pr1{padding-right:.25rem}.swagger-ui .pr2{padding-right:.5rem}.swagger-ui .pr3{padding-right:1rem}.swagger-ui .pr4{padding-right:2rem}.swagger-ui .pr5{padding-right:4rem}.swagger-ui .pr6{padding-right:8rem}.swagger-ui .pr7{padding-right:16rem}.swagger-ui .pb0{padding-bottom:0}.swagger-ui .pb1{padding-bottom:.25rem}.swagger-ui .pb2{padding-bottom:.5rem}.swagger-ui .pb3{padding-bottom:1rem}.swagger-ui .pb4{padding-bottom:2rem}.swagger-ui .pb5{padding-bottom:4rem}.swagger-ui .pb6{padding-bottom:8rem}.swagger-ui .pb7{padding-bottom:16rem}.swagger-ui .pt0{padding-top:0}.swagger-ui .pt1{padding-top:.25rem}.swagger-ui .pt2{padding-top:.5rem}.swagger-ui .pt3{padding-top:1rem}.swagger-ui .pt4{padding-top:2rem}.swagger-ui .pt5{padding-top:4rem}.swagger-ui .pt6{padding-top:8rem}.swagger-ui .pt7{padding-top:16rem}.swagger-ui .pv0{padding-top:0;padding-bottom:0}.swagger-ui .pv1{padding-top:.25rem;padding-bottom:.25rem}.swagger-ui .pv2{padding-top:.5rem;padding-bottom:.5rem}.swagger-ui .pv3{padding-top:1rem;padding-bottom:1rem}.swagger-ui .pv4{padding-top:2rem;padding-bottom:2rem}.swagger-ui .pv5{padding-top:4rem;padding-bottom:4rem}.swagger-ui .pv6{padding-top:8rem;padding-bottom:8rem}.swagger-ui .pv7{padding-top:16rem;padding-bottom:16rem}.swagger-ui .ph0{padding-left:0;padding-right:0}.swagger-ui .ph1{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0{margin:0}.swagger-ui .ma1{margin:.25rem}.swagger-ui .ma2{margin:.5rem}.swagger-ui .ma3{margin:1rem}.swagger-ui .ma4{margin:2rem}.swagger-ui .ma5{margin:4rem}.swagger-ui .ma6{margin:8rem}.swagger-ui .ma7{margin:16rem}.swagger-ui .ml0{margin-left:0}.swagger-ui .ml1{margin-left:.25rem}.swagger-ui .ml2{margin-left:.5rem}.swagger-ui .ml3{margin-left:1rem}.swagger-ui .ml4{margin-left:2rem}.swagger-ui .ml5{margin-left:4rem}.swagger-ui .ml6{margin-left:8rem}.swagger-ui .ml7{margin-left:16rem}.swagger-ui .mr0{margin-right:0}.swagger-ui .mr1{margin-right:.25rem}.swagger-ui .mr2{margin-right:.5rem}.swagger-ui .mr3{margin-right:1rem}.swagger-ui .mr4{margin-right:2rem}.swagger-ui .mr5{margin-right:4rem}.swagger-ui .mr6{margin-right:8rem}.swagger-ui .mr7{margin-right:16rem}.swagger-ui .mb0{margin-bottom:0}.swagger-ui .mb1{margin-bottom:.25rem}.swagger-ui .mb2{margin-bottom:.5rem}.swagger-ui .mb3{margin-bottom:1rem}.swagger-ui .mb4{margin-bottom:2rem}.swagger-ui .mb5{margin-bottom:4rem}.swagger-ui .mb6{margin-bottom:8rem}.swagger-ui .mb7{margin-bottom:16rem}.swagger-ui .mt0{margin-top:0}.swagger-ui .mt1{margin-top:.25rem}.swagger-ui .mt2{margin-top:.5rem}.swagger-ui .mt3{margin-top:1rem}.swagger-ui .mt4{margin-top:2rem}.swagger-ui .mt5{margin-top:4rem}.swagger-ui .mt6{margin-top:8rem}.swagger-ui .mt7{margin-top:16rem}.swagger-ui .mv0{margin-top:0;margin-bottom:0}.swagger-ui .mv1{margin-top:.25rem;margin-bottom:.25rem}.swagger-ui .mv2{margin-top:.5rem;margin-bottom:.5rem}.swagger-ui .mv3{margin-top:1rem;margin-bottom:1rem}.swagger-ui .mv4{margin-top:2rem;margin-bottom:2rem}.swagger-ui .mv5{margin-top:4rem;margin-bottom:4rem}.swagger-ui .mv6{margin-top:8rem;margin-bottom:8rem}.swagger-ui .mv7{margin-top:16rem;margin-bottom:16rem}.swagger-ui .mh0{margin-left:0;margin-right:0}.swagger-ui .mh1{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7{margin-left:16rem;margin-right:16rem}@media screen and (min-width:30em){.swagger-ui .pa0-ns{padding:0}.swagger-ui .pa1-ns{padding:.25rem}.swagger-ui .pa2-ns{padding:.5rem}.swagger-ui .pa3-ns{padding:1rem}.swagger-ui .pa4-ns{padding:2rem}.swagger-ui .pa5-ns{padding:4rem}.swagger-ui .pa6-ns{padding:8rem}.swagger-ui .pa7-ns{padding:16rem}.swagger-ui .pl0-ns{padding-left:0}.swagger-ui .pl1-ns{padding-left:.25rem}.swagger-ui .pl2-ns{padding-left:.5rem}.swagger-ui .pl3-ns{padding-left:1rem}.swagger-ui .pl4-ns{padding-left:2rem}.swagger-ui .pl5-ns{padding-left:4rem}.swagger-ui .pl6-ns{padding-left:8rem}.swagger-ui .pl7-ns{padding-left:16rem}.swagger-ui .pr0-ns{padding-right:0}.swagger-ui .pr1-ns{padding-right:.25rem}.swagger-ui .pr2-ns{padding-right:.5rem}.swagger-ui .pr3-ns{padding-right:1rem}.swagger-ui .pr4-ns{padding-right:2rem}.swagger-ui .pr5-ns{padding-right:4rem}.swagger-ui .pr6-ns{padding-right:8rem}.swagger-ui .pr7-ns{padding-right:16rem}.swagger-ui .pb0-ns{padding-bottom:0}.swagger-ui .pb1-ns{padding-bottom:.25rem}.swagger-ui .pb2-ns{padding-bottom:.5rem}.swagger-ui .pb3-ns{padding-bottom:1rem}.swagger-ui .pb4-ns{padding-bottom:2rem}.swagger-ui .pb5-ns{padding-bottom:4rem}.swagger-ui .pb6-ns{padding-bottom:8rem}.swagger-ui .pb7-ns{padding-bottom:16rem}.swagger-ui .pt0-ns{padding-top:0}.swagger-ui .pt1-ns{padding-top:.25rem}.swagger-ui .pt2-ns{padding-top:.5rem}.swagger-ui .pt3-ns{padding-top:1rem}.swagger-ui .pt4-ns{padding-top:2rem}.swagger-ui .pt5-ns{padding-top:4rem}.swagger-ui .pt6-ns{padding-top:8rem}.swagger-ui .pt7-ns{padding-top:16rem}.swagger-ui .pv0-ns{padding-top:0;padding-bottom:0}.swagger-ui .pv1-ns{padding-top:.25rem;padding-bottom:.25rem}.swagger-ui .pv2-ns{padding-top:.5rem;padding-bottom:.5rem}.swagger-ui .pv3-ns{padding-top:1rem;padding-bottom:1rem}.swagger-ui .pv4-ns{padding-top:2rem;padding-bottom:2rem}.swagger-ui .pv5-ns{padding-top:4rem;padding-bottom:4rem}.swagger-ui .pv6-ns{padding-top:8rem;padding-bottom:8rem}.swagger-ui .pv7-ns{padding-top:16rem;padding-bottom:16rem}.swagger-ui .ph0-ns{padding-left:0;padding-right:0}.swagger-ui .ph1-ns{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-ns{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-ns{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-ns{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-ns{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-ns{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-ns{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-ns{margin:0}.swagger-ui .ma1-ns{margin:.25rem}.swagger-ui .ma2-ns{margin:.5rem}.swagger-ui .ma3-ns{margin:1rem}.swagger-ui .ma4-ns{margin:2rem}.swagger-ui .ma5-ns{margin:4rem}.swagger-ui .ma6-ns{margin:8rem}.swagger-ui .ma7-ns{margin:16rem}.swagger-ui .ml0-ns{margin-left:0}.swagger-ui .ml1-ns{margin-left:.25rem}.swagger-ui .ml2-ns{margin-left:.5rem}.swagger-ui .ml3-ns{margin-left:1rem}.swagger-ui .ml4-ns{margin-left:2rem}.swagger-ui .ml5-ns{margin-left:4rem}.swagger-ui .ml6-ns{margin-left:8rem}.swagger-ui .ml7-ns{margin-left:16rem}.swagger-ui .mr0-ns{margin-right:0}.swagger-ui .mr1-ns{margin-right:.25rem}.swagger-ui .mr2-ns{margin-right:.5rem}.swagger-ui .mr3-ns{margin-right:1rem}.swagger-ui .mr4-ns{margin-right:2rem}.swagger-ui .mr5-ns{margin-right:4rem}.swagger-ui .mr6-ns{margin-right:8rem}.swagger-ui .mr7-ns{margin-right:16rem}.swagger-ui .mb0-ns{margin-bottom:0}.swagger-ui .mb1-ns{margin-bottom:.25rem}.swagger-ui .mb2-ns{margin-bottom:.5rem}.swagger-ui .mb3-ns{margin-bottom:1rem}.swagger-ui .mb4-ns{margin-bottom:2rem}.swagger-ui .mb5-ns{margin-bottom:4rem}.swagger-ui .mb6-ns{margin-bottom:8rem}.swagger-ui .mb7-ns{margin-bottom:16rem}.swagger-ui .mt0-ns{margin-top:0}.swagger-ui .mt1-ns{margin-top:.25rem}.swagger-ui .mt2-ns{margin-top:.5rem}.swagger-ui .mt3-ns{margin-top:1rem}.swagger-ui .mt4-ns{margin-top:2rem}.swagger-ui .mt5-ns{margin-top:4rem}.swagger-ui .mt6-ns{margin-top:8rem}.swagger-ui .mt7-ns{margin-top:16rem}.swagger-ui .mv0-ns{margin-top:0;margin-bottom:0}.swagger-ui .mv1-ns{margin-top:.25rem;margin-bottom:.25rem}.swagger-ui .mv2-ns{margin-top:.5rem;margin-bottom:.5rem}.swagger-ui .mv3-ns{margin-top:1rem;margin-bottom:1rem}.swagger-ui .mv4-ns{margin-top:2rem;margin-bottom:2rem}.swagger-ui .mv5-ns{margin-top:4rem;margin-bottom:4rem}.swagger-ui .mv6-ns{margin-top:8rem;margin-bottom:8rem}.swagger-ui .mv7-ns{margin-top:16rem;margin-bottom:16rem}.swagger-ui .mh0-ns{margin-left:0;margin-right:0}.swagger-ui .mh1-ns{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-ns{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-ns{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-ns{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-ns{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-ns{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-ns{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .pa0-m{padding:0}.swagger-ui .pa1-m{padding:.25rem}.swagger-ui .pa2-m{padding:.5rem}.swagger-ui .pa3-m{padding:1rem}.swagger-ui .pa4-m{padding:2rem}.swagger-ui .pa5-m{padding:4rem}.swagger-ui .pa6-m{padding:8rem}.swagger-ui .pa7-m{padding:16rem}.swagger-ui .pl0-m{padding-left:0}.swagger-ui .pl1-m{padding-left:.25rem}.swagger-ui .pl2-m{padding-left:.5rem}.swagger-ui .pl3-m{padding-left:1rem}.swagger-ui .pl4-m{padding-left:2rem}.swagger-ui .pl5-m{padding-left:4rem}.swagger-ui .pl6-m{padding-left:8rem}.swagger-ui .pl7-m{padding-left:16rem}.swagger-ui .pr0-m{padding-right:0}.swagger-ui .pr1-m{padding-right:.25rem}.swagger-ui .pr2-m{padding-right:.5rem}.swagger-ui .pr3-m{padding-right:1rem}.swagger-ui .pr4-m{padding-right:2rem}.swagger-ui .pr5-m{padding-right:4rem}.swagger-ui .pr6-m{padding-right:8rem}.swagger-ui .pr7-m{padding-right:16rem}.swagger-ui .pb0-m{padding-bottom:0}.swagger-ui .pb1-m{padding-bottom:.25rem}.swagger-ui .pb2-m{padding-bottom:.5rem}.swagger-ui .pb3-m{padding-bottom:1rem}.swagger-ui .pb4-m{padding-bottom:2rem}.swagger-ui .pb5-m{padding-bottom:4rem}.swagger-ui .pb6-m{padding-bottom:8rem}.swagger-ui .pb7-m{padding-bottom:16rem}.swagger-ui .pt0-m{padding-top:0}.swagger-ui .pt1-m{padding-top:.25rem}.swagger-ui .pt2-m{padding-top:.5rem}.swagger-ui .pt3-m{padding-top:1rem}.swagger-ui .pt4-m{padding-top:2rem}.swagger-ui .pt5-m{padding-top:4rem}.swagger-ui .pt6-m{padding-top:8rem}.swagger-ui .pt7-m{padding-top:16rem}.swagger-ui .pv0-m{padding-top:0;padding-bottom:0}.swagger-ui .pv1-m{padding-top:.25rem;padding-bottom:.25rem}.swagger-ui .pv2-m{padding-top:.5rem;padding-bottom:.5rem}.swagger-ui .pv3-m{padding-top:1rem;padding-bottom:1rem}.swagger-ui .pv4-m{padding-top:2rem;padding-bottom:2rem}.swagger-ui .pv5-m{padding-top:4rem;padding-bottom:4rem}.swagger-ui .pv6-m{padding-top:8rem;padding-bottom:8rem}.swagger-ui .pv7-m{padding-top:16rem;padding-bottom:16rem}.swagger-ui .ph0-m{padding-left:0;padding-right:0}.swagger-ui .ph1-m{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-m{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-m{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-m{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-m{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-m{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-m{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-m{margin:0}.swagger-ui .ma1-m{margin:.25rem}.swagger-ui .ma2-m{margin:.5rem}.swagger-ui .ma3-m{margin:1rem}.swagger-ui .ma4-m{margin:2rem}.swagger-ui .ma5-m{margin:4rem}.swagger-ui .ma6-m{margin:8rem}.swagger-ui .ma7-m{margin:16rem}.swagger-ui .ml0-m{margin-left:0}.swagger-ui .ml1-m{margin-left:.25rem}.swagger-ui .ml2-m{margin-left:.5rem}.swagger-ui .ml3-m{margin-left:1rem}.swagger-ui .ml4-m{margin-left:2rem}.swagger-ui .ml5-m{margin-left:4rem}.swagger-ui .ml6-m{margin-left:8rem}.swagger-ui .ml7-m{margin-left:16rem}.swagger-ui .mr0-m{margin-right:0}.swagger-ui .mr1-m{margin-right:.25rem}.swagger-ui .mr2-m{margin-right:.5rem}.swagger-ui .mr3-m{margin-right:1rem}.swagger-ui .mr4-m{margin-right:2rem}.swagger-ui .mr5-m{margin-right:4rem}.swagger-ui .mr6-m{margin-right:8rem}.swagger-ui .mr7-m{margin-right:16rem}.swagger-ui .mb0-m{margin-bottom:0}.swagger-ui .mb1-m{margin-bottom:.25rem}.swagger-ui .mb2-m{margin-bottom:.5rem}.swagger-ui .mb3-m{margin-bottom:1rem}.swagger-ui .mb4-m{margin-bottom:2rem}.swagger-ui .mb5-m{margin-bottom:4rem}.swagger-ui .mb6-m{margin-bottom:8rem}.swagger-ui .mb7-m{margin-bottom:16rem}.swagger-ui .mt0-m{margin-top:0}.swagger-ui .mt1-m{margin-top:.25rem}.swagger-ui .mt2-m{margin-top:.5rem}.swagger-ui .mt3-m{margin-top:1rem}.swagger-ui .mt4-m{margin-top:2rem}.swagger-ui .mt5-m{margin-top:4rem}.swagger-ui .mt6-m{margin-top:8rem}.swagger-ui .mt7-m{margin-top:16rem}.swagger-ui .mv0-m{margin-top:0;margin-bottom:0}.swagger-ui .mv1-m{margin-top:.25rem;margin-bottom:.25rem}.swagger-ui .mv2-m{margin-top:.5rem;margin-bottom:.5rem}.swagger-ui .mv3-m{margin-top:1rem;margin-bottom:1rem}.swagger-ui .mv4-m{margin-top:2rem;margin-bottom:2rem}.swagger-ui .mv5-m{margin-top:4rem;margin-bottom:4rem}.swagger-ui .mv6-m{margin-top:8rem;margin-bottom:8rem}.swagger-ui .mv7-m{margin-top:16rem;margin-bottom:16rem}.swagger-ui .mh0-m{margin-left:0;margin-right:0}.swagger-ui .mh1-m{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-m{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-m{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-m{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-m{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-m{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-m{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:60em){.swagger-ui .pa0-l{padding:0}.swagger-ui .pa1-l{padding:.25rem}.swagger-ui .pa2-l{padding:.5rem}.swagger-ui .pa3-l{padding:1rem}.swagger-ui .pa4-l{padding:2rem}.swagger-ui .pa5-l{padding:4rem}.swagger-ui .pa6-l{padding:8rem}.swagger-ui .pa7-l{padding:16rem}.swagger-ui .pl0-l{padding-left:0}.swagger-ui .pl1-l{padding-left:.25rem}.swagger-ui .pl2-l{padding-left:.5rem}.swagger-ui .pl3-l{padding-left:1rem}.swagger-ui .pl4-l{padding-left:2rem}.swagger-ui .pl5-l{padding-left:4rem}.swagger-ui .pl6-l{padding-left:8rem}.swagger-ui .pl7-l{padding-left:16rem}.swagger-ui .pr0-l{padding-right:0}.swagger-ui .pr1-l{padding-right:.25rem}.swagger-ui .pr2-l{padding-right:.5rem}.swagger-ui .pr3-l{padding-right:1rem}.swagger-ui .pr4-l{padding-right:2rem}.swagger-ui .pr5-l{padding-right:4rem}.swagger-ui .pr6-l{padding-right:8rem}.swagger-ui .pr7-l{padding-right:16rem}.swagger-ui .pb0-l{padding-bottom:0}.swagger-ui .pb1-l{padding-bottom:.25rem}.swagger-ui .pb2-l{padding-bottom:.5rem}.swagger-ui .pb3-l{padding-bottom:1rem}.swagger-ui .pb4-l{padding-bottom:2rem}.swagger-ui .pb5-l{padding-bottom:4rem}.swagger-ui .pb6-l{padding-bottom:8rem}.swagger-ui .pb7-l{padding-bottom:16rem}.swagger-ui .pt0-l{padding-top:0}.swagger-ui .pt1-l{padding-top:.25rem}.swagger-ui .pt2-l{padding-top:.5rem}.swagger-ui .pt3-l{padding-top:1rem}.swagger-ui .pt4-l{padding-top:2rem}.swagger-ui .pt5-l{padding-top:4rem}.swagger-ui .pt6-l{padding-top:8rem}.swagger-ui .pt7-l{padding-top:16rem}.swagger-ui .pv0-l{padding-top:0;padding-bottom:0}.swagger-ui .pv1-l{padding-top:.25rem;padding-bottom:.25rem}.swagger-ui .pv2-l{padding-top:.5rem;padding-bottom:.5rem}.swagger-ui .pv3-l{padding-top:1rem;padding-bottom:1rem}.swagger-ui .pv4-l{padding-top:2rem;padding-bottom:2rem}.swagger-ui .pv5-l{padding-top:4rem;padding-bottom:4rem}.swagger-ui .pv6-l{padding-top:8rem;padding-bottom:8rem}.swagger-ui .pv7-l{padding-top:16rem;padding-bottom:16rem}.swagger-ui .ph0-l{padding-left:0;padding-right:0}.swagger-ui .ph1-l{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-l{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-l{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-l{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-l{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-l{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-l{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-l{margin:0}.swagger-ui .ma1-l{margin:.25rem}.swagger-ui .ma2-l{margin:.5rem}.swagger-ui .ma3-l{margin:1rem}.swagger-ui .ma4-l{margin:2rem}.swagger-ui .ma5-l{margin:4rem}.swagger-ui .ma6-l{margin:8rem}.swagger-ui .ma7-l{margin:16rem}.swagger-ui .ml0-l{margin-left:0}.swagger-ui .ml1-l{margin-left:.25rem}.swagger-ui .ml2-l{margin-left:.5rem}.swagger-ui .ml3-l{margin-left:1rem}.swagger-ui .ml4-l{margin-left:2rem}.swagger-ui .ml5-l{margin-left:4rem}.swagger-ui .ml6-l{margin-left:8rem}.swagger-ui .ml7-l{margin-left:16rem}.swagger-ui .mr0-l{margin-right:0}.swagger-ui .mr1-l{margin-right:.25rem}.swagger-ui .mr2-l{margin-right:.5rem}.swagger-ui .mr3-l{margin-right:1rem}.swagger-ui .mr4-l{margin-right:2rem}.swagger-ui .mr5-l{margin-right:4rem}.swagger-ui .mr6-l{margin-right:8rem}.swagger-ui .mr7-l{margin-right:16rem}.swagger-ui .mb0-l{margin-bottom:0}.swagger-ui .mb1-l{margin-bottom:.25rem}.swagger-ui .mb2-l{margin-bottom:.5rem}.swagger-ui .mb3-l{margin-bottom:1rem}.swagger-ui .mb4-l{margin-bottom:2rem}.swagger-ui .mb5-l{margin-bottom:4rem}.swagger-ui .mb6-l{margin-bottom:8rem}.swagger-ui .mb7-l{margin-bottom:16rem}.swagger-ui .mt0-l{margin-top:0}.swagger-ui .mt1-l{margin-top:.25rem}.swagger-ui .mt2-l{margin-top:.5rem}.swagger-ui .mt3-l{margin-top:1rem}.swagger-ui .mt4-l{margin-top:2rem}.swagger-ui .mt5-l{margin-top:4rem}.swagger-ui .mt6-l{margin-top:8rem}.swagger-ui .mt7-l{margin-top:16rem}.swagger-ui .mv0-l{margin-top:0;margin-bottom:0}.swagger-ui .mv1-l{margin-top:.25rem;margin-bottom:.25rem}.swagger-ui .mv2-l{margin-top:.5rem;margin-bottom:.5rem}.swagger-ui .mv3-l{margin-top:1rem;margin-bottom:1rem}.swagger-ui .mv4-l{margin-top:2rem;margin-bottom:2rem}.swagger-ui .mv5-l{margin-top:4rem;margin-bottom:4rem}.swagger-ui .mv6-l{margin-top:8rem;margin-bottom:8rem}.swagger-ui .mv7-l{margin-top:16rem;margin-bottom:16rem}.swagger-ui .mh0-l{margin-left:0;margin-right:0}.swagger-ui .mh1-l{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-l{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-l{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-l{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-l{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-l{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-l{margin-left:16rem;margin-right:16rem}}.swagger-ui .na1{margin:-.25rem}.swagger-ui .na2{margin:-.5rem}.swagger-ui .na3{margin:-1rem}.swagger-ui .na4{margin:-2rem}.swagger-ui .na5{margin:-4rem}.swagger-ui .na6{margin:-8rem}.swagger-ui .na7{margin:-16rem}.swagger-ui .nl1{margin-left:-.25rem}.swagger-ui .nl2{margin-left:-.5rem}.swagger-ui .nl3{margin-left:-1rem}.swagger-ui .nl4{margin-left:-2rem}.swagger-ui .nl5{margin-left:-4rem}.swagger-ui .nl6{margin-left:-8rem}.swagger-ui .nl7{margin-left:-16rem}.swagger-ui .nr1{margin-right:-.25rem}.swagger-ui .nr2{margin-right:-.5rem}.swagger-ui .nr3{margin-right:-1rem}.swagger-ui .nr4{margin-right:-2rem}.swagger-ui .nr5{margin-right:-4rem}.swagger-ui .nr6{margin-right:-8rem}.swagger-ui .nr7{margin-right:-16rem}.swagger-ui .nb1{margin-bottom:-.25rem}.swagger-ui .nb2{margin-bottom:-.5rem}.swagger-ui .nb3{margin-bottom:-1rem}.swagger-ui .nb4{margin-bottom:-2rem}.swagger-ui .nb5{margin-bottom:-4rem}.swagger-ui .nb6{margin-bottom:-8rem}.swagger-ui .nb7{margin-bottom:-16rem}.swagger-ui .nt1{margin-top:-.25rem}.swagger-ui .nt2{margin-top:-.5rem}.swagger-ui .nt3{margin-top:-1rem}.swagger-ui .nt4{margin-top:-2rem}.swagger-ui .nt5{margin-top:-4rem}.swagger-ui .nt6{margin-top:-8rem}.swagger-ui .nt7{margin-top:-16rem}@media screen and (min-width:30em){.swagger-ui .na1-ns{margin:-.25rem}.swagger-ui .na2-ns{margin:-.5rem}.swagger-ui .na3-ns{margin:-1rem}.swagger-ui .na4-ns{margin:-2rem}.swagger-ui .na5-ns{margin:-4rem}.swagger-ui .na6-ns{margin:-8rem}.swagger-ui .na7-ns{margin:-16rem}.swagger-ui .nl1-ns{margin-left:-.25rem}.swagger-ui .nl2-ns{margin-left:-.5rem}.swagger-ui .nl3-ns{margin-left:-1rem}.swagger-ui .nl4-ns{margin-left:-2rem}.swagger-ui .nl5-ns{margin-left:-4rem}.swagger-ui .nl6-ns{margin-left:-8rem}.swagger-ui .nl7-ns{margin-left:-16rem}.swagger-ui .nr1-ns{margin-right:-.25rem}.swagger-ui .nr2-ns{margin-right:-.5rem}.swagger-ui .nr3-ns{margin-right:-1rem}.swagger-ui .nr4-ns{margin-right:-2rem}.swagger-ui .nr5-ns{margin-right:-4rem}.swagger-ui .nr6-ns{margin-right:-8rem}.swagger-ui .nr7-ns{margin-right:-16rem}.swagger-ui .nb1-ns{margin-bottom:-.25rem}.swagger-ui .nb2-ns{margin-bottom:-.5rem}.swagger-ui .nb3-ns{margin-bottom:-1rem}.swagger-ui .nb4-ns{margin-bottom:-2rem}.swagger-ui .nb5-ns{margin-bottom:-4rem}.swagger-ui .nb6-ns{margin-bottom:-8rem}.swagger-ui .nb7-ns{margin-bottom:-16rem}.swagger-ui .nt1-ns{margin-top:-.25rem}.swagger-ui .nt2-ns{margin-top:-.5rem}.swagger-ui .nt3-ns{margin-top:-1rem}.swagger-ui .nt4-ns{margin-top:-2rem}.swagger-ui .nt5-ns{margin-top:-4rem}.swagger-ui .nt6-ns{margin-top:-8rem}.swagger-ui .nt7-ns{margin-top:-16rem}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .na1-m{margin:-.25rem}.swagger-ui .na2-m{margin:-.5rem}.swagger-ui .na3-m{margin:-1rem}.swagger-ui .na4-m{margin:-2rem}.swagger-ui .na5-m{margin:-4rem}.swagger-ui .na6-m{margin:-8rem}.swagger-ui .na7-m{margin:-16rem}.swagger-ui .nl1-m{margin-left:-.25rem}.swagger-ui .nl2-m{margin-left:-.5rem}.swagger-ui .nl3-m{margin-left:-1rem}.swagger-ui .nl4-m{margin-left:-2rem}.swagger-ui .nl5-m{margin-left:-4rem}.swagger-ui .nl6-m{margin-left:-8rem}.swagger-ui .nl7-m{margin-left:-16rem}.swagger-ui .nr1-m{margin-right:-.25rem}.swagger-ui .nr2-m{margin-right:-.5rem}.swagger-ui .nr3-m{margin-right:-1rem}.swagger-ui .nr4-m{margin-right:-2rem}.swagger-ui .nr5-m{margin-right:-4rem}.swagger-ui .nr6-m{margin-right:-8rem}.swagger-ui .nr7-m{margin-right:-16rem}.swagger-ui .nb1-m{margin-bottom:-.25rem}.swagger-ui .nb2-m{margin-bottom:-.5rem}.swagger-ui .nb3-m{margin-bottom:-1rem}.swagger-ui .nb4-m{margin-bottom:-2rem}.swagger-ui .nb5-m{margin-bottom:-4rem}.swagger-ui .nb6-m{margin-bottom:-8rem}.swagger-ui .nb7-m{margin-bottom:-16rem}.swagger-ui .nt1-m{margin-top:-.25rem}.swagger-ui .nt2-m{margin-top:-.5rem}.swagger-ui .nt3-m{margin-top:-1rem}.swagger-ui .nt4-m{margin-top:-2rem}.swagger-ui .nt5-m{margin-top:-4rem}.swagger-ui .nt6-m{margin-top:-8rem}.swagger-ui .nt7-m{margin-top:-16rem}}@media screen and (min-width:60em){.swagger-ui .na1-l{margin:-.25rem}.swagger-ui .na2-l{margin:-.5rem}.swagger-ui .na3-l{margin:-1rem}.swagger-ui .na4-l{margin:-2rem}.swagger-ui .na5-l{margin:-4rem}.swagger-ui .na6-l{margin:-8rem}.swagger-ui .na7-l{margin:-16rem}.swagger-ui .nl1-l{margin-left:-.25rem}.swagger-ui .nl2-l{margin-left:-.5rem}.swagger-ui .nl3-l{margin-left:-1rem}.swagger-ui .nl4-l{margin-left:-2rem}.swagger-ui .nl5-l{margin-left:-4rem}.swagger-ui .nl6-l{margin-left:-8rem}.swagger-ui .nl7-l{margin-left:-16rem}.swagger-ui .nr1-l{margin-right:-.25rem}.swagger-ui .nr2-l{margin-right:-.5rem}.swagger-ui .nr3-l{margin-right:-1rem}.swagger-ui .nr4-l{margin-right:-2rem}.swagger-ui .nr5-l{margin-right:-4rem}.swagger-ui .nr6-l{margin-right:-8rem}.swagger-ui .nr7-l{margin-right:-16rem}.swagger-ui .nb1-l{margin-bottom:-.25rem}.swagger-ui .nb2-l{margin-bottom:-.5rem}.swagger-ui .nb3-l{margin-bottom:-1rem}.swagger-ui .nb4-l{margin-bottom:-2rem}.swagger-ui .nb5-l{margin-bottom:-4rem}.swagger-ui .nb6-l{margin-bottom:-8rem}.swagger-ui .nb7-l{margin-bottom:-16rem}.swagger-ui .nt1-l{margin-top:-.25rem}.swagger-ui .nt2-l{margin-top:-.5rem}.swagger-ui .nt3-l{margin-top:-1rem}.swagger-ui .nt4-l{margin-top:-2rem}.swagger-ui .nt5-l{margin-top:-4rem}.swagger-ui .nt6-l{margin-top:-8rem}.swagger-ui .nt7-l{margin-top:-16rem}}.swagger-ui .collapse{border-collapse:collapse;border-spacing:0}.swagger-ui .striped--light-silver:nth-child(odd){background-color:#aaa}.swagger-ui .striped--moon-gray:nth-child(odd){background-color:#ccc}.swagger-ui .striped--light-gray:nth-child(odd){background-color:#eee}.swagger-ui .striped--near-white:nth-child(odd){background-color:#f4f4f4}.swagger-ui .stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.swagger-ui .stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.swagger-ui .strike{text-decoration:line-through}.swagger-ui .underline{text-decoration:underline}.swagger-ui .no-underline{text-decoration:none}@media screen and (min-width:30em){.swagger-ui .strike-ns{text-decoration:line-through}.swagger-ui .underline-ns{text-decoration:underline}.swagger-ui .no-underline-ns{text-decoration:none}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .strike-m{text-decoration:line-through}.swagger-ui .underline-m{text-decoration:underline}.swagger-ui .no-underline-m{text-decoration:none}}@media screen and (min-width:60em){.swagger-ui .strike-l{text-decoration:line-through}.swagger-ui .underline-l{text-decoration:underline}.swagger-ui .no-underline-l{text-decoration:none}}.swagger-ui .tl{text-align:left}.swagger-ui .tr{text-align:right}.swagger-ui .tc{text-align:center}@media screen and (min-width:30em){.swagger-ui .tl-ns{text-align:left}.swagger-ui .tr-ns{text-align:right}.swagger-ui .tc-ns{text-align:center}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .tl-m{text-align:left}.swagger-ui .tr-m{text-align:right}.swagger-ui .tc-m{text-align:center}}@media screen and (min-width:60em){.swagger-ui .tl-l{text-align:left}.swagger-ui .tr-l{text-align:right}.swagger-ui .tc-l{text-align:center}}.swagger-ui .ttc{text-transform:capitalize}.swagger-ui .ttl{text-transform:lowercase}.swagger-ui .ttu{text-transform:uppercase}.swagger-ui .ttn{text-transform:none}@media screen and (min-width:30em){.swagger-ui .ttc-ns{text-transform:capitalize}.swagger-ui .ttl-ns{text-transform:lowercase}.swagger-ui .ttu-ns{text-transform:uppercase}.swagger-ui .ttn-ns{text-transform:none}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .ttc-m{text-transform:capitalize}.swagger-ui .ttl-m{text-transform:lowercase}.swagger-ui .ttu-m{text-transform:uppercase}.swagger-ui .ttn-m{text-transform:none}}@media screen and (min-width:60em){.swagger-ui .ttc-l{text-transform:capitalize}.swagger-ui .ttl-l{text-transform:lowercase}.swagger-ui .ttu-l{text-transform:uppercase}.swagger-ui .ttn-l{text-transform:none}}.swagger-ui .f-6,.swagger-ui .f-headline{font-size:6rem}.swagger-ui .f-5,.swagger-ui .f-subheadline{font-size:5rem}.swagger-ui .f1{font-size:3rem}.swagger-ui .f2{font-size:2.25rem}.swagger-ui .f3{font-size:1.5rem}.swagger-ui .f4{font-size:1.25rem}.swagger-ui .f5{font-size:1rem}.swagger-ui .f6{font-size:.875rem}.swagger-ui .f7{font-size:.75rem}@media screen and (min-width:30em){.swagger-ui .f-6-ns,.swagger-ui .f-headline-ns{font-size:6rem}.swagger-ui .f-5-ns,.swagger-ui .f-subheadline-ns{font-size:5rem}.swagger-ui .f1-ns{font-size:3rem}.swagger-ui .f2-ns{font-size:2.25rem}.swagger-ui .f3-ns{font-size:1.5rem}.swagger-ui .f4-ns{font-size:1.25rem}.swagger-ui .f5-ns{font-size:1rem}.swagger-ui .f6-ns{font-size:.875rem}.swagger-ui .f7-ns{font-size:.75rem}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .f-6-m,.swagger-ui .f-headline-m{font-size:6rem}.swagger-ui .f-5-m,.swagger-ui .f-subheadline-m{font-size:5rem}.swagger-ui .f1-m{font-size:3rem}.swagger-ui .f2-m{font-size:2.25rem}.swagger-ui .f3-m{font-size:1.5rem}.swagger-ui .f4-m{font-size:1.25rem}.swagger-ui .f5-m{font-size:1rem}.swagger-ui .f6-m{font-size:.875rem}.swagger-ui .f7-m{font-size:.75rem}}@media screen and (min-width:60em){.swagger-ui .f-6-l,.swagger-ui .f-headline-l{font-size:6rem}.swagger-ui .f-5-l,.swagger-ui .f-subheadline-l{font-size:5rem}.swagger-ui .f1-l{font-size:3rem}.swagger-ui .f2-l{font-size:2.25rem}.swagger-ui .f3-l{font-size:1.5rem}.swagger-ui .f4-l{font-size:1.25rem}.swagger-ui .f5-l{font-size:1rem}.swagger-ui .f6-l{font-size:.875rem}.swagger-ui .f7-l{font-size:.75rem}}.swagger-ui .measure{max-width:30em}.swagger-ui .measure-wide{max-width:34em}.swagger-ui .measure-narrow{max-width:20em}.swagger-ui .indent{text-indent:1em;margin-top:0;margin-bottom:0}.swagger-ui .small-caps{font-variant:small-caps}.swagger-ui .truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media screen and (min-width:30em){.swagger-ui .measure-ns{max-width:30em}.swagger-ui .measure-wide-ns{max-width:34em}.swagger-ui .measure-narrow-ns{max-width:20em}.swagger-ui .indent-ns{text-indent:1em;margin-top:0;margin-bottom:0}.swagger-ui .small-caps-ns{font-variant:small-caps}.swagger-ui .truncate-ns{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .measure-m{max-width:30em}.swagger-ui .measure-wide-m{max-width:34em}.swagger-ui .measure-narrow-m{max-width:20em}.swagger-ui .indent-m{text-indent:1em;margin-top:0;margin-bottom:0}.swagger-ui .small-caps-m{font-variant:small-caps}.swagger-ui .truncate-m{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}@media screen and (min-width:60em){.swagger-ui .measure-l{max-width:30em}.swagger-ui .measure-wide-l{max-width:34em}.swagger-ui .measure-narrow-l{max-width:20em}.swagger-ui .indent-l{text-indent:1em;margin-top:0;margin-bottom:0}.swagger-ui .small-caps-l{font-variant:small-caps}.swagger-ui .truncate-l{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}.swagger-ui .overflow-container{overflow-y:scroll}.swagger-ui .center{margin-right:auto;margin-left:auto}.swagger-ui .mr-auto{margin-right:auto}.swagger-ui .ml-auto{margin-left:auto}@media screen and (min-width:30em){.swagger-ui .center-ns{margin-right:auto;margin-left:auto}.swagger-ui .mr-auto-ns{margin-right:auto}.swagger-ui .ml-auto-ns{margin-left:auto}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .center-m{margin-right:auto;margin-left:auto}.swagger-ui .mr-auto-m{margin-right:auto}.swagger-ui .ml-auto-m{margin-left:auto}}@media screen and (min-width:60em){.swagger-ui .center-l{margin-right:auto;margin-left:auto}.swagger-ui .mr-auto-l{margin-right:auto}.swagger-ui .ml-auto-l{margin-left:auto}}.swagger-ui .clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}@media screen and (min-width:30em){.swagger-ui .clip-ns{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:60em){.swagger-ui .clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}.swagger-ui .ws-normal{white-space:normal}.swagger-ui .nowrap{white-space:nowrap}.swagger-ui .pre{white-space:pre}@media screen and (min-width:30em){.swagger-ui .ws-normal-ns{white-space:normal}.swagger-ui .nowrap-ns{white-space:nowrap}.swagger-ui .pre-ns{white-space:pre}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .ws-normal-m{white-space:normal}.swagger-ui .nowrap-m{white-space:nowrap}.swagger-ui .pre-m{white-space:pre}}@media screen and (min-width:60em){.swagger-ui .ws-normal-l{white-space:normal}.swagger-ui .nowrap-l{white-space:nowrap}.swagger-ui .pre-l{white-space:pre}}.swagger-ui .v-base{vertical-align:baseline}.swagger-ui .v-mid{vertical-align:middle}.swagger-ui .v-top{vertical-align:top}.swagger-ui .v-btm{vertical-align:bottom}@media screen and (min-width:30em){.swagger-ui .v-base-ns{vertical-align:baseline}.swagger-ui .v-mid-ns{vertical-align:middle}.swagger-ui .v-top-ns{vertical-align:top}.swagger-ui .v-btm-ns{vertical-align:bottom}}@media screen and (min-width:30em) and (max-width:60em){.swagger-ui .v-base-m{vertical-align:baseline}.swagger-ui .v-mid-m{vertical-align:middle}.swagger-ui .v-top-m{vertical-align:top}.swagger-ui .v-btm-m{vertical-align:bottom}}@media screen and (min-width:60em){.swagger-ui .v-base-l{vertical-align:baseline}.swagger-ui .v-mid-l{vertical-align:middle}.swagger-ui .v-top-l{vertical-align:top}.swagger-ui .v-btm-l{vertical-align:bottom}}.swagger-ui .dim{opacity:1}.swagger-ui .dim,.swagger-ui .dim:focus,.swagger-ui .dim:hover{-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.swagger-ui .dim:focus,.swagger-ui .dim:hover{opacity:.5}.swagger-ui .dim:active{opacity:.8;-webkit-transition:opacity .15s ease-out;transition:opacity .15s ease-out}.swagger-ui .glow,.swagger-ui .glow:focus,.swagger-ui .glow:hover{-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.swagger-ui .glow:focus,.swagger-ui .glow:hover{opacity:1}.swagger-ui .hide-child .child{opacity:0;-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.swagger-ui .hide-child:active .child,.swagger-ui .hide-child:focus .child,.swagger-ui .hide-child:hover .child{opacity:1;-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.swagger-ui .underline-hover:focus,.swagger-ui .underline-hover:hover{text-decoration:underline}.swagger-ui .grow{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-transition:-webkit-transform .25s ease-out;transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out}.swagger-ui .grow:focus,.swagger-ui .grow:hover{-webkit-transform:scale(1.05);transform:scale(1.05)}.swagger-ui .grow:active{-webkit-transform:scale(.9);transform:scale(.9)}.swagger-ui .grow-large{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-transition:-webkit-transform .25s ease-in-out;transition:-webkit-transform .25s ease-in-out;transition:transform .25s ease-in-out;transition:transform .25s ease-in-out,-webkit-transform .25s ease-in-out}.swagger-ui .grow-large:focus,.swagger-ui .grow-large:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.swagger-ui .grow-large:active{-webkit-transform:scale(.95);transform:scale(.95)}.swagger-ui .pointer:hover{cursor:pointer}.swagger-ui .shadow-hover{cursor:pointer;position:relative;-webkit-transition:all .5s cubic-bezier(.165,.84,.44,1);transition:all .5s cubic-bezier(.165,.84,.44,1)}.swagger-ui .shadow-hover:after{content:"";-webkit-box-shadow:0 0 16px 2px rgba(0,0,0,.2);box-shadow:0 0 16px 2px rgba(0,0,0,.2);border-radius:inherit;opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:-1;-webkit-transition:opacity .5s cubic-bezier(.165,.84,.44,1);transition:opacity .5s cubic-bezier(.165,.84,.44,1)}.swagger-ui .shadow-hover:focus:after,.swagger-ui .shadow-hover:hover:after{opacity:1}.swagger-ui .bg-animate,.swagger-ui .bg-animate:focus,.swagger-ui .bg-animate:hover{-webkit-transition:background-color .15s ease-in-out;transition:background-color .15s ease-in-out}.swagger-ui .z-0{z-index:0}.swagger-ui .z-1{z-index:1}.swagger-ui .z-2{z-index:2}.swagger-ui .z-3{z-index:3}.swagger-ui .z-4{z-index:4}.swagger-ui .z-5{z-index:5}.swagger-ui .z-999{z-index:999}.swagger-ui .z-9999{z-index:9999}.swagger-ui .z-max{z-index:2147483647}.swagger-ui .z-inherit{z-index:inherit}.swagger-ui .z-initial{z-index:auto}.swagger-ui .z-unset{z-index:unset}.swagger-ui .nested-copy-line-height ol,.swagger-ui .nested-copy-line-height p,.swagger-ui .nested-copy-line-height ul{line-height:1.5}.swagger-ui .nested-headline-line-height h1,.swagger-ui .nested-headline-line-height h2,.swagger-ui .nested-headline-line-height h3,.swagger-ui .nested-headline-line-height h4,.swagger-ui .nested-headline-line-height h5,.swagger-ui .nested-headline-line-height h6{line-height:1.25rem}.swagger-ui .nested-list-reset ol,.swagger-ui .nested-list-reset ul{padding-left:0;margin-left:0;list-style-type:none}.swagger-ui .nested-copy-indent p+p{text-indent:.1em;margin-top:0;margin-bottom:0}.swagger-ui .nested-copy-seperator p+p{margin-top:1.5em}.swagger-ui .nested-img img{width:100%;max-width:100%;display:block}.swagger-ui .nested-links a{color:#357edd;-webkit-transition:color .15s ease-in;transition:color .15s ease-in}.swagger-ui .nested-links a:focus,.swagger-ui .nested-links a:hover{color:#96ccff;-webkit-transition:color .15s ease-in;transition:color .15s ease-in}.swagger-ui .wrapper{width:100%;max-width:1460px;margin:0 auto;padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.swagger-ui .opblock-tag-section{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .opblock-tag{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:10px 20px 10px 10px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui .opblock-tag:hover{background:rgba(0,0,0,.02)}.swagger-ui .opblock-tag{font-size:24px;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock-tag.no-desc span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .opblock-tag svg{-webkit-transition:all .4s;transition:all .4s}.swagger-ui .opblock-tag small{font-size:14px;font-weight:400;-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 10px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameter__type{font-size:12px;padding:5px 0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .view-line-link{position:relative;top:3px;width:20px;margin:0 5px;cursor:pointer;-webkit-transition:all .5s;transition:all .5s}.swagger-ui .opblock{margin:0 0 15px;border:1px solid #000;border-radius:4px;-webkit-box-shadow:0 0 3px rgba(0,0,0,.19);box-shadow:0 0 3px rgba(0,0,0,.19)}.swagger-ui .opblock .tab-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .opblock .tab-header .tab-item{padding:0 40px;cursor:pointer}.swagger-ui .opblock .tab-header .tab-item:first-of-type{padding:0 40px 0 0}.swagger-ui .opblock .tab-header .tab-item.active h4 span{position:relative}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{position:absolute;bottom:-15px;left:50%;width:120%;height:4px;content:"";-webkit-transform:translateX(-50%);transform:translateX(-50%);background:gray}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{padding:8px 20px;min-height:50px;background:hsla(0,0%,100%,.8);-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1);box-shadow:0 1px 2px rgba(0,0,0,.1)}.swagger-ui .opblock .opblock-section-header,.swagger-ui .opblock .opblock-section-header label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock .opblock-section-header label{font-size:12px;font-weight:700;margin:0;margin-left:auto;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-section-header label span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-section-header h4{font-size:14px;-webkit-box-flex:1;-ms-flex:1;flex:1;margin:0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary-method{font-size:14px;font-weight:700;min-width:80px;padding:6px 15px;text-align:center;border-radius:3px;background:#000;text-shadow:0 1px 0 rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 3 auto;flex:0 3 auto;-webkit-box-align:center;-ms-flex-align:center;align-items:center;word-break:break-all;padding:0 10px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}@media (max-width:768px){.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:12px}}.swagger-ui .opblock .opblock-summary-operation-id .view-line-link,.swagger-ui .opblock .opblock-summary-path .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated .view-line-link{position:relative;top:2px;width:0;margin:0;cursor:pointer;-webkit-transition:all .5s;transition:all .5s}.swagger-ui .opblock .opblock-summary-operation-id:hover .view-line-link,.swagger-ui .opblock .opblock-summary-path:hover .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated:hover .view-line-link{width:18px;margin:0 5px}.swagger-ui .opblock .opblock-summary-path__deprecated{text-decoration:line-through}.swagger-ui .opblock .opblock-summary-operation-id{font-size:14px}.swagger-ui .opblock .opblock-summary-description{font-size:13px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:5px;cursor:pointer}.swagger-ui .opblock.opblock-post{border-color:#49cc90;background:rgba(73,204,144,.1)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#49cc90}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:#49cc90}.swagger-ui .opblock.opblock-put{border-color:#fca130;background:rgba(252,161,48,.1)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#fca130}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:#fca130}.swagger-ui .opblock.opblock-delete{border-color:#f93e3e;background:rgba(249,62,62,.1)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:#f93e3e}.swagger-ui .opblock.opblock-get{border-color:#61affe;background:rgba(97,175,254,.1)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#61affe}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:#61affe}.swagger-ui .opblock.opblock-patch{border-color:#50e3c2;background:rgba(80,227,194,.1)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:#50e3c2}.swagger-ui .opblock.opblock-head{border-color:#9012fe;background:rgba(144,18,254,.1)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#9012fe}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:#9012fe}.swagger-ui .opblock.opblock-options{border-color:#0d5aa7;background:rgba(13,90,167,.1)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:#0d5aa7}.swagger-ui .opblock.opblock-deprecated{opacity:.6;border-color:#ebebeb;background:hsla(0,0%,92%,.1)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:#ebebeb}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#ebebeb}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:#ebebeb}.swagger-ui .opblock .opblock-schemes{padding:8px 20px}.swagger-ui .opblock .opblock-schemes .schemes-title{padding:0 10px 0 0}.swagger-ui .filter .operation-filter-input{width:100%;margin:20px 0;padding:10px;border:2px solid #d8dde7}.swagger-ui .tab{display:-webkit-box;display:-ms-flexbox;display:flex;margin:20px 0 10px;padding:0;list-style:none}.swagger-ui .tab li{font-size:12px;min-width:100px;min-width:90px;padding:0;cursor:pointer;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .tab li:first-of-type{position:relative;padding-left:0}.swagger-ui .tab li:first-of-type:after{position:absolute;top:0;right:6px;width:1px;height:100%;content:"";background:rgba(0,0,0,.2)}.swagger-ui .tab li.active{font-weight:700}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-title_normal{font-size:12px;margin:0 0 5px;padding:15px 20px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-title_normal h4{font-size:12px;margin:0 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-external-docs-wrapper h4{padding-left:0}.swagger-ui .execute-wrapper{padding:20px;text-align:right}.swagger-ui .execute-wrapper .btn{width:100%;padding:8px 40px}.swagger-ui .body-param-options{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .body-param-options .body-param-edit{padding:10px 0}.swagger-ui .body-param-options label{padding:8px 0}.swagger-ui .body-param-options label select{margin:3px 0 0}.swagger-ui .responses-inner{padding:20px}.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{font-size:12px;margin:10px 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status .response-undocumented{font-size:11px;font-family:Source Code Pro,monospace;font-weight:600;color:#909090}.swagger-ui .response-col_links{padding-left:2em;max-width:40em;font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_links .response-undocumented{font-size:11px;font-family:Source Code Pro,monospace;font-weight:600;color:#909090}.swagger-ui .response-col_description__inner div.markdown,.swagger-ui .response-col_description__inner div.renderedMarkdown{font-size:12px;font-style:italic;display:block;margin:0;padding:10px;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .response-col_description__inner div.markdown p,.swagger-ui .response-col_description__inner div.renderedMarkdown p{margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .response-col_description__inner div.markdown a,.swagger-ui .response-col_description__inner div.renderedMarkdown a{font-family:Source Code Pro,monospace;font-weight:600;color:#89bf04;text-decoration:underline}.swagger-ui .response-col_description__inner div.markdown a:hover,.swagger-ui .response-col_description__inner div.renderedMarkdown a:hover{color:#81b10c}.swagger-ui .response-col_description__inner div.markdown th,.swagger-ui .response-col_description__inner div.renderedMarkdown th{font-family:Source Code Pro,monospace;font-weight:600;color:#fff;border-bottom:1px solid #fff}.swagger-ui .opblock-body .opblock-loading-animation{display:block;margin:3em;margin-left:auto;margin-right:auto}.swagger-ui .opblock-body pre{font-size:12px;margin:0;padding:10px;white-space:pre-wrap;word-wrap:break-word;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;border-radius:4px;background:#41444e;overflow-wrap:break-word;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .opblock-body pre span{color:#fff!important}.swagger-ui .opblock-body pre .headerline{display:block}.swagger-ui .highlight-code{position:relative}.swagger-ui .highlight-code>.microlight{overflow-y:auto;max-height:400px;min-height:6em}.swagger-ui .download-contents{position:absolute;bottom:10px;right:10px;cursor:pointer;background:#7d8293;text-align:center;padding:5px;border-radius:4px;font-family:Titillium Web,sans-serif;font-weight:600;color:#fff;font-size:14px;height:30px;width:75px}.swagger-ui .scheme-container{margin:0 0 20px;padding:30px 0;background:#fff;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.15);box-shadow:0 1px 2px 0 rgba(0,0,0,.15)}.swagger-ui .scheme-container .schemes{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .scheme-container .schemes>label{font-size:12px;font-weight:700;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scheme-container .schemes>label select{min-width:130px;text-transform:uppercase}.swagger-ui .loading-container{padding:40px 0 60px;margin-top:1em;min-height:1px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{font-size:10px;font-weight:700;position:absolute;top:50%;left:50%;content:"loading";-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-transform:uppercase;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .loading-container .loading:before{position:absolute;top:50%;left:50%;display:block;width:60px;height:60px;margin:-30px;content:"";-webkit-animation:rotation 1s infinite linear,opacity .5s;animation:rotation 1s infinite linear,opacity .5s;opacity:1;border:2px solid rgba(85,85,85,.1);border-top-color:rgba(0,0,0,.6);border-radius:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden}@-webkit-keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.swagger-ui .response-content-type{padding-top:1em}.swagger-ui .response-content-type.controls-accept-header select{border-color:green}.swagger-ui .response-content-type.controls-accept-header small{color:green;font-size:.7em}@-webkit-keyframes blinker{50%{opacity:0}}@keyframes blinker{50%{opacity:0}}.swagger-ui section h3{font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui a.nostyle{display:inline}.swagger-ui a.nostyle,.swagger-ui a.nostyle:visited{text-decoration:inherit;color:inherit;cursor:pointer}.swagger-ui .btn{font-size:14px;font-weight:700;padding:5px 23px;-webkit-transition:all .3s;transition:all .3s;border:2px solid gray;border-radius:4px;background:transparent;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1);box-shadow:0 1px 2px rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .btn.btn-sm{font-size:12px;padding:4px 23px}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{border-color:#ff6060;background-color:transparent;font-family:Titillium Web,sans-serif;color:#ff6060}.swagger-ui .btn.authorize{line-height:1;display:inline;color:#49cc90;border-color:#49cc90;background-color:transparent}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{background-color:#4990e2;color:#fff;border-color:#4990e2}.swagger-ui .btn-group{display:-webkit-box;display:-ms-flexbox;display:flex;padding:30px}.swagger-ui .btn-group .btn{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{padding:0 10px;border:none;background:none}.swagger-ui .authorization__btn.locked{opacity:1}.swagger-ui .authorization__btn.unlocked{opacity:.4}.swagger-ui .expand-methods,.swagger-ui .expand-operation{border:none;background:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{width:20px;height:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#404040}.swagger-ui .expand-methods svg{-webkit-transition:all .3s;transition:all .3s;fill:#707070}.swagger-ui button{cursor:pointer;outline:none}.swagger-ui button.invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}.swagger-ui select{font-size:14px;font-weight:700;padding:5px 40px 5px 10px;border:2px solid #41444e;border-radius:4px;background:#f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat;background-size:20px;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.25);box-shadow:0 1px 2px 0 rgba(0,0,0,.25);font-family:Titillium Web,sans-serif;color:#3b4151;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui select[multiple]{margin:5px 0;padding:5px;background:#f7f7f7}.swagger-ui select.invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}.swagger-ui .opblock-body select{min-width:230px}@media (max-width:768px){.swagger-ui .opblock-body select{min-width:180px}}.swagger-ui label{font-size:12px;font-weight:700;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{min-width:100px;margin:5px 0;padding:8px 10px;border:1px solid #d9d9d9;border-radius:4px;background:#fff}@media (max-width:768px){.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{max-width:175px}}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui textarea.invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}@-webkit-keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}.swagger-ui textarea{font-size:12px;width:100%;min-height:280px;padding:10px;border:none;border-radius:4px;outline:none;background:hsla(0,0%,100%,.8);font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{font-size:12px;min-height:100px;margin:0;padding:10px;resize:none;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .checkbox{padding:5px 0 10px;-webkit-transition:opacity .5s;transition:opacity .5s;color:#303030}.swagger-ui .checkbox label{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .checkbox p{font-weight:400!important;font-style:italic;margin:0!important;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{position:relative;top:3px;display:inline-block;width:16px;height:16px;margin:0 8px 0 0;padding:5px;cursor:pointer;border-radius:1px;background:#e8e8e8;-webkit-box-shadow:0 0 0 2px #e8e8e8;box-shadow:0 0 0 2px #e8e8e8;-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{-webkit-transform:scale(.9);transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='8' viewBox='3 7 10 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%2341474E' fill-rule='evenodd' d='M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z'/%3E%3C/svg%3E") 50% no-repeat}.swagger-ui .dialog-ux{position:fixed;z-index:9999;top:0;right:0;bottom:0;left:0}.swagger-ui .dialog-ux .backdrop-ux{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.8)}.swagger-ui .dialog-ux .modal-ux{position:absolute;z-index:9999;top:50%;left:50%;width:100%;min-width:300px;max-width:650px;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border:1px solid #ebebeb;border-radius:4px;background:#fff;-webkit-box-shadow:0 10px 30px 0 rgba(0,0,0,.2);box-shadow:0 10px 30px 0 rgba(0,0,0,.2)}.swagger-ui .dialog-ux .modal-ux-content{overflow-y:auto;max-height:540px;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{font-size:12px;margin:0 0 5px;color:#41444e;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-content h4{font-size:18px;font-weight:600;margin:15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-header{display:-webkit-box;display:-ms-flexbox;display:flex;padding:12px 0;border-bottom:1px solid #ebebeb;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .dialog-ux .modal-ux-header .close-modal{padding:0 10px;border:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui .dialog-ux .modal-ux-header h3{font-size:20px;font-weight:600;margin:0;padding:0 20px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .model{font-size:12px;font-weight:300;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:#a0a0a0!important}.swagger-ui .model .deprecated>td:first-of-type{text-decoration:line-through}.swagger-ui .model-toggle{font-size:10px;position:relative;top:6px;display:inline-block;margin:auto .3em;cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0deg);transform:rotate(0deg)}.swagger-ui .model-toggle:after{display:block;width:20px;height:20px;content:"";background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E") 50% no-repeat;background-size:100%}.swagger-ui .model-jump-to-path{position:relative;cursor:pointer}.swagger-ui .model-jump-to-path .view-line-link{position:absolute;top:-.4em;cursor:pointer}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{visibility:visible}.swagger-ui .model-hint{position:absolute;top:-1.8em;visibility:hidden;padding:.1em .5em;white-space:nowrap;color:#ebebeb;border-radius:4px;background:rgba(0,0,0,.7)}.swagger-ui .model p{margin:0 0 1em}.swagger-ui section.models{margin:30px 0;border:1px solid rgba(59,65,81,.3);border-radius:4px}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{margin:0 0 5px;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui section.models h4{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0;padding:10px 20px 10px 10px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;font-family:Titillium Web,sans-serif;color:#606060}.swagger-ui section.models h4 svg{-webkit-transition:all .4s;transition:all .4s}.swagger-ui section.models h4 span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{font-size:16px;margin:0 0 10px;font-family:Titillium Web,sans-serif;color:#707070}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{margin:0 20px 15px;-webkit-transition:all .5s;transition:all .5s;border-radius:4px;background:rgba(0,0,0,.05)}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-box{background:none}.swagger-ui .model-box{padding:10px;display:inline-block;border-radius:4px;background:rgba(0,0,0,.1)}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-box.deprecated{opacity:.5}.swagger-ui .model-title{font-size:16px;font-family:Titillium Web,sans-serif;color:#505050}.swagger-ui .model-deprecated-warning{font-size:16px;font-weight:600;margin-right:1em;font-family:Titillium Web,sans-serif;color:#f93e3e}.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-name{display:inline-block;margin-right:1em}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#606060}.swagger-ui .servers>label{font-size:12px;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .servers>label select{min-width:130px;max-width:100%}.swagger-ui .servers h4.message{padding-bottom:2em}.swagger-ui .servers table tr{width:30em}.swagger-ui .servers table td{display:inline-block;max-width:15em;vertical-align:middle;padding-top:10px;padding-bottom:10px}.swagger-ui .servers table td:first-of-type{padding-right:2em}.swagger-ui .servers table td input{width:100%;height:100%}.swagger-ui .servers .computed-url{margin:2em 0}.swagger-ui .servers .computed-url code{display:inline-block;padding:4px;font-size:16px;margin:0 1em}.swagger-ui .global-server-container{margin:0 0 20px;padding:30px 0;background:#fff;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.15);box-shadow:0 1px 2px 0 rgba(0,0,0,.15)}.swagger-ui .global-server-container .servers-title{line-height:2em;font-weight:700}.swagger-ui .operation-servers h4.message{margin-bottom:2em}.swagger-ui table{width:100%;padding:0 10px;border-collapse:collapse}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{width:174px;padding:0 0 0 2em}.swagger-ui table.headers td{font-size:12px;font-weight:300;vertical-align:middle;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{max-width:20%;min-width:6em;padding:10px 0}.swagger-ui table thead tr td,.swagger-ui table thead tr th{font-size:12px;font-weight:700;padding:12px 0;text-align:left;border-bottom:1px solid rgba(59,65,81,.2);font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description input[type=text]{width:100%;max-width:340px}.swagger-ui .parameters-col_description select{border-width:1px}.swagger-ui .parameter__name{font-size:16px;font-weight:400;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required:after{font-size:10px;position:relative;top:-6px;padding:5px;content:"required";color:rgba(255,0,0,.6)}.swagger-ui .parameter__extension,.swagger-ui .parameter__in{font-size:12px;font-style:italic;font-family:Source Code Pro,monospace;font-weight:600;color:gray}.swagger-ui .parameter__deprecated{font-size:12px;font-style:italic;font-family:Source Code Pro,monospace;font-weight:600;color:red}.swagger-ui .table-container{padding:20px}.swagger-ui .topbar{padding:8px 0;background-color:#89bf04}.swagger-ui .topbar .topbar-wrapper,.swagger-ui .topbar a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .topbar a{font-size:1.5em;font-weight:700;-webkit-box-flex:1;-ms-flex:1;flex:1;max-width:300px;text-decoration:none;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:3;-ms-flex:3;flex:3;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .topbar .download-url-wrapper input[type=text]{width:100%;margin:0;border:2px solid #547f00;border-radius:4px 0 0 4px;outline:none}.swagger-ui .topbar .download-url-wrapper .select-label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;max-width:600px;margin:0}.swagger-ui .topbar .download-url-wrapper .select-label span{font-size:16px;-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 10px 0 0;text-align:right}.swagger-ui .topbar .download-url-wrapper .select-label select{-webkit-box-flex:2;-ms-flex:2;flex:2;width:100%;border:2px solid #547f00;outline:none;-webkit-box-shadow:none;box-shadow:none}.swagger-ui .topbar .download-url-wrapper .download-url-button{font-size:16px;font-weight:700;padding:4px 30px;border:none;border-radius:0 4px 4px 0;background:#547f00;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .info{margin:50px 0}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5{font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info code{padding:3px 5px;border-radius:4px;background:rgba(0,0,0,.05);font-family:Source Code Pro,monospace;font-weight:600;color:#9012fe}.swagger-ui .info a{font-size:14px;-webkit-transition:all .4s;transition:all .4s;font-family:Open Sans,sans-serif;color:#4990e2}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{font-size:12px;font-weight:300!important;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .info .title{font-size:36px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info .title small{font-size:10px;position:relative;top:-5px;display:inline-block;margin:0 0 0 5px;padding:2px 4px;vertical-align:super;border-radius:57px;background:#7d8492}.swagger-ui .info .title small pre{margin:0;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .auth-btn-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 0;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .auth-btn-wrapper .btn-done{margin-right:1em}.swagger-ui .auth-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{padding-right:20px;margin-right:10px}.swagger-ui .auth-container{margin:0 0 10px;padding:10px 20px;border-bottom:1px solid #ebebeb}.swagger-ui .auth-container:last-of-type{margin:0;padding:10px 20px;border:0}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{font-size:12px;padding:10px;border-radius:4px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .scopes h2{font-size:14px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{margin:20px;padding:10px 20px;-webkit-animation:scaleUp .5s;animation:scaleUp .5s;border:2px solid #f93e3e;border-radius:4px;background:rgba(249,62,62,.1)}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{font-size:14px;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .errors-wrapper .errors small{color:#606060}.swagger-ui .errors-wrapper hgroup{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .errors-wrapper hgroup h4{font-size:20px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}@-webkit-keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.swagger-ui .Resizer.vertical.disabled{display:none} +/*# sourceMappingURL=swagger-ui.css.map*/ \ No newline at end of file diff --git a/storage_service/static/images/favicon-16x16.png b/storage_service/static/images/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..0f7e13b0d9903d27a9129950b1dad362361504e4 GIT binary patch literal 445 zcmV;u0Yd(XP)rNm2=6wQ7&2F}_`h_PI>(9Fx!5<0%l6W{u0OQ#*rglqx3__&vD?|#%fhn*Mn&YY1i+JQHqPvZ34FR@_E%P@x zzTL;Bw#nJXWY}D7^bC>-bx{t|^|R6Oci&MKvov8Op~S=}R=h^p-=vZ0uqG@LE6tP7 n92{cY$^db6>&z__iT?Z#Z8BG|DVcT0DjiaEd>Z!7_`J}8! zKk_$1lGm$vJOY&DjT-(&VGn0;R`iN9=1aOuG`H}BlY>&R3KbGER zB2$7euhH;y1C_LTQex%L6khZpkjFn!ajOUK)f3JLz+I;CE@(N)T)CM4AWjfl-(04= zrsMQ)#NG6nr^Y7!6LA;iHXh?UOFE%hhy>7dl=;I$J>g0BH_r|_4ctEsXx z2sDIQnwa*rcK=*3XUC$D{I@}DTNs@GCb7dB2%%nV%jR){xktt;Ah09op7x@l5D6B2 z0uBdt0YmcN!o?lMpu9Io(1&B1s{TUu*a>2&>Iycx__fbDRM8PYtLt+#G*xSt(cn}K zt!~W2{`9r)xkh^xodLS&FbYw`x$t&Vhl?)#f&k-lZIs<`$gTj{^#^HewuJz(WnUZZ z{Ty_aE;^93bhc-^^k6ZM!^e~$q5!Zz`XPta{a@651gPzaFx$&%IHL6hx$mSeAa#n6 zLkyc-M zs$qhBZhCNE^aIEV)H_~^IeqSRnvo!21Qc`Z;S9!IqXl4K(RUImejotzuG65LVuGS# zcqp@OA8~ln^4c^VihUew)IOX^E9KMtvSvnZ| zC@rl{f(B*PA26aFR`|X!!I(7x_|kq{rlqwhCia+CfNbOg_yYt0bDCc4g#h#`3jpCd zNAhr%4#Ye{i>ni$fzY%r0IS%l3HHZ4tTjOi=JW-t_iG~)oC!2C!52Cc|TAPaH zJ}l%m9yPmA-4#lJea@uf$a`(1;={rL2f*8;7%icbF}e^_`X#ndU=SI0nIn8hXPXHS zSN4rbF}jl0HWx(_`q`-SRa9jP8Ab!}sThNkQ634k=qXBVM4`o{M>qrLJD ze*%D)S;wpxG$d%FcDf-6%zMqWA+gw!C1~T5+|ys$G3Ksm&x59Lyd?0l+LWSk6hc4~ z+yC>|4f;X3#cq3!)>#Mvb-^co7LMrzqWeKB$21I>tJgaGFwu6eB%&j?@d*8GAx~In zI1p-lXVKtcvY7;$TX~wjYw|QhB%q!npQES%F~%Aqz~pJB%rNu!xAj;>xZt75!VHju zfFy%B-`3;Qf<{h94~I62zcHv}D5pS-QCN`M8K1>jN9mpbrFk=5no8j!00000NkvXX Hu0mjfOavUK literal 0 HcmV?d00001 diff --git a/storage_service/static/js/swagger-ui-bundle.js b/storage_service/static/js/swagger-ui-bundle.js new file mode 100644 index 000000000..8904027f5 --- /dev/null +++ b/storage_service/static/js/swagger-ui-bundle.js @@ -0,0 +1,104 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=1167)}([function(e,t,n){"use strict";e.exports=n(92)},function(e,t,n){e.exports=n(951)()},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";t.__esModule=!0;var r,i=n(325),o=(r=i)&&r.__esModule?r:{default:r};t.default=function(){function e(e,t){for(var n=0;n>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?S(e)+t:t}function A(){return!0}function D(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function M(e,t){return T(e,t,0)}function O(e,t){return T(e,t,t)}function T(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var P=0,I=1,R=2,N="function"==typeof Symbol&&Symbol.iterator,F="@@iterator",j=N||F;function B(e){this.next=e}function L(e,t,n,r){var i=0===e?t:1===e?n:[t,n];return r?r.value=i:r={value:i,done:!1},r}function q(){return{value:void 0,done:!0}}function z(e){return!!V(e)}function U(e){return e&&"function"==typeof e.next}function W(e){var t=V(e);return t&&t.call(e)}function V(e){var t=e&&(N&&e[N]||e[F]);if("function"==typeof t)return t}function H(e){return e&&"number"==typeof e.length}function J(e){return null===e||void 0===e?oe():a(e)?e.toSeq():function(e){var t=ue(e)||"object"==typeof e&&new te(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}(e)}function G(e){return null===e||void 0===e?oe().toKeyedSeq():a(e)?s(e)?e.toSeq():e.fromEntrySeq():ae(e)}function K(e){return null===e||void 0===e?oe():a(e)?s(e)?e.entrySeq():e.toIndexedSeq():se(e)}function X(e){return(null===e||void 0===e?oe():a(e)?s(e)?e.entrySeq():e:se(e)).toSetSeq()}B.prototype.toString=function(){return"[Iterator]"},B.KEYS=P,B.VALUES=I,B.ENTRIES=R,B.prototype.inspect=B.prototype.toSource=function(){return this.toString()},B.prototype[j]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return le(this,e,t,!0)},J.prototype.__iterator=function(e,t){return ce(this,e,t,!0)},t(G,J),G.prototype.toKeyedSeq=function(){return this},t(K,J),K.of=function(){return K(arguments)},K.prototype.toIndexedSeq=function(){return this},K.prototype.toString=function(){return this.__toString("Seq [","]")},K.prototype.__iterate=function(e,t){return le(this,e,t,!1)},K.prototype.__iterator=function(e,t){return ce(this,e,t,!1)},t(X,J),X.of=function(){return X(arguments)},X.prototype.toSetSeq=function(){return this},J.isSeq=ie,J.Keyed=G,J.Set=X,J.Indexed=K;var Y,$,Z,Q="@@__IMMUTABLE_SEQ__@@";function ee(e){this._array=e,this.size=e.length}function te(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function ne(e){this._iterable=e,this.size=e.length||e.size}function re(e){this._iterator=e,this._iteratorCache=[]}function ie(e){return!(!e||!e[Q])}function oe(){return Y||(Y=new ee([]))}function ae(e){var t=Array.isArray(e)?new ee(e).fromEntrySeq():U(e)?new re(e).fromEntrySeq():z(e)?new ne(e).fromEntrySeq():"object"==typeof e?new te(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function se(e){var t=ue(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ue(e){return H(e)?new ee(e):U(e)?new re(e):z(e)?new ne(e):void 0}function le(e,t,n,r){var i=e._cache;if(i){for(var o=i.length-1,a=0;a<=o;a++){var s=i[n?o-a:a];if(!1===t(s[1],r?s[0]:a,e))return a+1}return a}return e.__iterateUncached(t,n)}function ce(e,t,n,r){var i=e._cache;if(i){var o=i.length-1,a=0;return new B(function(){var e=i[n?o-a:a];return a++>o?{value:void 0,done:!0}:L(t,r?e[0]:a-1,e[1])})}return e.__iteratorUncached(t,n)}function pe(e,t){return t?function e(t,n,r,i){if(Array.isArray(n))return t.call(i,r,K(n).map(function(r,i){return e(t,r,i,n)}));if(he(n))return t.call(i,r,G(n).map(function(r,i){return e(t,r,i,n)}));return n}(t,e,"",{"":e}):fe(e)}function fe(e){return Array.isArray(e)?K(e).map(fe).toList():he(e)?G(e).map(fe).toMap():e}function he(e){return e&&(e.constructor===Object||void 0===e.constructor)}function de(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function me(e,t){if(e===t)return!0;if(!a(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||s(e)!==s(t)||u(e)!==u(t)||c(e)!==c(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!l(e);if(c(e)){var r=e.entries();return t.every(function(e,t){var i=r.next().value;return i&&de(i[1],e)&&(n||de(i[0],t))})&&r.next().done}var i=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{i=!0;var o=e;e=t,t=o}var p=!0,f=t.__iterate(function(t,r){if(n?!e.has(t):i?!de(t,e.get(r,y)):!de(e.get(r,y),t))return p=!1,!1});return p&&e.size===f}function ve(e,t){if(!(this instanceof ve))return new ve(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if($)return $;$=this}}function ge(e,t){if(!e)throw new Error(t)}function ye(e,t,n){if(!(this instanceof ye))return new ye(e,t,n);if(ge(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?{value:void 0,done:!0}:L(e,i,n[t?r-i++:i++])})},t(te,G),te.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},te.prototype.has=function(e){return this._object.hasOwnProperty(e)},te.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,i=r.length-1,o=0;o<=i;o++){var a=r[t?i-o:o];if(!1===e(n[a],a,this))return o+1}return o},te.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,i=r.length-1,o=0;return new B(function(){var a=r[t?i-o:o];return o++>i?{value:void 0,done:!0}:L(e,a,n[a])})},te.prototype[d]=!0,t(ne,K),ne.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=W(this._iterable),r=0;if(U(n))for(var i;!(i=n.next()).done&&!1!==e(i.value,r++,this););return r},ne.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=W(this._iterable);if(!U(n))return new B(q);var r=0;return new B(function(){var t=n.next();return t.done?t:L(e,r++,t.value)})},t(re,K),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var t=n.next();if(t.done)return t;r[i]=t.value}return L(e,i,r[i++])})},t(ve,K),ve.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},ve.prototype.get=function(e,t){return this.has(e)?this._value:t},ve.prototype.includes=function(e){return de(this._value,e)},ve.prototype.slice=function(e,t){var n=this.size;return D(e,t,n)?this:new ve(this._value,O(t,n)-M(e,n))},ve.prototype.reverse=function(){return this},ve.prototype.indexOf=function(e){return de(this._value,e)?0:-1},ve.prototype.lastIndexOf=function(e){return de(this._value,e)?this.size:-1},ve.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?{value:void 0,done:!0}:L(e,o++,a)})},ye.prototype.equals=function(e){return e instanceof ye?this._start===e._start&&this._end===e._end&&this._step===e._step:me(this,e)},t(_e,n),t(be,_e),t(xe,_e),t(ke,_e),_e.Keyed=be,_e.Indexed=xe,_e.Set=ke;var we="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function Ee(e){return e>>>1&1073741824|3221225471&e}function Se(e){if(!1===e||null===e||void 0===e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null===e||void 0===e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return Ee(n)}if("string"===t)return e.length>Ie?function(e){var t=Fe[e];void 0===t&&(t=Ce(e),Ne===Re&&(Ne=0,Fe={}),Ne++,Fe[e]=t);return t}(e):Ce(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return function(e){var t;if(Oe&&void 0!==(t=Me.get(e)))return t;if(void 0!==(t=e[Pe]))return t;if(!De){if(void 0!==(t=e.propertyIsEnumerable&&e.propertyIsEnumerable[Pe]))return t;if(void 0!==(t=function(e){if(e&&e.nodeType>0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}(e)))return t}t=++Te,1073741824&Te&&(Te=0);if(Oe)Me.set(e,t);else{if(void 0!==Ae&&!1===Ae(e))throw new Error("Non-extensible objects are not allowed as keys.");if(De)Object.defineProperty(e,Pe,{enumerable:!1,configurable:!1,writable:!1,value:t});else if(void 0!==e.propertyIsEnumerable&&e.propertyIsEnumerable===e.constructor.prototype.propertyIsEnumerable)e.propertyIsEnumerable=function(){return this.constructor.prototype.propertyIsEnumerable.apply(this,arguments)},e.propertyIsEnumerable[Pe]=t;else{if(void 0===e.nodeType)throw new Error("Unable to set a non-enumerable property on object.");e[Pe]=t}}return t}(e);if("function"==typeof e.toString)return Ce(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function Ce(e){for(var t=0,n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}})},Be.prototype.toString=function(){return this.__toString("Map {","}")},Be.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Be.prototype.set=function(e,t){return Qe(this,e,t)},Be.prototype.setIn=function(e,t){return this.updateIn(e,y,function(){return t})},Be.prototype.remove=function(e){return Qe(this,e,y)},Be.prototype.deleteIn=function(e){return this.updateIn(e,function(){return y})},Be.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},Be.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=function e(t,n,r,i){var o=t===y;var a=n.next();if(a.done){var s=o?r:t,u=i(s);return u===s?t:u}ge(o||t&&t.set,"invalid keyPath");var l=a.value;var c=o?y:t.get(l,y);var p=e(c,n,r,i);return p===c?t:p===y?t.remove(l):(o?Ze():t).set(l,p)}(this,nn(e),t,n);return r===y?void 0:r},Be.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Ze()},Be.prototype.merge=function(){return rt(this,void 0,arguments)},Be.prototype.mergeWith=function(t){return rt(this,t,e.call(arguments,1))},Be.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,Ze(),function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]})},Be.prototype.mergeDeep=function(){return rt(this,it,arguments)},Be.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return rt(this,ot(t),n)},Be.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,Ze(),function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]})},Be.prototype.sort=function(e){return Mt(Ht(this,e))},Be.prototype.sortBy=function(e,t){return Mt(Ht(this,t,e))},Be.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Be.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new w)},Be.prototype.asImmutable=function(){return this.__ensureOwner()},Be.prototype.wasAltered=function(){return this.__altered},Be.prototype.__iterator=function(e,t){return new Ke(this,e,t)},Be.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate(function(t){return r++,e(t[1],t[0],n)},t),r},Be.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?$e(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Be.isMap=Le;var qe,ze="@@__IMMUTABLE_MAP__@@",Ue=Be.prototype;function We(e,t){this.ownerID=e,this.entries=t}function Ve(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function He(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Je(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Ge(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function Ke(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&Ye(e._root)}function Xe(e,t){return L(e,t[0],t[1])}function Ye(e,t){return{node:e,index:0,__prev:t}}function $e(e,t,n,r){var i=Object.create(Ue);return i.size=e,i._root=t,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Ze(){return qe||(qe=$e(0))}function Qe(e,t,n){var r,i;if(e._root){var o=x(_),a=x(b);if(r=et(e._root,e.__ownerID,0,void 0,t,n,o,a),!a.value)return e;i=e.size+(o.value?n===y?-1:1:0)}else{if(n===y)return e;i=1,r=new We(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=i,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?$e(i,r):Ze()}function et(e,t,n,r,i,o,a,s){return e?e.update(t,n,r,i,o,a,s):o===y?e:(k(s),k(a),new Ge(t,r,[i,o]))}function tt(e){return e.constructor===Ge||e.constructor===Je}function nt(e,t,n,r,i){if(e.keyHash===r)return new Je(t,r,[e.entry,i]);var o,a=(0===n?e.keyHash:e.keyHash>>>n)&g,s=(0===n?r:r>>>n)&g;return new Ve(t,1<>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function ut(e,t,n,r){var i=r?e:E(e);return i[t]=n,i}Ue[ze]=!0,Ue.delete=Ue.remove,Ue.removeIn=Ue.deleteIn,We.prototype.get=function(e,t,n,r){for(var i=this.entries,o=0,a=i.length;o=lt)return function(e,t,n,r){e||(e=new w);for(var i=new Ge(e,Se(n),[n,r]),o=0;o>>e)&g),o=this.bitmap;return 0==(o&i)?r:this.nodes[st(o&i-1)].get(e+m,t,n,r)},Ve.prototype.update=function(e,t,n,r,i,o,a){void 0===n&&(n=Se(r));var s=(0===t?n:n>>>t)&g,u=1<=ct)return function(e,t,n,r,i){for(var o=0,a=new Array(v),s=0;0!==n;s++,n>>>=1)a[s]=1&n?t[o++]:void 0;return a[r]=i,new He(e,o+1,a)}(e,f,l,s,d);if(c&&!d&&2===f.length&&tt(f[1^p]))return f[1^p];if(c&&d&&1===f.length&&tt(d))return d;var _=e&&e===this.ownerID,b=c?d?l:l^u:l|u,x=c?d?ut(f,p,d,_):function(e,t,n){var r=e.length-1;if(n&&t===r)return e.pop(),e;for(var i=new Array(r),o=0,a=0;a>>e)&g,o=this.nodes[i];return o?o.get(e+m,t,n,r):r},He.prototype.update=function(e,t,n,r,i,o,a){void 0===n&&(n=Se(r));var s=(0===t?n:n>>>t)&g,u=i===y,l=this.nodes,c=l[s];if(u&&!c)return this;var p=et(c,e,t+m,n,r,i,o,a);if(p===c)return this;var f=this.count;if(c){if(!p&&--f0&&r=0&&e=e.size||t<0)return e.withMutations(function(e){t<0?Ct(e,t).set(0,n):Ct(e,0,t+1).set(t,n)});t+=e._origin;var r=e._tail,i=e._root,o=x(b);t>=Dt(e._capacity)?r=wt(r,e.__ownerID,0,t,n,o):i=wt(i,e.__ownerID,e._level,t,n,o);if(!o.value)return e;if(e.__ownerID)return e._root=i,e._tail=r,e.__hash=void 0,e.__altered=!0,e;return xt(e._origin,e._capacity,e._level,i,r)}(this,e,t)},ft.prototype.remove=function(e){return this.has(e)?0===e?this.shift():e===this.size-1?this.pop():this.splice(e,1):this},ft.prototype.insert=function(e,t){return this.splice(e,0,t)},ft.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=m,this._root=this._tail=null,this.__hash=void 0,this.__altered=!0,this):kt()},ft.prototype.push=function(){var e=arguments,t=this.size;return this.withMutations(function(n){Ct(n,0,t+e.length);for(var r=0;r>>t&g;if(r>=this.array.length)return new vt([],e);var i,o=0===r;if(t>0){var a=this.array[r];if((i=a&&a.removeBefore(e,t-m,n))===a&&o)return this}if(o&&!i)return this;var s=Et(this,e);if(!o)for(var u=0;u>>t&g;if(i>=this.array.length)return this;if(t>0){var o=this.array[i];if((r=o&&o.removeAfter(e,t-m,n))===o&&i===this.array.length-1)return this}var a=Et(this,e);return a.array.splice(i+1),r&&(a.array[i]=r),a};var gt,yt,_t={};function bt(e,t){var n=e._origin,r=e._capacity,i=Dt(r),o=e._tail;return a(e._root,e._level,0);function a(e,s,u){return 0===s?function(e,a){var s=a===i?o&&o.array:e&&e.array,u=a>n?0:n-a,l=r-a;l>v&&(l=v);return function(){if(u===l)return _t;var e=t?--l:u++;return s&&s[e]}}(e,u):function(e,i,o){var s,u=e&&e.array,l=o>n?0:n-o>>i,c=1+(r-o>>i);c>v&&(c=v);return function(){for(;;){if(s){var e=s();if(e!==_t)return e;s=null}if(l===c)return _t;var n=t?--c:l++;s=a(u&&u[n],i-m,o+(n<>>n&g,u=e&&s0){var l=e&&e.array[s],c=wt(l,t,n-m,r,i,o);return c===l?e:((a=Et(e,t)).array[s]=c,a)}return u&&e.array[s]===i?e:(k(o),a=Et(e,t),void 0===i&&s===a.array.length-1?a.array.pop():a.array[s]=i,a)}function Et(e,t){return t&&e&&t===e.ownerID?e:new vt(e?e.array.slice():[],t)}function St(e,t){if(t>=Dt(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&g],r-=m;return n}}function Ct(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new w,i=e._origin,o=e._capacity,a=i+t,s=void 0===n?o:n<0?o+n:i+n;if(a===i&&s===o)return e;if(a>=s)return e.clear();for(var u=e._level,l=e._root,c=0;a+c<0;)l=new vt(l&&l.array.length?[void 0,l]:[],r),c+=1<<(u+=m);c&&(a+=c,i+=c,s+=c,o+=c);for(var p=Dt(o),f=Dt(s);f>=1<p?new vt([],r):h;if(h&&f>p&&am;y-=m){var _=p>>>y&g;v=v.array[_]=Et(v.array[_],r)}v.array[p>>>m&g]=h}if(s=f)a-=f,s-=f,u=m,l=null,d=d&&d.removeBefore(r,0,a);else if(a>i||f>>u&g;if(b!==f>>>u&g)break;b&&(c+=(1<i&&(l=l.removeBefore(r,u,a-c)),l&&fo&&(o=l.size),a(u)||(l=l.map(function(e){return pe(e)})),r.push(l)}return o>e.size&&(e=e.setSize(o)),at(e,t,r)}function Dt(e){return e>>m<=v&&a.size>=2*o.size?(r=(i=a.filter(function(e,t){return void 0!==e&&s!==t})).toKeyedSeq().map(function(e){return e[0]}).flip().toMap(),e.__ownerID&&(r.__ownerID=i.__ownerID=e.__ownerID)):(r=o.remove(t),i=s===a.size-1?a.pop():a.set(s,void 0))}else if(u){if(n===a.get(s)[1])return e;r=o,i=a.set(s,[t,n])}else r=o.set(t,a.size),i=a.set(a.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=i,e.__hash=void 0,e):Tt(r,i)}function Rt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Nt(e){this._iter=e,this.size=e.size}function Ft(e){this._iter=e,this.size=e.size}function jt(e){this._iter=e,this.size=e.size}function Bt(e){var t=Qt(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=en,t.__iterateUncached=function(t,n){var r=this;return e.__iterate(function(e,n){return!1!==t(n,e,r)},n)},t.__iteratorUncached=function(t,n){if(t===R){var r=e.__iterator(t,n);return new B(function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e})}return e.__iterator(t===I?P:I,n)},t}function Lt(e,t,n){var r=Qt(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,i){var o=e.get(r,y);return o===y?i:t.call(n,o,r,e)},r.__iterateUncached=function(r,i){var o=this;return e.__iterate(function(e,i,a){return!1!==r(t.call(n,e,i,a),i,o)},i)},r.__iteratorUncached=function(r,i){var o=e.__iterator(R,i);return new B(function(){var i=o.next();if(i.done)return i;var a=i.value,s=a[0];return L(r,s,t.call(n,a[1],s,e),i)})},r}function qt(e,t){var n=Qt(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Bt(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=en,n.__iterate=function(t,n){var r=this;return e.__iterate(function(e,n){return t(e,n,r)},!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function zt(e,t,n,r){var i=Qt(e);return r&&(i.has=function(r){var i=e.get(r,y);return i!==y&&!!t.call(n,i,r,e)},i.get=function(r,i){var o=e.get(r,y);return o!==y&&t.call(n,o,r,e)?o:i}),i.__iterateUncached=function(i,o){var a=this,s=0;return e.__iterate(function(e,o,u){if(t.call(n,e,o,u))return s++,i(e,r?o:s-1,a)},o),s},i.__iteratorUncached=function(i,o){var a=e.__iterator(R,o),s=0;return new B(function(){for(;;){var o=a.next();if(o.done)return o;var u=o.value,l=u[0],c=u[1];if(t.call(n,c,l,e))return L(i,r?l:s++,c,o)}})},i}function Ut(e,t,n,r){var i=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=i:n|=0),D(t,n,i))return e;var o=M(t,i),a=O(n,i);if(o!=o||a!=a)return Ut(e.toSeq().cacheResult(),t,n,r);var s,u=a-o;u==u&&(s=u<0?0:u);var l=Qt(e);return l.size=0===s?s:e.size&&s||void 0,!r&&ie(e)&&s>=0&&(l.get=function(t,n){return(t=C(this,t))>=0&&ts)return{value:void 0,done:!0};var e=i.next();return r||t===I?e:L(t,u-1,t===P?void 0:e.value[1],e)})},l}function Wt(e,t,n,r){var i=Qt(e);return i.__iterateUncached=function(i,o){var a=this;if(o)return this.cacheResult().__iterate(i,o);var s=!0,u=0;return e.__iterate(function(e,o,l){if(!s||!(s=t.call(n,e,o,l)))return u++,i(e,r?o:u-1,a)}),u},i.__iteratorUncached=function(i,o){var a=this;if(o)return this.cacheResult().__iterator(i,o);var s=e.__iterator(R,o),u=!0,l=0;return new B(function(){var e,o,c;do{if((e=s.next()).done)return r||i===I?e:L(i,l++,i===P?void 0:e.value[1],e);var p=e.value;o=p[0],c=p[1],u&&(u=t.call(n,c,o,a))}while(u);return i===R?e:L(i,o,c,e)})},i}function Vt(e,t,n){var r=Qt(e);return r.__iterateUncached=function(r,i){var o=0,s=!1;return function e(u,l){var c=this;u.__iterate(function(i,u){return(!t||l0}function Kt(e,t,r){var i=Qt(e);return i.size=new ee(r).map(function(e){return e.size}).min(),i.__iterate=function(e,t){for(var n,r=this.__iterator(I,t),i=0;!(n=r.next()).done&&!1!==e(n.value,i++,this););return i},i.__iteratorUncached=function(e,i){var o=r.map(function(e){return e=n(e),W(i?e.reverse():e)}),a=0,s=!1;return new B(function(){var n;return s||(n=o.map(function(e){return e.next()}),s=n.some(function(e){return e.done})),s?{value:void 0,done:!0}:L(e,a++,t.apply(null,n.map(function(e){return e.value})))})},i}function Xt(e,t){return ie(e)?t:e.constructor(t)}function Yt(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function $t(e){return je(e.size),S(e)}function Zt(e){return s(e)?r:u(e)?i:o}function Qt(e){return Object.create((s(e)?G:u(e)?K:X).prototype)}function en(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):J.prototype.cacheResult.call(this)}function tn(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):An(e,t)},kn.prototype.pushAll=function(e){if(0===(e=i(e)).size)return this;je(e.size);var t=this.size,n=this._head;return e.reverse().forEach(function(e){t++,n={value:e,next:n}}),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):An(t,n)},kn.prototype.pop=function(){return this.slice(1)},kn.prototype.unshift=function(){return this.push.apply(this,arguments)},kn.prototype.unshiftAll=function(e){return this.pushAll(e)},kn.prototype.shift=function(){return this.pop.apply(this,arguments)},kn.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Dn()},kn.prototype.slice=function(e,t){if(D(e,t,this.size))return this;var n=M(e,this.size);if(O(t,this.size)!==this.size)return xe.prototype.slice.call(this,e,t);for(var r=this.size-n,i=this._head;n--;)i=i.next;return this.__ownerID?(this.size=r,this._head=i,this.__hash=void 0,this.__altered=!0,this):An(r,i)},kn.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?An(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},kn.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},kn.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new B(function(){if(r){var t=r.value;return r=r.next,L(e,n++,t)}return{value:void 0,done:!0}})},kn.isStack=wn;var En,Sn="@@__IMMUTABLE_STACK__@@",Cn=kn.prototype;function An(e,t,n,r){var i=Object.create(Cn);return i.size=e,i._head=t,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Dn(){return En||(En=An(0))}function Mn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}Cn[Sn]=!0,Cn.withMutations=Ue.withMutations,Cn.asMutable=Ue.asMutable,Cn.asImmutable=Ue.asImmutable,Cn.wasAltered=Ue.wasAltered,n.Iterator=B,Mn(n,{toArray:function(){je(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate(function(t,n){e[n]=t}),e},toIndexedSeq:function(){return new Nt(this)},toJS:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJS?e.toJS():e}).__toJS()},toJSON:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e}).__toJS()},toKeyedSeq:function(){return new Rt(this,!0)},toMap:function(){return Be(this.toKeyedSeq())},toObject:function(){je(this.size);var e={};return this.__iterate(function(t,n){e[n]=t}),e},toOrderedMap:function(){return Mt(this.toKeyedSeq())},toOrderedSet:function(){return vn(s(this)?this.valueSeq():this)},toSet:function(){return un(s(this)?this.valueSeq():this)},toSetSeq:function(){return new Ft(this)},toSeq:function(){return u(this)?this.toIndexedSeq():s(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return kn(s(this)?this.valueSeq():this)},toList:function(){return ft(s(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return Xt(this,function(e,t){var n=s(e),i=[e].concat(t).map(function(e){return a(e)?n&&(e=r(e)):e=n?ae(e):se(Array.isArray(e)?e:[e]),e}).filter(function(e){return 0!==e.size});if(0===i.length)return e;if(1===i.length){var o=i[0];if(o===e||n&&s(o)||u(e)&&u(o))return o}var l=new ee(i);return n?l=l.toKeyedSeq():u(e)||(l=l.toSetSeq()),(l=l.flatten(!0)).size=i.reduce(function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}},0),l}(this,e.call(arguments,0)))},includes:function(e){return this.some(function(t){return de(t,e)})},entries:function(){return this.__iterator(R)},every:function(e,t){je(this.size);var n=!0;return this.__iterate(function(r,i,o){if(!e.call(t,r,i,o))return n=!1,!1}),n},filter:function(e,t){return Xt(this,zt(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return je(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){je(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate(function(r){n?n=!1:t+=e,t+=null!==r&&void 0!==r?r.toString():""}),t},keys:function(){return this.__iterator(P)},map:function(e,t){return Xt(this,Lt(this,e,t))},reduce:function(e,t,n){var r,i;return je(this.size),arguments.length<2?i=!0:r=t,this.__iterate(function(t,o,a){i?(i=!1,r=t):r=e.call(n,r,t,o,a)}),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return Xt(this,qt(this,!0))},slice:function(e,t){return Xt(this,Ut(this,e,t,!0))},some:function(e,t){return!this.every(Rn(e),t)},sort:function(e){return Xt(this,Ht(this,e))},values:function(){return this.__iterator(I)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some(function(){return!0})},count:function(e,t){return S(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return function(e,t,n){var r=Be().asMutable();return e.__iterate(function(i,o){r.update(t.call(n,i,o,e),0,function(e){return e+1})}),r.asImmutable()}(this,e,t)},equals:function(e){return me(this,e)},entrySeq:function(){var e=this;if(e._cache)return new ee(e._cache);var t=e.toSeq().map(In).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(Rn(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate(function(n,i,o){if(e.call(t,n,i,o))return r=[i,n],!1}),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(A)},flatMap:function(e,t){return Xt(this,function(e,t,n){var r=Zt(e);return e.toSeq().map(function(i,o){return r(t.call(n,i,o,e))}).flatten(!0)}(this,e,t))},flatten:function(e){return Xt(this,Vt(this,e,!0))},fromEntrySeq:function(){return new jt(this)},get:function(e,t){return this.find(function(t,n){return de(n,e)},void 0,t)},getIn:function(e,t){for(var n,r=this,i=nn(e);!(n=i.next()).done;){var o=n.value;if((r=r&&r.get?r.get(o,y):y)===y)return t}return r},groupBy:function(e,t){return function(e,t,n){var r=s(e),i=(c(e)?Mt():Be()).asMutable();e.__iterate(function(o,a){i.update(t.call(n,o,a,e),function(e){return(e=e||[]).push(r?[a,o]:o),e})});var o=Zt(e);return i.map(function(t){return Xt(e,o(t))})}(this,e,t)},has:function(e){return this.get(e,y)!==y},hasIn:function(e){return this.getIn(e,y)!==y},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every(function(t){return e.includes(t)})},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey(function(t){return de(t,e)})},keySeq:function(){return this.toSeq().map(Pn).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return Jt(this,e)},maxBy:function(e,t){return Jt(this,t,e)},min:function(e){return Jt(this,e?Nn(e):Bn)},minBy:function(e,t){return Jt(this,t?Nn(t):Bn,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return Xt(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return Xt(this,Wt(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(Rn(e),t)},sortBy:function(e,t){return Xt(this,Ht(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return Xt(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return Xt(this,function(e,t,n){var r=Qt(e);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var a=0;return e.__iterate(function(e,i,s){return t.call(n,e,i,s)&&++a&&r(e,i,o)}),a},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var a=e.__iterator(R,i),s=!0;return new B(function(){if(!s)return{value:void 0,done:!0};var e=a.next();if(e.done)return e;var i=e.value,u=i[0],l=i[1];return t.call(n,l,u,o)?r===R?e:L(r,u,l,e):(s=!1,{value:void 0,done:!0})})},r}(this,e,t))},takeUntil:function(e,t){return this.takeWhile(Rn(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=function(e){if(e.size===1/0)return 0;var t=c(e),n=s(e),r=t?1:0;return function(e,t){return t=we(t,3432918353),t=we(t<<15|t>>>-15,461845907),t=we(t<<13|t>>>-13,5),t=we((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=Ee((t=we(t^t>>>13,3266489909))^t>>>16)}(e.__iterate(n?t?function(e,t){r=31*r+Ln(Se(e),Se(t))|0}:function(e,t){r=r+Ln(Se(e),Se(t))|0}:t?function(e){r=31*r+Se(e)|0}:function(e){r=r+Se(e)|0}),r)}(this))}});var On=n.prototype;On[p]=!0,On[j]=On.values,On.__toJS=On.toArray,On.__toStringMapper=Fn,On.inspect=On.toSource=function(){return this.toString()},On.chain=On.flatMap,On.contains=On.includes,Mn(r,{flip:function(){return Xt(this,Bt(this))},mapEntries:function(e,t){var n=this,r=0;return Xt(this,this.toSeq().map(function(i,o){return e.call(t,[o,i],r++,n)}).fromEntrySeq())},mapKeys:function(e,t){var n=this;return Xt(this,this.toSeq().flip().map(function(r,i){return e.call(t,r,i,n)}).flip())}});var Tn=r.prototype;function Pn(e,t){return t}function In(e,t){return[t,e]}function Rn(e){return function(){return!e.apply(this,arguments)}}function Nn(e){return function(){return-e.apply(this,arguments)}}function Fn(e){return"string"==typeof e?JSON.stringify(e):String(e)}function jn(){return E(arguments)}function Bn(e,t){return et?-1:0}function Ln(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Tn[f]=!0,Tn[j]=On.entries,Tn.__toJS=On.toObject,Tn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+Fn(e)},Mn(i,{toKeyedSeq:function(){return new Rt(this,!1)},filter:function(e,t){return Xt(this,zt(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return Xt(this,qt(this,!1))},slice:function(e,t){return Xt(this,Ut(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=M(e,e<0?this.count():this.size);var r=this.slice(0,e);return Xt(this,1===n?r:r.concat(E(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return Xt(this,Vt(this,e,!1))},get:function(e,t){return(e=C(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find(function(t,n){return n===e},void 0,t)},has:function(e){return(e=C(this,e))>=0&&(void 0!==this.size?this.size===1/0||e5e3)return e.textContent;return function(e){for(var n,r,i,o,a,s=e.textContent,u=0,l=s[0],c=1,p=e.innerHTML="",f=0;r=n,n=f<7&&"\\"==n?1:c;){if(c=l,l=s[++u],o=p.length>1,!c||f>8&&"\n"==c||[/\S/.test(c),1,1,!/[$\w]/.test(c),("/"==n||"\n"==n)&&o,'"'==n&&o,"'"==n&&o,s[u-4]+r+n=="--\x3e",r+n=="*/"][f])for(p&&(e.appendChild(a=t.createElement("span")).setAttribute("style",["color: #555; font-weight: bold;","","","color: #555;",""][f?f<3?2:f>6?4:f>3?3:+/^(a(bstract|lias|nd|rguments|rray|s(m|sert)?|uto)|b(ase|egin|ool(ean)?|reak|yte)|c(ase|atch|har|hecked|lass|lone|ompl|onst|ontinue)|de(bugger|cimal|clare|f(ault|er)?|init|l(egate|ete)?)|do|double|e(cho|ls?if|lse(if)?|nd|nsure|num|vent|x(cept|ec|p(licit|ort)|te(nds|nsion|rn)))|f(allthrough|alse|inal(ly)?|ixed|loat|or(each)?|riend|rom|unc(tion)?)|global|goto|guard|i(f|mp(lements|licit|ort)|n(it|clude(_once)?|line|out|stanceof|t(erface|ernal)?)?|s)|l(ambda|et|ock|ong)|m(icrolight|odule|utable)|NaN|n(amespace|ative|ext|ew|il|ot|ull)|o(bject|perator|r|ut|verride)|p(ackage|arams|rivate|rotected|rotocol|ublic)|r(aise|e(adonly|do|f|gister|peat|quire(_once)?|scue|strict|try|turn))|s(byte|ealed|elf|hort|igned|izeof|tatic|tring|truct|ubscript|uper|ynchronized|witch)|t(emplate|hen|his|hrows?|ransient|rue|ry|ype(alias|def|id|name|of))|u(n(checked|def(ined)?|ion|less|signed|til)|se|sing)|v(ar|irtual|oid|olatile)|w(char_t|hen|here|hile|ith)|xor|yield)$/.test(p):0]),a.appendChild(t.createTextNode(p))),i=f&&f<7?f:i,p="",f=11;![1,/[\/{}[(\-+*=<>:;|\\.,?!&@~]/.test(c),/[\])]/.test(c),/[$\w]/.test(c),"/"==c&&i<2&&"<"!=n,'"'==c,"'"==c,c+l+s[u+1]+s[u+2]=="\x3c!--",c+l=="/*",c+l=="//","#"==c][--f];);p+=c}}(e)},t.mapToList=function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"key";var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:l.default.Map();if(!l.default.Map.isMap(t)||!t.size)return l.default.List();Array.isArray(n)||(n=[n]);if(n.length<1)return t.merge(r);var a=l.default.List();var s=n[0];var u=!0;var c=!1;var p=void 0;try{for(var f,h=(0,o.default)(t.entries());!(u=(f=h.next()).done);u=!0){var d=f.value,m=(0,i.default)(d,2),v=m[0],g=m[1],y=e(g,n.slice(1),r.set(s,v));a=l.default.List.isList(y)?a.concat(y):a.push(y)}}catch(e){c=!0,p=e}finally{try{!u&&h.return&&h.return()}finally{if(c)throw p}}return a},t.extractFileNameFromContentDispositionHeader=function(e){var t=/filename="([^;]*);?"/i.exec(e);null===t&&(t=/filename=([^;]*);?/i.exec(e));if(null!==t&&t.length>1)return t[1];return null},t.pascalCase=S,t.pascalCaseFilename=function(e){return S(e.replace(/\.[^./]*$/,""))},t.sanitizeUrl=function(e){if("string"!=typeof e||""===e)return"";return(0,c.sanitizeUrl)(e)},t.getAcceptControllingResponse=function(e){if(!l.default.OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;var t=e.find(function(e,t){return t.startsWith("2")&&(0,s.default)(e.get("content")||{}).length>0}),n=e.get("default")||l.default.OrderedMap(),r=(n.get("content")||l.default.OrderedMap()).keySeq().toJS().length?n:null;return t||r},t.deeplyStripKey=function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0};if("object"!==(void 0===t?"undefined":(0,u.default)(t))||Array.isArray(t)||!n)return t;var i=(0,a.default)({},t);(0,s.default)(i).forEach(function(t){t===n&&r(i[t],t)?delete i[t]:i[t]=e(i[t],n,r)});return i};var l=b(n(7)),c=n(489),p=b(n(909)),f=b(n(422)),h=b(n(418)),d=b(n(223)),m=b(n(927)),v=b(n(115)),g=n(170),y=b(n(35)),_=b(n(681));function b(e){return e&&e.__esModule?e:{default:e}}var x="default",k=t.isImmutable=function(e){return l.default.Iterable.isIterable(e)};function w(e){return Array.isArray(e)?e:[e]}function E(e){return!!e&&"object"===(void 0===e?"undefined":(0,u.default)(e))}t.memoize=h.default;function S(e){return(0,f.default)((0,p.default)(e))}t.propChecker=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];return(0,s.default)(e).length!==(0,s.default)(t).length||((0,m.default)(e,function(e,n){if(r.includes(n))return!1;var i=t[n];return l.default.Iterable.isIterable(e)?!l.default.is(e,i):("object"!==(void 0===e?"undefined":(0,u.default)(e))||"object"!==(void 0===i?"undefined":(0,u.default)(i)))&&e!==i})||n.some(function(n){return!(0,v.default)(e[n],t[n])}))};var C=t.validateMaximum=function(e,t){if(e>t)return"Value must be less than Maximum"},A=t.validateMinimum=function(e,t){if(et)return"Value must be less than MaxLength"},F=t.validateMinLength=function(e,t){if(e.length2&&void 0!==arguments[2]&&arguments[2],r=[],i=t&&"body"===e.get("in")?e.get("value_xml"):e.get("value"),o=e.get("required"),a=n?e.get("schema"):e;if(!a)return r;var s=a.get("maximum"),c=a.get("minimum"),p=a.get("type"),f=a.get("format"),h=a.get("maxLength"),d=a.get("minLength"),m=a.get("pattern");if(p&&(o||i)){var v="string"===p&&i,g="array"===p&&Array.isArray(i)&&i.length,_="array"===p&&l.default.List.isList(i)&&i.count(),b="file"===p&&i instanceof y.default.File,x="boolean"===p&&(i||!1===i),k="number"===p&&(i||0===i),w="integer"===p&&(i||0===i),E=!1;if(n&&"object"===p)if("object"===(void 0===i?"undefined":(0,u.default)(i)))E=!0;else if("string"==typeof i)try{JSON.parse(i),E=!0}catch(e){return r.push("Parameter string value must be valid JSON"),r}var S=[v,g,_,b,x,k,w,E].some(function(e){return!!e});if(o&&!S)return r.push("Required field is not provided"),r;if(m){var B=j(i,m);B&&r.push(B)}if(h||0===h){var L=N(i,h);L&&r.push(L)}if(d){var q=F(i,d);q&&r.push(q)}if(s||0===s){var z=C(i,s);z&&r.push(z)}if(c||0===c){var U=A(i,c);U&&r.push(U)}if("string"===p){var W=void 0;if(!(W="date-time"===f?I(i):"uuid"===f?R(i):P(i)))return r;r.push(W)}else if("boolean"===p){var V=T(i);if(!V)return r;r.push(V)}else if("number"===p){var H=D(i);if(!H)return r;r.push(H)}else if("integer"===p){var J=M(i);if(!J)return r;r.push(J)}else if("array"===p){var G;if(!_||!i.count())return r;G=a.getIn(["items","type"]),i.forEach(function(e,t){var n=void 0;"number"===G?n=D(e):"integer"===G?n=M(e):"string"===G&&(n=P(e)),n&&r.push({index:t,error:n})})}else if("file"===p){var K=O(i);if(!K)return r;r.push(K)}}return r},t.getSampleSchema=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(/xml/.test(t)){if(!e.xml||!e.xml.name){if(e.xml=e.xml||{},!e.$$ref)return e.type||e.items||e.properties||e.additionalProperties?'\n\x3c!-- XML example cannot be generated --\x3e':null;var i=e.$$ref.match(/\S*\/(\S+)$/);e.xml.name=i[1]}return(0,g.memoizedCreateXMLExample)(e,n)}return(0,r.default)((0,g.memoizedSampleFromSchema)(e,n),null,2)},t.parseSearch=function(){var e={},t=y.default.location.search;if(!t)return{};if(""!=t){var n=t.substr(1).split("&");for(var r in n)n.hasOwnProperty(r)&&(r=n[r].split("="),e[decodeURIComponent(r[0])]=r[1]&&decodeURIComponent(r[1])||"")}return e},t.serializeSearch=function(e){return(0,s.default)(e).map(function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])}).join("&")},t.btoa=function(t){return(t instanceof e?t:new e(t.toString(),"utf-8")).toString("base64")},t.sorters={operationsSorter:{alpha:function(e,t){return e.get("path").localeCompare(t.get("path"))},method:function(e,t){return e.get("method").localeCompare(t.get("method"))}},tagsSorter:{alpha:function(e,t){return e.localeCompare(t)}}},t.buildFormData=function(e){var t=[];for(var n in e){var r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},t.shallowEqualKeys=function(e,t,n){return!!(0,d.default)(n,function(n){return(0,v.default)(e[n],t[n])})};var B=t.createDeepLinkPath=function(e){return"string"==typeof e||e instanceof String?e.trim().replace(/\s/g,"_"):""};t.escapeDeepLinkPath=function(e){return(0,_.default)(B(e))},t.getExtensions=function(e){return e.filter(function(e,t){return/^x-/.test(t)})},t.getCommonExtensions=function(e){return e.filter(function(e,t){return/^pattern|maxLength|minLength|maximum|minimum/.test(t)})}}).call(t,n(52).Buffer)},function(e,t,n){"use strict";var r=n(33);e.exports=r},function(e,t,n){"use strict";e.exports=function(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r>",o={listOf:function(e){return l(e,"List",r.List.isList)},mapOf:function(e,t){return c(e,t,"Map",r.Map.isMap)},orderedMapOf:function(e,t){return c(e,t,"OrderedMap",r.OrderedMap.isOrderedMap)},setOf:function(e){return l(e,"Set",r.Set.isSet)},orderedSetOf:function(e){return l(e,"OrderedSet",r.OrderedSet.isOrderedSet)},stackOf:function(e){return l(e,"Stack",r.Stack.isStack)},iterableOf:function(e){return l(e,"Iterable",r.Iterable.isIterable)},recordOf:function(e){return s(function(t,n,i,o,s){for(var u=arguments.length,l=Array(u>5?u-5:0),c=5;c6?u-6:0),c=6;c5?l-5:0),p=5;p5?o-5:0),s=5;s key("+c[p]+")"].concat(a));if(h instanceof Error)return h}})).apply(void 0,o);var u})}function p(e){var t=void 0===arguments[1]?"Iterable":arguments[1],n=void 0===arguments[2]?r.Iterable.isIterable:arguments[2];return s(function(r,i,o,s,u){for(var l=arguments.length,c=Array(l>5?l-5:0),p=5;p?@[\]^_`{|}~-])/g;function a(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(65535!=(65535&e)&&65534!=(65535&e)&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function s(e){if(e>65535){var t=55296+((e-=65536)>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}var u=/&([a-z#][a-z0-9]{1,31});/gi,l=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,c=n(471);function p(e,t){var n=0;return i(c,t)?c[t]:35===t.charCodeAt(0)&&l.test(t)&&a(n="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10))?s(n):e}var f=/[&<>"]/,h=/[&<>"]/g,d={"&":"&","<":"<",">":">",'"':"""};function m(e){return d[e]}t.assign=function(e){return[].slice.call(arguments,1).forEach(function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach(function(n){e[n]=t[n]})}}),e},t.isString=function(e){return"[object String]"===function(e){return Object.prototype.toString.call(e)}(e)},t.has=i,t.unescapeMd=function(e){return e.indexOf("\\")<0?e:e.replace(o,"$1")},t.isValidEntityCode=a,t.fromCodePoint=s,t.replaceEntities=function(e){return e.indexOf("&")<0?e:e.replace(u,p)},t.escapeHtml=function(e){return f.test(e)?e.replace(h,m):e}},function(e,t,n){"use strict";t.__esModule=!0;var r,i=n(325),o=(r=i)&&r.__esModule?r:{default:r};t.default=function(e,t,n){return t in e?(0,o.default)(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(31),i=n(57),o=n(63),a=n(77),s=n(131),u=function(e,t,n){var l,c,p,f,h=e&u.F,d=e&u.G,m=e&u.S,v=e&u.P,g=e&u.B,y=d?r:m?r[t]||(r[t]={}):(r[t]||{}).prototype,_=d?i:i[t]||(i[t]={}),b=_.prototype||(_.prototype={});for(l in d&&(n=t),n)p=((c=!h&&y&&void 0!==y[l])?y:n)[l],f=g&&c?s(p,r):v&&"function"==typeof p?s(Function.call,p):p,y&&a(y,l,p,e&u.U),_[l]!=p&&o(_,l,f),v&&b[l]!=p&&(b[l]=p)};r.core=i,u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,e.exports=u},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t,n){var r=n(30),i=n(107),o=n(58),a=/"/g,s=function(e,t,n,r){var i=String(o(e)),s="<"+t;return""!==n&&(s+=" "+n+'="'+String(r).replace(a,""")+'"'),s+">"+i+""};e.exports=function(e,t){var n={};n[e]=t(s),r(r.P+r.F*i(function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}),"String",n)}},function(e,t,n){"use strict";function r(e){return function(){return e}}var i=function(){};i.thatReturns=r,i.thatReturnsFalse=r(!1),i.thatReturnsTrue=r(!0),i.thatReturnsNull=r(null),i.thatReturnsThis=function(){return this},i.thatReturnsArgument=function(e){return e},e.exports=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=o(n(24));t.isOAS3=a,t.isSwagger2=function(e){var t=e.get("swagger");if(!t)return!1;return t.startsWith("2")},t.OAS3ComponentWrapFactory=function(e){return function(t,n){return function(o){if(n&&n.specSelectors&&n.specSelectors.specJson){var s=n.specSelectors.specJson();return a(s)?i.default.createElement(e,(0,r.default)({},o,n,{Ori:t})):i.default.createElement(t,o)}return console.warn("OAS3 wrapper: couldn't get spec"),null}}};var i=o(n(0));function o(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=e.get("openapi");return!!t&&t.startsWith("3")}},function(e,t,n){"use strict";var r,i=n(95),o=(r=i)&&r.__esModule?r:{default:r};e.exports=function(){var e={location:{},history:{},open:function(){},close:function(){},File:function(){}};if("undefined"==typeof window)return e;try{e=window;var t=!0,n=!1,r=void 0;try{for(var i,a=(0,o.default)(["File","Blob","FormData"]);!(t=(i=a.next()).done);t=!0){var s=i.value;s in window&&(e[s]=window[s])}}catch(e){n=!0,r=e}finally{try{!t&&a.return&&a.return()}finally{if(n)throw r}}}catch(e){console.error(e)}return e}()},function(e,t,n){e.exports={default:n(570),__esModule:!0}},function(e,t,n){var r=n(29);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var r=n(401),i="object"==typeof self&&self&&self.Object===Object&&self,o=r||i||Function("return this")();e.exports=o},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t){var n,r,i=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var u,l=[],c=!1,p=-1;function f(){c&&u&&(c=!1,u.length?l=u.concat(l):p=-1,l.length&&h())}function h(){if(!c){var e=s(f);c=!0;for(var t=l.length;t;){for(u=l,l=[];++p1)for(var n=1;n0&&(o=this.buffer[s-1],e.call("\0\r\n…\u2028\u2029",o)<0);)if(s--,this.pointer-s>n/2-1){i=" ... ",s+=5;break}for(u="",r=this.pointer;rn/2-1){u=" ... ",r-=5;break}return""+new Array(t).join(" ")+i+this.buffer.slice(s,r)+u+"\n"+new Array(t+this.pointer-s+i.length).join(" ")+"^"},t.prototype.toString=function(){var e,t;return e=this.get_snippet(),t=" on line "+(this.line+1)+", column "+(this.column+1),e?t:t+":\n"+e},t}(),this.YAMLError=function(e){function n(e){this.message=e,n.__super__.constructor.call(this),this.stack=this.toString()+"\n"+(new Error).stack.split("\n").slice(1).join("\n")}return t(n,e),n.prototype.toString=function(){return this.message},n}(Error),this.MarkedYAMLError=function(e){function n(e,t,r,i,o){this.context=e,this.context_mark=t,this.problem=r,this.problem_mark=i,this.note=o,n.__super__.constructor.call(this)}return t(n,e),n.prototype.toString=function(){var e;return e=[],null!=this.context&&e.push(this.context),null==this.context_mark||null!=this.problem&&null!=this.problem_mark&&this.context_mark.line===this.problem_mark.line&&this.context_mark.column===this.problem_mark.column||e.push(this.context_mark.toString()),null!=this.problem&&e.push(this.problem),null!=this.problem_mark&&e.push(this.problem_mark.toString()),null!=this.note&&e.push(this.note),e.join("\n")},n}(this.YAMLError)}).call(this)},function(e,t,n){e.exports=!n(54)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,n){if(n)return[e,t];return e},e.exports=t.default},function(e,t){e.exports=function(e){return null!=e&&"object"==typeof e}},function(e,t,n){"use strict";var r=n(13),i=n(69),o=n(33),a=(n(10),["dispatchConfig","_targetInst","nativeEvent","isDefaultPrevented","isPropagationStopped","_dispatchListeners","_dispatchInstances"]),s={type:null,target:null,currentTarget:o.thatReturnsNull,eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null};function u(e,t,n,r){this.dispatchConfig=e,this._targetInst=t,this.nativeEvent=n;var i=this.constructor.Interface;for(var a in i)if(i.hasOwnProperty(a)){0;var s=i[a];s?this[a]=s(n):"target"===a?this.target=r:this[a]=n[a]}var u=null!=n.defaultPrevented?n.defaultPrevented:!1===n.returnValue;return this.isDefaultPrevented=u?o.thatReturnsTrue:o.thatReturnsFalse,this.isPropagationStopped=o.thatReturnsFalse,this}r(u.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=o.thatReturnsTrue)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=o.thatReturnsTrue)},persist:function(){this.isPersistent=o.thatReturnsTrue},isPersistent:o.thatReturnsFalse,destructor:function(){var e=this.constructor.Interface;for(var t in e)this[t]=null;for(var n=0;n + * @license MIT + */ +var r=n(553),i=n(738),o=n(379);function a(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(e,t){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|e}function d(e,t){if(u.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return q(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(e).length;default:if(r)return q(e).length;t=(""+t).toLowerCase(),r=!0}}function m(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function v(e,t,n,r,i){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=i?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(i)return-1;n=e.length-1}else if(n<0){if(!i)return-1;n=0}if("string"==typeof t&&(t=u.from(t,r)),u.isBuffer(t))return 0===t.length?-1:g(e,t,n,r,i);if("number"==typeof t)return t&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):g(e,[t],n,r,i);throw new TypeError("val must be string, number or Buffer")}function g(e,t,n,r,i){var o,a=1,s=e.length,u=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,s/=2,u/=2,n/=2}function l(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(i){var c=-1;for(o=n;os&&(n=s-u),o=n;o>=0;o--){for(var p=!0,f=0;fi&&(r=i):r=i;var o=t.length;if(o%2!=0)throw new TypeError("Invalid hex string");r>o/2&&(r=o/2);for(var a=0;a>8,i=n%256,o.push(i),o.push(r);return o}(t,e.length-n),e,n,r)}function E(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function S(e,t,n){n=Math.min(e.length,n);for(var r=[],i=t;i239?4:l>223?3:l>191?2:1;if(i+p<=n)switch(p){case 1:l<128&&(c=l);break;case 2:128==(192&(o=e[i+1]))&&(u=(31&l)<<6|63&o)>127&&(c=u);break;case 3:o=e[i+1],a=e[i+2],128==(192&o)&&128==(192&a)&&(u=(15&l)<<12|(63&o)<<6|63&a)>2047&&(u<55296||u>57343)&&(c=u);break;case 4:o=e[i+1],a=e[i+2],s=e[i+3],128==(192&o)&&128==(192&a)&&128==(192&s)&&(u=(15&l)<<18|(63&o)<<12|(63&a)<<6|63&s)>65535&&u<1114112&&(c=u)}null===c?(c=65533,p=1):c>65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c),i+=p}return function(e){var t=e.length;if(t<=C)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return M(this,t,n);case"utf8":case"utf-8":return S(this,t,n);case"ascii":return A(this,t,n);case"latin1":case"binary":return D(this,t,n);case"base64":return E(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}.apply(this,arguments)},u.prototype.equals=function(e){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===u.compare(this,e)},u.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},u.prototype.compare=function(e,t,n,r,i){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),t<0||n>e.length||r<0||i>this.length)throw new RangeError("out of range index");if(r>=i&&t>=n)return 0;if(r>=i)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,i>>>=0,this===e)return 0;for(var o=i-r,a=n-t,s=Math.min(o,a),l=this.slice(r,i),c=e.slice(t,n),p=0;pi)&&(n=i),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var o=!1;;)switch(r){case"hex":return y(this,e,t,n);case"utf8":case"utf-8":return _(this,e,t,n);case"ascii":return b(this,e,t,n);case"latin1":case"binary":return x(this,e,t,n);case"base64":return k(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return w(this,e,t,n);default:if(o)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),o=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var C=4096;function A(e,t,n){var r="";n=Math.min(e.length,n);for(var i=t;ir)&&(n=r);for(var i="",o=t;on)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,n,r,i,o){if(!u.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw new RangeError("Index out of range")}function I(e,t,n,r){t<0&&(t=65535+t+1);for(var i=0,o=Math.min(e.length-n,2);i>>8*(r?i:1-i)}function R(e,t,n,r){t<0&&(t=4294967295+t+1);for(var i=0,o=Math.min(e.length-n,4);i>>8*(r?i:3-i)&255}function N(e,t,n,r,i,o){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function F(e,t,n,r,o){return o||N(e,0,n,4),i.write(e,t,n,r,23,4),n+4}function j(e,t,n,r,o){return o||N(e,0,n,8),i.write(e,t,n,r,52,8),n+8}u.prototype.slice=function(e,t){var n,r=this.length;if(e=~~e,t=void 0===t?r:~~t,e<0?(e+=r)<0&&(e=0):e>r&&(e=r),t<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(i*=256);)r+=this[e+--t]*i;return r},u.prototype.readUInt8=function(e,t){return t||T(e,1,this.length),this[e]},u.prototype.readUInt16LE=function(e,t){return t||T(e,2,this.length),this[e]|this[e+1]<<8},u.prototype.readUInt16BE=function(e,t){return t||T(e,2,this.length),this[e]<<8|this[e+1]},u.prototype.readUInt32LE=function(e,t){return t||T(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},u.prototype.readUInt32BE=function(e,t){return t||T(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},u.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=this[e],i=1,o=0;++o=(i*=128)&&(r-=Math.pow(2,8*t)),r},u.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=t,i=1,o=this[e+--r];r>0&&(i*=256);)o+=this[e+--r]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},u.prototype.readInt8=function(e,t){return t||T(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},u.prototype.readInt16LE=function(e,t){t||T(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt16BE=function(e,t){t||T(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt32LE=function(e,t){return t||T(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},u.prototype.readInt32BE=function(e,t){return t||T(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},u.prototype.readFloatLE=function(e,t){return t||T(e,4,this.length),i.read(this,e,!0,23,4)},u.prototype.readFloatBE=function(e,t){return t||T(e,4,this.length),i.read(this,e,!1,23,4)},u.prototype.readDoubleLE=function(e,t){return t||T(e,8,this.length),i.read(this,e,!0,52,8)},u.prototype.readDoubleBE=function(e,t){return t||T(e,8,this.length),i.read(this,e,!1,52,8)},u.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||P(this,e,t,n,Math.pow(2,8*n)-1,0);var i=1,o=0;for(this[t]=255&e;++o=0&&(o*=256);)this[t+i]=e/o&255;return t+n},u.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,255,0),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},u.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):I(this,e,t,!0),t+2},u.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):I(this,e,t,!1),t+2},u.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):R(this,e,t,!0),t+4},u.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var i=Math.pow(2,8*n-1);P(this,e,t,n,i-1,-i)}var o=0,a=1,s=0;for(this[t]=255&e;++o>0)-s&255;return t+n},u.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var i=Math.pow(2,8*n-1);P(this,e,t,n,i-1,-i)}var o=n-1,a=1,s=0;for(this[t+o]=255&e;--o>=0&&(a*=256);)e<0&&0===s&&0!==this[t+o+1]&&(s=1),this[t+o]=(e/a>>0)-s&255;return t+n},u.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,127,-128),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},u.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):I(this,e,t,!0),t+2},u.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):I(this,e,t,!1),t+2},u.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):R(this,e,t,!0),t+4},u.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeFloatLE=function(e,t,n){return F(this,e,t,!0,n)},u.prototype.writeFloatBE=function(e,t,n){return F(this,e,t,!1,n)},u.prototype.writeDoubleLE=function(e,t,n){return j(this,e,t,!0,n)},u.prototype.writeDoubleBE=function(e,t,n){return j(this,e,t,!1,n)},u.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--i)e[i+t]=this[i+n];else if(o<1e3||!u.TYPED_ARRAY_SUPPORT)for(i=0;i>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(o=t;o55295&&n<57344){if(!i){if(n>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&o.push(239,191,189);continue}i=n;continue}if(n<56320){(t-=3)>-1&&o.push(239,191,189),i=n;continue}n=65536+(i-55296<<10|n-56320)}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,n<128){if((t-=1)<0)break;o.push(n)}else if(n<2048){if((t-=2)<0)break;o.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function z(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(B,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function U(e,t,n,r){for(var i=0;i=t.length||i>=e.length);++i)t[i+n]=e[i];return i}}).call(t,n(19))},function(e,t,n){var r=n(97);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var r=n(44),i=n(101);e.exports=n(47)?function(e,t,n){return r.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t){var n=e.exports={version:"2.5.6"};"number"==typeof __e&&(__e=n)},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){"use strict";e.exports=function(e){if("function"!=typeof e)throw new TypeError(e+" is not a function");return e}},function(e,t,n){"use strict";function r(e,t){return e===t}function i(e){var t=arguments.length<=1||void 0===arguments[1]?r:arguments[1],n=null,i=null;return function(){for(var r=arguments.length,o=Array(r),a=0;a1?t-1:0),r=1;r2?n-2:0),i=2;i=n?e:e.length+1===n?""+t+e:""+new Array(n-e.length+1).join(t)+e},this.to_hex=function(e){return"string"==typeof e&&(e=e.charCodeAt(0)),e.toString(16)}}).call(this)}).call(t,n(19))},function(e,t,n){var r=n(76);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var r=n(134),i=n(354);e.exports=n(106)?function(e,t,n){return r.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";var r=n(703),i=Math.max;e.exports=function(e){return i(0,r(e))}},function(e,t,n){var r=n(82),i=n(864),o=n(893),a="[object Null]",s="[object Undefined]",u=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?s:a:u&&u in Object(e)?i(e):o(e)}},function(e,t,n){var r=n(825),i=n(865);e.exports=function(e,t){var n=i(e,t);return r(n)?n:void 0}},function(e,t,n){var r=n(385),i=n(828),o=n(86);e.exports=function(e){return o(e)?r(e):i(e)}},function(e,t,n){"use strict"},function(e,t,n){"use strict";var r=n(11),i=(n(8),function(e){if(this.instancePool.length){var t=this.instancePool.pop();return this.call(t,e),t}return new this(e)}),o=function(e){e instanceof this||r("25"),e.destructor(),this.instancePool.length`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>",u="]",l=new RegExp("^(?:<[A-Za-z][A-Za-z0-9-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^\"'=<>`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>|]|\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e|[<][?].*?[?][>]|]*>|)","i"),c=/[\\&]/,p="[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]",f=new RegExp("\\\\"+p+"|"+a,"gi"),h=new RegExp('[&<>"]',"g"),d=new RegExp(a+'|[&<>"]',"gi"),m=function(e){return 92===e.charCodeAt(0)?e.charAt(1):o(e)},v=function(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case'"':return""";default:return e}};e.exports={unescapeString:function(e){return c.test(e)?e.replace(f,m):e},normalizeURI:function(e){try{return r(i(e))}catch(t){return e}},escapeXml:function(e,t){return h.test(e)?t?e.replace(d,v):e.replace(h,v):e},reHtmlTag:l,OPENTAG:s,CLOSETAG:u,ENTITY:a,ESCAPABLE:p}},function(e,t){e.exports={}},function(e,t,n){var r=n(180),i=n(177);e.exports=function(e){return r(i(e))}},function(e,t,n){var r=n(177);e.exports=function(e){return Object(r(e))}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(31),i=n(63),o=n(133),a=n(202)("src"),s=Function.toString,u=(""+s).split("toString");n(57).inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,n,s){var l="function"==typeof n;l&&(o(n,"name")||i(n,"name",t)),e[t]!==n&&(l&&(o(n,a)||i(n,a,e[t]?""+e[t]:u.join(String(t)))),e===r?e[t]=n:s?e[t]?e[t]=n:i(e,t,n):(delete e[t],i(e,t,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[a]||s.call(this)})},function(e,t,n){"use strict";var r=n(366)();e.exports=function(e){return e!==r&&null!==e}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){"use strict";function r(e){return void 0===e||null===e}e.exports.isNothing=r,e.exports.isObject=function(e){return"object"==typeof e&&null!==e},e.exports.toArray=function(e){return Array.isArray(e)?e:r(e)?[]:[e]},e.exports.repeat=function(e,t){var n,r="";for(n=0;n`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>",u="]",l=new RegExp("^(?:<[A-Za-z][A-Za-z0-9-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^\"'=<>`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>|]|\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e|[<][?].*?[?][>]|]*>|)","i"),c=/[\\&]/,p="[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]",f=new RegExp("\\\\"+p+"|"+a,"gi"),h=new RegExp('[&<>"]',"g"),d=new RegExp(a+'|[&<>"]',"gi"),m=function(e){return 92===e.charCodeAt(0)?e.charAt(1):o(e)},v=function(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case'"':return""";default:return e}};e.exports={unescapeString:function(e){return c.test(e)?e.replace(f,m):e},normalizeURI:function(e){try{return r(i(e))}catch(t){return e}},escapeXml:function(e,t){return h.test(e)?t?e.replace(d,v):e.replace(h,v):e},reHtmlTag:l,OPENTAG:s,CLOSETAG:u,ENTITY:a,ESCAPABLE:p}},function(e,t,n){"use strict";var r=n(13),i=n(457),o=n(1058),a=n(1059),s=n(93),u=n(1060),l=n(1061),c=n(1062),p=n(1066),f=s.createElement,h=s.createFactory,d=s.cloneElement,m=r,v=function(e){return e},g={Children:{map:o.map,forEach:o.forEach,count:o.count,toArray:o.toArray,only:p},Component:i.Component,PureComponent:i.PureComponent,createElement:f,cloneElement:d,isValidElement:s.isValidElement,PropTypes:u,createClass:c,createFactory:h,createMixin:v,DOM:a,version:l,__spread:m};e.exports=g},function(e,t,n){"use strict";var r=n(13),i=n(51),o=(n(10),n(461),Object.prototype.hasOwnProperty),a=n(459),s={key:!0,ref:!0,__self:!0,__source:!0};function u(e){return void 0!==e.ref}function l(e){return void 0!==e.key}var c=function(e,t,n,r,i,o,s){var u={$$typeof:a,type:e,key:t,ref:n,props:s,_owner:o};return u};c.createElement=function(e,t,n){var r,a={},p=null,f=null;if(null!=t)for(r in u(t)&&(f=t.ref),l(t)&&(p=""+t.key),void 0===t.__self?null:t.__self,void 0===t.__source?null:t.__source,t)o.call(t,r)&&!s.hasOwnProperty(r)&&(a[r]=t[r]);var h=arguments.length-2;if(1===h)a.children=n;else if(h>1){for(var d=Array(h),m=0;m1){for(var g=Array(v),y=0;y=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t){e.exports=!0},function(e,t,n){var r=n(340),i=n(179);e.exports=Object.keys||function(e){return r(e,i)}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,n){var r=n(44).f,i=n(55),o=n(21)("toStringTag");e.exports=function(e,t,n){e&&!i(e=n?e:e.prototype,o)&&r(e,o,{configurable:!0,value:t})}},function(e,t,n){"use strict";var r=n(599)(!0);n(334)(String,"String",function(e){this._t=String(e),this._i=0},function(){var e,t=this._t,n=this._i;return n>=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){n(605);for(var r=n(20),i=n(56),o=n(73),a=n(21)("toStringTag"),s="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),u=0;u0?i(r(e),9007199254740991):0}},function(e,t,n){(function(e){function n(e){return Object.prototype.toString.call(e)}t.isArray=function(e){return Array.isArray?Array.isArray(e):"[object Array]"===n(e)},t.isBoolean=function(e){return"boolean"==typeof e},t.isNull=function(e){return null===e},t.isNullOrUndefined=function(e){return null==e},t.isNumber=function(e){return"number"==typeof e},t.isString=function(e){return"string"==typeof e},t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=function(e){return void 0===e},t.isRegExp=function(e){return"[object RegExp]"===n(e)},t.isObject=function(e){return"object"==typeof e&&null!==e},t.isDate=function(e){return"[object Date]"===n(e)},t.isError=function(e){return"[object Error]"===n(e)||e instanceof Error},t.isFunction=function(e){return"function"==typeof e},t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=e.isBuffer}).call(t,n(52).Buffer)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return"string"==typeof e&&r.test(e)};var r=/-webkit-|-moz-|-ms-/;e.exports=t.default},function(e,t,n){"use strict";var r=n(78);e.exports=function(e){if(!r(e))throw new TypeError("Cannot use null or undefined");return e}},function(e,t,n){"use strict";function r(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r.prototype.toString=function(e){var t=this.name+": ";return t+=this.reason||"(unknown reason)",!e&&this.mark&&(t+=" "+this.mark.toString()),t},e.exports=r},function(e,t,n){"use strict";var r=n(81);e.exports=new r({include:[n(380)],implicit:[n(789),n(782)],explicit:[n(774),n(784),n(785),n(787)]})},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t,n){"use strict";var r=n(11),i=n(234),o=n(235),a=n(239),s=n(445),u=n(446),l=(n(8),{}),c=null,p=function(e,t){e&&(o.executeDispatchesInOrder(e,t),e.isPersistent()||e.constructor.release(e))},f=function(e){return p(e,!0)},h=function(e){return p(e,!1)},d=function(e){return"."+e._rootNodeID};var m={injection:{injectEventPluginOrder:i.injectEventPluginOrder,injectEventPluginsByName:i.injectEventPluginsByName},putListener:function(e,t,n){"function"!=typeof n&&r("94",t,typeof n);var o=d(e);(l[t]||(l[t]={}))[o]=n;var a=i.registrationNameModules[t];a&&a.didPutListener&&a.didPutListener(e,t,n)},getListener:function(e,t){var n=l[t];if(function(e,t,n){switch(e){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":return!(!n.disabled||(r=t,"button"!==r&&"input"!==r&&"select"!==r&&"textarea"!==r));default:return!1}var r}(t,e._currentElement.type,e._currentElement.props))return null;var r=d(e);return n&&n[r]},deleteListener:function(e,t){var n=i.registrationNameModules[t];n&&n.willDeleteListener&&n.willDeleteListener(e,t);var r=l[t];r&&delete r[d(e)]},deleteAllListeners:function(e){var t=d(e);for(var n in l)if(l.hasOwnProperty(n)&&l[n][t]){var r=i.registrationNameModules[n];r&&r.willDeleteListener&&r.willDeleteListener(e,n),delete l[n][t]}},extractEvents:function(e,t,n,r){for(var o,a=i.plugins,u=0;u0&&void 0!==arguments[0]?arguments[0]:{};return{type:p,payload:e}},t.clearBy=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!0};return{type:f,payload:e}};var r,i=n(256),o=(r=i)&&r.__esModule?r:{default:r};var a=t.NEW_THROWN_ERR="err_new_thrown_err",s=t.NEW_THROWN_ERR_BATCH="err_new_thrown_err_batch",u=t.NEW_SPEC_ERR="err_new_spec_err",l=t.NEW_SPEC_ERR_BATCH="err_new_spec_err_batch",c=t.NEW_AUTH_ERR="err_new_auth_err",p=t.CLEAR="err_clear",f=t.CLEAR_BY="err_clear_by"},function(e,t,n){e.exports={default:n(577),__esModule:!0}},function(e,t,n){var r; +/*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +/*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +!function(){"use strict";var n={}.hasOwnProperty;function i(){for(var e=[],t=0;t_;_++)if((v=t?y(a(d=e[_])[0],d[1]):y(e[_]))===l||v===c)return v}else for(m=g.call(e);!(d=m.next()).done;)if((v=i(m,y,d.value,t))===l||v===c)return v}).BREAK=l,t.RETURN=c},function(e,t,n){var r=n(129)("meta"),i=n(29),o=n(55),a=n(44).f,s=0,u=Object.isExtensible||function(){return!0},l=!n(54)(function(){return u(Object.preventExtensions({}))}),c=function(e){a(e,r,{value:{i:"O"+ ++s,w:{}}})},p=e.exports={KEY:r,NEED:!1,fastKey:function(e,t){if(!i(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!o(e,r)){if(!u(e))return"F";if(!t)return"E";c(e)}return e[r].i},getWeak:function(e,t){if(!o(e,r)){if(!u(e))return!0;if(!t)return!1;c(e)}return e[r].w},onFreeze:function(e){return l&&p.NEED&&u(e)&&!o(e,r)&&c(e),e}}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){var r=n(188),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){var r=n(130);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},function(e,t,n){"use strict";var r=n(63),i=n(77),o=n(107),a=n(58),s=n(18);e.exports=function(e,t,n){var u=s(e),l=n(a,u,""[e]),c=l[0],p=l[1];o(function(){var t={};return t[u]=function(){return 7},7!=""[e](t)})&&(i(String.prototype,e,c),r(RegExp.prototype,u,2==t?function(e,t){return p.call(e,this,t)}:function(e){return p.call(e,this)}))}},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var r=n(62),i=n(625),o=n(644),a=Object.defineProperty;t.f=n(106)?Object.defineProperty:function(e,t,n){if(r(e),t=o(t,!0),r(n),i)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(627),i=n(58);e.exports=function(e){return r(i(e))}},function(e,t,n){"use strict";var r=n(367),i=n(370),o=n(707),a=n(712);(e.exports=function(e,t){var n,o,s,u,l;return arguments.length<2||"string"!=typeof e?(u=t,t=e,e=null):u=arguments[2],null==e?(n=s=!0,o=!1):(n=a.call(e,"c"),o=a.call(e,"e"),s=a.call(e,"w")),l={value:t,configurable:n,enumerable:o,writable:s},u?r(i(u),l):l}).gs=function(e,t,n){var s,u,l,c;return"string"!=typeof e?(l=n,n=t,t=e,e=null):l=arguments[3],null==t?t=void 0:o(t)?null==n?n=void 0:o(n)||(l=n,n=void 0):(l=t,t=n=void 0),null==e?(s=!0,u=!1):(s=a.call(e,"c"),u=a.call(e,"e")),c={get:t,set:n,configurable:s,enumerable:u},l?r(i(l),c):c}},function(e,t,n){var r=n(688),i=n(686);t.decode=function(e,t){return(!t||t<=0?i.XML:i.HTML)(e)},t.decodeStrict=function(e,t){return(!t||t<=0?i.XML:i.HTMLStrict)(e)},t.encode=function(e,t){return(!t||t<=0?r.XML:r.HTML)(e)},t.encodeXML=r.XML,t.encodeHTML4=t.encodeHTML5=t.encodeHTML=r.HTML,t.decodeXML=t.decodeXMLStrict=i.XML,t.decodeHTML4=t.decodeHTML5=t.decodeHTML=i.HTML,t.decodeHTML4Strict=t.decodeHTML5Strict=t.decodeHTMLStrict=i.HTMLStrict,t.escape=r.escape},function(e,t,n){"use strict";e.exports=n(704)("forEach")},function(e,t,n){"use strict";var r={};e.exports=r},function(e,t,n){"use strict";var r=n(81);e.exports=r.DEFAULT=new r({include:[n(114)],explicit:[n(780),n(779),n(778)]})},function(e,t,n){var r=n(879),i=n(880),o=n(881),a=n(882),s=n(883);function u(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e]/;e.exports=function(e){return"boolean"==typeof e||"number"==typeof e?""+e:function(e){var t,n=""+e,i=r.exec(n);if(!i)return n;var o="",a=0,s=0;for(a=i.index;a]/,u=n(241)(function(e,t){if(e.namespaceURI!==o.svg||"innerHTML"in e)e.innerHTML=t;else{(r=r||document.createElement("div")).innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(i.canUseDOM){var l=document.createElement("div");l.innerHTML=" ",""===l.innerHTML&&(u=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),l=null}e.exports=u},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e){var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]="number"==typeof e[n]?e[n]:e[n].val);return t},e.exports=t.default},function(e,t,n){"use strict";e.exports=function(e,t){var n,r,i,o=-1,a=e.posMax,s=e.pos,u=e.isInLabel;if(e.isInLabel)return-1;if(e.labelUnmatchedScopes)return e.labelUnmatchedScopes--,-1;for(e.pos=t+1,e.isInLabel=!0,n=1;e.pos1&&void 0!==arguments[1])||arguments[1];return e=(0,r.normalizeArray)(e),{type:s,payload:{thing:e,shown:t}}},t.changeMode=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=(0,r.normalizeArray)(e),{type:a,payload:{thing:e,mode:t}}};var r=n(9),i=t.UPDATE_LAYOUT="layout_update_layout",o=t.UPDATE_FILTER="layout_update_filter",a=t.UPDATE_MODE="layout_update_mode",s=t.SHOW="layout_show"},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setSelectedServer=function(e,t){return{type:r,payload:{selectedServerUrl:e,namespace:t}}},t.setRequestBodyValue=function(e){var t=e.value,n=e.pathMethod;return{type:i,payload:{value:t,pathMethod:n}}},t.setRequestContentType=function(e){var t=e.value,n=e.pathMethod;return{type:o,payload:{value:t,pathMethod:n}}},t.setResponseContentType=function(e){var t=e.value,n=e.path,r=e.method;return{type:a,payload:{value:t,path:n,method:r}}},t.setServerVariableValue=function(e){var t=e.server,n=e.namespace,r=e.key,i=e.val;return{type:s,payload:{server:t,namespace:n,key:r,val:i}}};var r=t.UPDATE_SELECTED_SERVER="oas3_set_servers",i=t.UPDATE_REQUEST_BODY_VALUE="oas3_set_request_body_value",o=t.UPDATE_REQUEST_CONTENT_TYPE="oas3_set_request_content_type",a=t.UPDATE_RESPONSE_CONTENT_TYPE="oas3_set_response_content_type",s=t.UPDATE_SERVER_VARIABLE_VALUE="oas3_set_server_variable_value"},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.memoizedSampleFromSchema=t.memoizedCreateXMLExample=t.sampleXmlFromSchema=t.inferSchema=t.sampleFromSchema=void 0,t.createXMLExample=p;var r=n(9),i=a(n(1155)),o=a(n(939));function a(e){return e&&e.__esModule?e:{default:e}}var s={string:function(){return"string"},string_email:function(){return"user@example.com"},"string_date-time":function(){return(new Date).toISOString()},number:function(){return 0},number_float:function(){return 0},integer:function(){return 0},boolean:function(e){return"boolean"!=typeof e.default||e.default}},u=function(e){var t=e=(0,r.objectify)(e),n=t.type,i=t.format,o=s[n+"_"+i]||s[n];return(0,r.isFunc)(o)?o(e):"Unknown Type: "+e.type},l=t.sampleFromSchema=function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=(0,r.objectify)(t),o=i.type,a=i.example,s=i.properties,l=i.additionalProperties,c=i.items,p=n.includeReadOnly,f=n.includeWriteOnly;if(void 0!==a)return(0,r.deeplyStripKey)(a,"$$ref",function(e){return"string"==typeof e&&e.indexOf("#")>-1});if(!o)if(s)o="object";else{if(!c)return;o="array"}if("object"===o){var h=(0,r.objectify)(s),d={};for(var m in h)h[m].readOnly&&!p||h[m].writeOnly&&!f||(d[m]=e(h[m],n));if(!0===l)d.additionalProp1={};else if(l)for(var v=(0,r.objectify)(l),g=e(v,n),y=1;y<4;y++)d["additionalProp"+y]=g;return d}return"array"===o?Array.isArray(c.anyOf)?c.anyOf.map(function(t){return e(t,n)}):Array.isArray(c.oneOf)?c.oneOf.map(function(t){return e(t,n)}):[e(c,n)]:t.enum?t.default?t.default:(0,r.normalizeArray)(t.enum)[0]:"file"!==o?u(t):void 0},c=(t.inferSchema=function(e){return e.schema&&(e=e.schema),e.properties&&(e.type="object"),e},t.sampleXmlFromSchema=function e(t){var n,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=(0,r.objectify)(t),a=o.type,s=o.properties,l=o.additionalProperties,c=o.items,p=o.example,f=i.includeReadOnly,h=i.includeWriteOnly,d=o.default,m={},v={},g=t.xml,y=g.name,_=g.prefix,b=g.namespace,x=o.enum,k=void 0;if(!a)if(s||l)a="object";else{if(!c)return;a="array"}(y=y||"notagname",n=(_?_+":":"")+y,b)&&(v[_?"xmlns:"+_:"xmlns"]=b);if("array"===a&&c){if(c.xml=c.xml||g||{},c.xml.name=c.xml.name||g.name,g.wrapped)return m[n]=[],Array.isArray(p)?p.forEach(function(t){c.example=t,m[n].push(e(c,i))}):Array.isArray(d)?d.forEach(function(t){c.default=t,m[n].push(e(c,i))}):m[n]=[e(c,i)],v&&m[n].push({_attr:v}),m;var w=[];return Array.isArray(p)?(p.forEach(function(t){c.example=t,w.push(e(c,i))}),w):Array.isArray(d)?(d.forEach(function(t){c.default=t,w.push(e(c,i))}),w):e(c,i)}if("object"===a){var E=(0,r.objectify)(s);for(var S in m[n]=[],p=p||{},E)if(E.hasOwnProperty(S)&&(!E[S].readOnly||f)&&(!E[S].writeOnly||h))if(E[S].xml=E[S].xml||{},E[S].xml.attribute){var C=Array.isArray(E[S].enum)&&E[S].enum[0],A=E[S].example,D=E[S].default;v[E[S].xml.name||S]=void 0!==A&&A||void 0!==p[S]&&p[S]||void 0!==D&&D||C||u(E[S])}else{E[S].xml.name=E[S].xml.name||S,void 0===E[S].example&&void 0!==p[S]&&(E[S].example=p[S]);var M=e(E[S]);Array.isArray(M)?m[n]=m[n].concat(M):m[n].push(M)}return!0===l?m[n].push({additionalProp:"Anything can be here"}):l&&m[n].push({additionalProp:u(l)}),v&&m[n].push({_attr:v}),m}return k=void 0!==p?p:void 0!==d?d:Array.isArray(x)?x[0]:u(t),m[n]=v?[{_attr:v},k]:k,m});function p(e,t){var n=c(e,t);if(n)return(0,i.default)(n,{declaration:!0,indent:"\t"})}t.memoizedCreateXMLExample=(0,o.default)(p),t.memoizedSampleFromSchema=(0,o.default)(l)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.execute=t.executeRequest=t.logRequest=t.setMutatedRequest=t.setRequest=t.setResponse=t.validateParams=t.invalidateResolvedSubtreeCache=t.updateResolvedSubtree=t.requestResolvedSubtree=t.resolveSpec=t.parseToJson=t.SET_SCHEME=t.UPDATE_RESOLVED_SUBTREE=t.UPDATE_RESOLVED=t.UPDATE_OPERATION_META_VALUE=t.CLEAR_VALIDATE_PARAMS=t.CLEAR_REQUEST=t.CLEAR_RESPONSE=t.LOG_REQUEST=t.SET_MUTATED_REQUEST=t.SET_REQUEST=t.SET_RESPONSE=t.VALIDATE_PARAMS=t.UPDATE_PARAM=t.UPDATE_JSON=t.UPDATE_URL=t.UPDATE_SPEC=void 0;var r=_(n(24)),i=_(n(96)),o=_(n(23)),a=_(n(42)),s=_(n(123)),u=_(n(327)),l=_(n(326)),c=_(n(43));t.updateSpec=function(e){var t=F(e).replace(/\t/g," ");if("string"==typeof e)return{type:b,payload:t}},t.updateResolved=function(e){return{type:I,payload:e}},t.updateUrl=function(e){return{type:x,payload:e}},t.updateJsonSpec=function(e){return{type:k,payload:e}},t.changeParam=function(e,t,n,r,i){return{type:w,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:i}}},t.clearValidateParams=function(e){return{type:T,payload:{pathMethod:e}}},t.changeConsumesValue=function(e,t){return{type:P,payload:{path:e,value:t,key:"consumes_value"}}},t.changeProducesValue=function(e,t){return{type:P,payload:{path:e,value:t,key:"produces_value"}}},t.clearResponse=function(e,t){return{type:M,payload:{path:e,method:t}}},t.clearRequest=function(e,t){return{type:O,payload:{path:e,method:t}}},t.setScheme=function(e,t,n){return{type:N,payload:{scheme:e,path:t,method:n}}};var p=_(n(211)),f=n(7),h=_(n(481)),d=_(n(256)),m=_(n(415)),v=_(n(913)),g=_(n(926)),y=n(9);function _(e){return e&&e.__esModule?e:{default:e}}var b=t.UPDATE_SPEC="spec_update_spec",x=t.UPDATE_URL="spec_update_url",k=t.UPDATE_JSON="spec_update_json",w=t.UPDATE_PARAM="spec_update_param",E=t.VALIDATE_PARAMS="spec_validate_param",S=t.SET_RESPONSE="spec_set_response",C=t.SET_REQUEST="spec_set_request",A=t.SET_MUTATED_REQUEST="spec_set_mutated_request",D=t.LOG_REQUEST="spec_log_request",M=t.CLEAR_RESPONSE="spec_clear_response",O=t.CLEAR_REQUEST="spec_clear_request",T=t.CLEAR_VALIDATE_PARAMS="spec_clear_validate_param",P=t.UPDATE_OPERATION_META_VALUE="spec_update_operation_meta_value",I=t.UPDATE_RESOLVED="spec_update_resolved",R=t.UPDATE_RESOLVED_SUBTREE="spec_update_resolved_subtree",N=t.SET_SCHEME="set_scheme",F=function(e){return(0,m.default)(e)?e:""};t.parseToJson=function(e){return function(t){var n=t.specActions,r=t.specSelectors,i=t.errActions,o=r.specStr,a=null;try{e=e||o(),i.clear({source:"parser"}),a=p.default.safeLoad(e)}catch(e){return console.error(e),i.newSpecErr({source:"parser",level:"error",message:e.reason,line:e.mark&&e.mark.line?e.mark.line+1:void 0})}return a&&"object"===(void 0===a?"undefined":(0,c.default)(a))?n.updateJsonSpec(a):{}}};var j=!1,B=(t.resolveSpec=function(e,t){return function(n){var r=n.specActions,i=n.specSelectors,o=n.errActions,a=n.fn,s=a.fetch,u=a.resolve,l=a.AST,c=n.getConfigs;j||(console.warn("specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!"),j=!0);var p=c(),f=p.modelPropertyMacro,h=p.parameterMacro,d=p.requestInterceptor,m=p.responseInterceptor;void 0===e&&(e=i.specJson()),void 0===t&&(t=i.url());var v=l.getLineNumberForPath,g=i.specStr();return u({fetch:s,spec:e,baseDoc:t,modelPropertyMacro:f,parameterMacro:h,requestInterceptor:d,responseInterceptor:m}).then(function(e){var t=e.spec,n=e.errors;if(o.clear({type:"thrown"}),Array.isArray(n)&&n.length>0){var i=n.map(function(e){return console.error(e),e.line=e.fullPath?v(g,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",Object.defineProperty(e,"message",{enumerable:!0,value:e.message}),e});o.newThrownErrBatch(i)}return r.updateResolved(t)})}},[]),L=(0,v.default)((0,l.default)(u.default.mark(function e(){var t,n,r,i,o,a,c,p,h,d,m,v,y,_,b;return u.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(t=B.system){e.next=4;break}return console.error("debResolveSubtrees: don't have a system to operate on, aborting."),e.abrupt("return");case 4:if(n=t.errActions,r=t.errSelectors,i=t.fn,o=i.resolveSubtree,a=i.AST.getLineNumberForPath,c=t.specSelectors,p=t.specActions,o){e.next=8;break}return console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing."),e.abrupt("return");case 8:return h=c.specStr(),d=t.getConfigs(),m=d.modelPropertyMacro,v=d.parameterMacro,y=d.requestInterceptor,_=d.responseInterceptor,e.prev=10,e.next=13,B.reduce(function(){var e=(0,l.default)(u.default.mark(function e(t,i){var s,l,p,f,d,b,x;return u.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,t;case 2:return s=e.sent,l=s.resultMap,p=s.specWithCurrentSubtrees,e.next=7,o(p,i,{baseDoc:c.url(),modelPropertyMacro:m,parameterMacro:v,requestInterceptor:y,responseInterceptor:_});case 7:return f=e.sent,d=f.errors,b=f.spec,r.allErrors().size&&n.clear({type:"thrown"}),Array.isArray(d)&&d.length>0&&(x=d.map(function(e){return e.line=e.fullPath?a(h,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",Object.defineProperty(e,"message",{enumerable:!0,value:e.message}),e}),n.newThrownErrBatch(x)),(0,g.default)(l,i,b),(0,g.default)(p,i,b),e.abrupt("return",{resultMap:l,specWithCurrentSubtrees:p});case 15:case"end":return e.stop()}},e,void 0)}));return function(t,n){return e.apply(this,arguments)}}(),s.default.resolve({resultMap:(c.specResolvedSubtree([])||(0,f.Map)()).toJS(),specWithCurrentSubtrees:c.specJson().toJS()}));case 13:b=e.sent,delete B.system,B=[],e.next=21;break;case 18:e.prev=18,e.t0=e.catch(10),console.error(e.t0);case 21:p.updateResolvedSubtree([],b.resultMap);case 22:case"end":return e.stop()}},e,void 0,[[10,18]])})),35);t.requestResolvedSubtree=function(e){return function(t){B.push(e),B.system=t,L()}};t.updateResolvedSubtree=function(e,t){return{type:R,payload:{path:e,value:t}}},t.invalidateResolvedSubtreeCache=function(){return{type:R,payload:{path:[],value:(0,f.Map)()}}},t.validateParams=function(e,t){return{type:E,payload:{pathMethod:e,isOAS3:t}}};t.setResponse=function(e,t,n){return{payload:{path:e,method:t,res:n},type:S}},t.setRequest=function(e,t,n){return{payload:{path:e,method:t,req:n},type:C}},t.setMutatedRequest=function(e,t,n){return{payload:{path:e,method:t,req:n},type:A}},t.logRequest=function(e){return{payload:e,type:D}},t.executeRequest=function(e){return function(t){var n=t.fn,r=t.specActions,i=t.specSelectors,s=t.getConfigs,u=t.oas3Selectors,l=e.pathName,c=e.method,p=e.operation,f=s(),m=f.requestInterceptor,v=f.responseInterceptor,g=p.toJS();if(e.contextUrl=(0,h.default)(i.url()).toString(),g&&g.operationId?e.operationId=g.operationId:g&&l&&c&&(e.operationId=n.opId(g,l,c)),i.isOAS3()){var _=l+":"+c;e.server=u.selectedServer(_)||u.selectedServer();var b=u.serverVariables({server:e.server,namespace:_}).toJS(),x=u.serverVariables({server:e.server}).toJS();e.serverVariables=(0,a.default)(b).length?b:x,e.requestContentType=u.requestContentType(l,c),e.responseContentType=u.responseContentType(l,c)||"*/*";var k=u.requestBodyValue(l,c);(0,y.isJSONObject)(k)?e.requestBody=JSON.parse(k):e.requestBody=k}var w=(0,o.default)({},e);w=n.buildRequest(w),r.setRequest(e.pathName,e.method,w);e.requestInterceptor=function(t){var n=m.apply(this,[t]),i=(0,o.default)({},n);return r.setMutatedRequest(e.pathName,e.method,i),n},e.responseInterceptor=v;var E=Date.now();return n.execute(e).then(function(t){t.duration=Date.now()-E,r.setResponse(e.pathName,e.method,t)}).catch(function(t){return r.setResponse(e.pathName,e.method,{error:!0,err:(0,d.default)(t)})})}};t.execute=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.path,n=e.method,o=(0,i.default)(e,["path","method"]);return function(e){var i=e.fn.fetch,a=e.specSelectors,s=e.specActions,u=a.specJsonWithResolvedSubtrees().toJS(),l=a.operationScheme(t,n),c=a.contentTypeValues([t,n]).toJS(),p=c.requestContentType,f=c.responseContentType,h=/xml/i.test(p),d=a.parameterValues([t,n],h).toJS();return s.executeRequest((0,r.default)({},o,{fetch:i,spec:u,pathName:t,method:n,parameters:d,requestContentType:p,scheme:l,responseContentType:f}))}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateBeforeExecute=t.canExecuteScheme=t.operationScheme=t.hasHost=t.parameterWithMeta=t.operationWithMeta=t.allowTryItOutFor=t.mutatedRequestFor=t.requestFor=t.responseFor=t.mutatedRequests=t.requests=t.responses=t.taggedOperations=t.operationsWithTags=t.tagDetails=t.tags=t.operationsWithRootInherited=t.schemes=t.host=t.basePath=t.definitions=t.findDefinition=t.securityDefinitions=t.security=t.produces=t.consumes=t.operations=t.paths=t.semver=t.version=t.externalDocs=t.info=t.isOAS3=t.spec=t.specJsonWithResolvedSubtrees=t.specResolvedSubtree=t.specResolved=t.specJson=t.specSource=t.specStr=t.url=t.lastError=void 0;var r,i=n(71),o=(r=i)&&r.__esModule?r:{default:r};t.getParameter=function(e,t,n,r){return t=t||[],e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.fromJS)([])).find(function(e){return u.Map.isMap(e)&&e.get("name")===n&&e.get("in")===r})||(0,u.Map)()},t.parameterValues=function(e,t,n){return t=t||[],D.apply(void 0,[e].concat((0,o.default)(t))).get("parameters",(0,u.List)()).reduce(function(e,t){var r=n&&"body"===t.get("in")?t.get("value_xml"):t.get("value");return e.set(t.get("in")+"."+t.get("name"),r)},(0,u.fromJS)({}))},t.parametersIncludeIn=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(u.List.isList(e))return e.some(function(e){return u.Map.isMap(e)&&e.get("in")===t})},t.parametersIncludeType=M,t.contentTypeValues=function(e,t){t=t||[];var n=h(e).getIn(["paths"].concat((0,o.default)(t)),(0,u.fromJS)({})),r=e.getIn(["meta","paths"].concat((0,o.default)(t)),(0,u.fromJS)({})),i=O(e,t),a=n.get("parameters")||new u.List,s=r.get("consumes_value")?r.get("consumes_value"):M(a,"file")?"multipart/form-data":M(a,"formData")?"application/x-www-form-urlencoded":void 0;return(0,u.fromJS)({requestContentType:s,responseContentType:i})},t.operationConsumes=function(e,t){return t=t||[],h(e).getIn(["paths"].concat((0,o.default)(t),["consumes"]),(0,u.fromJS)({}))},t.currentProducesFor=O;var a=n(60),s=n(9),u=n(7);var l=["get","put","post","delete","options","head","patch","trace"],c=function(e){return e||(0,u.Map)()},p=(t.lastError=(0,a.createSelector)(c,function(e){return e.get("lastError")}),t.url=(0,a.createSelector)(c,function(e){return e.get("url")}),t.specStr=(0,a.createSelector)(c,function(e){return e.get("spec")||""}),t.specSource=(0,a.createSelector)(c,function(e){return e.get("specSource")||"not-editor"}),t.specJson=(0,a.createSelector)(c,function(e){return e.get("json",(0,u.Map)())})),f=(t.specResolved=(0,a.createSelector)(c,function(e){return e.get("resolved",(0,u.Map)())}),t.specResolvedSubtree=function(e,t){return e.getIn(["resolvedSubtrees"].concat((0,o.default)(t)),void 0)},function e(t,n){return u.Map.isMap(t)&&u.Map.isMap(n)?n.get("$$ref")?n:(0,u.OrderedMap)().mergeWith(e,t,n):n}),h=t.specJsonWithResolvedSubtrees=(0,a.createSelector)(c,function(e){return(0,u.OrderedMap)().mergeWith(f,e.get("json"),e.get("resolvedSubtrees"))}),d=t.spec=function(e){return p(e)},m=(t.isOAS3=(0,a.createSelector)(d,function(){return!1}),t.info=(0,a.createSelector)(d,function(e){return P(e&&e.get("info"))})),v=(t.externalDocs=(0,a.createSelector)(d,function(e){return P(e&&e.get("externalDocs"))}),t.version=(0,a.createSelector)(m,function(e){return e&&e.get("version")})),g=(t.semver=(0,a.createSelector)(v,function(e){return/v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(e).slice(1)}),t.paths=(0,a.createSelector)(h,function(e){return e.get("paths")})),y=t.operations=(0,a.createSelector)(g,function(e){if(!e||e.size<1)return(0,u.List)();var t=(0,u.List)();return e&&e.forEach?(e.forEach(function(e,n){if(!e||!e.forEach)return{};e.forEach(function(e,r){l.indexOf(r)<0||(t=t.push((0,u.fromJS)({path:n,method:r,operation:e,id:r+"-"+n})))})}),t):(0,u.List)()}),_=t.consumes=(0,a.createSelector)(d,function(e){return(0,u.Set)(e.get("consumes"))}),b=t.produces=(0,a.createSelector)(d,function(e){return(0,u.Set)(e.get("produces"))}),x=(t.security=(0,a.createSelector)(d,function(e){return e.get("security",(0,u.List)())}),t.securityDefinitions=(0,a.createSelector)(d,function(e){return e.get("securityDefinitions")}),t.findDefinition=function(e,t){var n=e.getIn(["resolvedSubtrees","definitions",t],null),r=e.getIn(["json","definitions",t],null);return n||r||null},t.definitions=(0,a.createSelector)(d,function(e){return e.get("definitions")||(0,u.Map)()}),t.basePath=(0,a.createSelector)(d,function(e){return e.get("basePath")}),t.host=(0,a.createSelector)(d,function(e){return e.get("host")}),t.schemes=(0,a.createSelector)(d,function(e){return e.get("schemes",(0,u.Map)())}),t.operationsWithRootInherited=(0,a.createSelector)(y,_,b,function(e,t,n){return e.map(function(e){return e.update("operation",function(e){if(e){if(!u.Map.isMap(e))return;return e.withMutations(function(e){return e.get("consumes")||e.update("consumes",function(e){return(0,u.Set)(e).merge(t)}),e.get("produces")||e.update("produces",function(e){return(0,u.Set)(e).merge(n)}),e})}return(0,u.Map)()})})})),k=t.tags=(0,a.createSelector)(d,function(e){return e.get("tags",(0,u.List)())}),w=t.tagDetails=function(e,t){return(k(e)||(0,u.List)()).filter(u.Map.isMap).find(function(e){return e.get("name")===t},(0,u.Map)())},E=t.operationsWithTags=(0,a.createSelector)(x,k,function(e,t){return e.reduce(function(e,t){var n=(0,u.Set)(t.getIn(["operation","tags"]));return n.count()<1?e.update("default",(0,u.List)(),function(e){return e.push(t)}):n.reduce(function(e,n){return e.update(n,(0,u.List)(),function(e){return e.push(t)})},e)},t.reduce(function(e,t){return e.set(t.get("name"),(0,u.List)())},(0,u.OrderedMap)()))}),S=(t.taggedOperations=function(e){return function(t){var n=(0,t.getConfigs)(),r=n.tagsSorter,i=n.operationsSorter;return E(e).sortBy(function(e,t){return t},function(e,t){var n="function"==typeof r?r:s.sorters.tagsSorter[r];return n?n(e,t):null}).map(function(t,n){var r="function"==typeof i?i:s.sorters.operationsSorter[i],o=r?t.sort(r):t;return(0,u.Map)({tagDetails:w(e,n),operations:o})})}},t.responses=(0,a.createSelector)(c,function(e){return e.get("responses",(0,u.Map)())})),C=t.requests=(0,a.createSelector)(c,function(e){return e.get("requests",(0,u.Map)())}),A=t.mutatedRequests=(0,a.createSelector)(c,function(e){return e.get("mutatedRequests",(0,u.Map)())}),D=(t.responseFor=function(e,t,n){return S(e).getIn([t,n],null)},t.requestFor=function(e,t,n){return C(e).getIn([t,n],null)},t.mutatedRequestFor=function(e,t,n){return A(e).getIn([t,n],null)},t.allowTryItOutFor=function(){return!0},t.operationWithMeta=function(e,t,n){var r=h(e).getIn(["paths",t,n],(0,u.Map)()),i=e.getIn(["meta","paths",t,n],(0,u.Map)()),o=r.get("parameters",(0,u.List)()).map(function(e){return(0,u.Map)().merge(e,i.getIn(["parameters",e.get("name")+"."+e.get("in")]))});return(0,u.Map)().merge(r,i).set("parameters",o)});t.parameterWithMeta=function(e,t,n,r){var i=h(e).getIn(["paths"].concat((0,o.default)(t),["parameters"]),(0,u.Map)()),a=e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.Map)());return i.map(function(e){return(0,u.Map)().merge(e,a.get(e.get("name")+"."+e.get("in")))}).find(function(e){return e.get("in")===r&&e.get("name")===n},(0,u.Map)())};t.hasHost=(0,a.createSelector)(d,function(e){var t=e.get("host");return"string"==typeof t&&t.length>0&&"/"!==t[0]});function M(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(u.List.isList(e))return e.some(function(e){return u.Map.isMap(e)&&e.get("type")===t})}function O(e,t){t=t||[];var n=h(e).getIn(["paths"].concat((0,o.default)(t)),null);if(null!==n){var r=e.getIn(["meta","paths"].concat((0,o.default)(t),["produces_value"]),null),i=n.getIn(["produces",0],null);return r||i||"application/json"}}var T=t.operationScheme=function(e,t,n){var r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),i=Array.isArray(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||i||""};t.canExecuteScheme=function(e,t,n){return["http","https"].indexOf(T(e,t,n))>-1},t.validateBeforeExecute=function(e,t){t=t||[];var n=!0;return e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.fromJS)([])).forEach(function(e){var t=e.get("errors");t&&t.count()&&(n=!1)}),n};function P(e){return u.Map.isMap(e)?e:new u.Map}},function(e,t,n){"use strict";function r(e){switch(e._type){case"document":case"block_quote":case"list":case"item":case"paragraph":case"heading":case"emph":case"strong":case"link":case"image":case"custom_inline":case"custom_block":return!0;default:return!1}}var i=function(e,t){this.current=e,this.entering=!0===t},o=function(){var e=this.current,t=this.entering;if(null===e)return null;var n=r(e);return t&&n?e._firstChild?(this.current=e._firstChild,this.entering=!0):this.entering=!1:e===this.root?this.current=null:null===e._next?(this.current=e._parent,this.entering=!1):(this.current=e._next,this.entering=!0),{entering:t,node:e}},a=function(e,t){this._type=e,this._parent=null,this._firstChild=null,this._lastChild=null,this._prev=null,this._next=null,this._sourcepos=t,this._lastLineBlank=!1,this._open=!0,this._string_content=null,this._literal=null,this._listData={},this._info=null,this._destination=null,this._title=null,this._isFenced=!1,this._fenceChar=null,this._fenceLength=0,this._fenceOffset=null,this._level=null,this._onEnter=null,this._onExit=null},s=a.prototype;Object.defineProperty(s,"isContainer",{get:function(){return r(this)}}),Object.defineProperty(s,"type",{get:function(){return this._type}}),Object.defineProperty(s,"firstChild",{get:function(){return this._firstChild}}),Object.defineProperty(s,"lastChild",{get:function(){return this._lastChild}}),Object.defineProperty(s,"next",{get:function(){return this._next}}),Object.defineProperty(s,"prev",{get:function(){return this._prev}}),Object.defineProperty(s,"parent",{get:function(){return this._parent}}),Object.defineProperty(s,"sourcepos",{get:function(){return this._sourcepos}}),Object.defineProperty(s,"literal",{get:function(){return this._literal},set:function(e){this._literal=e}}),Object.defineProperty(s,"destination",{get:function(){return this._destination},set:function(e){this._destination=e}}),Object.defineProperty(s,"title",{get:function(){return this._title},set:function(e){this._title=e}}),Object.defineProperty(s,"info",{get:function(){return this._info},set:function(e){this._info=e}}),Object.defineProperty(s,"level",{get:function(){return this._level},set:function(e){this._level=e}}),Object.defineProperty(s,"listType",{get:function(){return this._listData.type},set:function(e){this._listData.type=e}}),Object.defineProperty(s,"listTight",{get:function(){return this._listData.tight},set:function(e){this._listData.tight=e}}),Object.defineProperty(s,"listStart",{get:function(){return this._listData.start},set:function(e){this._listData.start=e}}),Object.defineProperty(s,"listDelimiter",{get:function(){return this._listData.delimiter},set:function(e){this._listData.delimiter=e}}),Object.defineProperty(s,"onEnter",{get:function(){return this._onEnter},set:function(e){this._onEnter=e}}),Object.defineProperty(s,"onExit",{get:function(){return this._onExit},set:function(e){this._onExit=e}}),a.prototype.appendChild=function(e){e.unlink(),e._parent=this,this._lastChild?(this._lastChild._next=e,e._prev=this._lastChild,this._lastChild=e):(this._firstChild=e,this._lastChild=e)},a.prototype.prependChild=function(e){e.unlink(),e._parent=this,this._firstChild?(this._firstChild._prev=e,e._next=this._firstChild,this._firstChild=e):(this._firstChild=e,this._lastChild=e)},a.prototype.unlink=function(){this._prev?this._prev._next=this._next:this._parent&&(this._parent._firstChild=this._next),this._next?this._next._prev=this._prev:this._parent&&(this._parent._lastChild=this._prev),this._parent=null,this._next=null,this._prev=null},a.prototype.insertAfter=function(e){e.unlink(),e._next=this._next,e._next&&(e._next._prev=e),e._prev=this,this._next=e,e._parent=this._parent,e._next||(e._parent._lastChild=e)},a.prototype.insertBefore=function(e){e.unlink(),e._prev=this._prev,e._prev&&(e._prev._next=e),e._next=this,this._prev=e,e._parent=this._parent,e._prev||(e._parent._firstChild=e)},a.prototype.walker=function(){return new function(e){return{current:e,root:e,entering:!0,next:o,resumeAt:i}}(this)},e.exports=a},function(e,t){e.exports=function(e,t,n,r){if(!(e instanceof t)||void 0!==r&&r in e)throw TypeError(n+": incorrect invocation!");return e}},function(e,t,n){var r=n(53),i=n(180),o=n(75),a=n(128),s=n(584);e.exports=function(e,t){var n=1==e,u=2==e,l=3==e,c=4==e,p=6==e,f=5==e||p,h=t||s;return function(t,s,d){for(var m,v,g=o(t),y=i(g),_=r(s,d,3),b=a(y.length),x=0,k=n?h(t,b):u?h(t,0):void 0;b>x;x++)if((f||x in y)&&(v=_(m=y[x],x,g),e))if(n)k[x]=v;else if(v)switch(e){case 3:return!0;case 5:return m;case 6:return x;case 2:k.push(m)}else if(c)return!1;return p?-1:l||c?c:k}}},function(e,t,n){var r=n(98),i=n(21)("toStringTag"),o="Arguments"==r(function(){return arguments}());e.exports=function(e){var t,n,a;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),i))?n:o?r(t):"Object"==(a=r(t))&&"function"==typeof t.callee?"Arguments":a}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){var r=n(29),i=n(20).document,o=r(i)&&r(i.createElement);e.exports=function(e){return o?i.createElement(e):{}}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){var r=n(98);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==r(e)?e.split(""):Object(e)}},function(e,t,n){"use strict";var r=n(97);e.exports.f=function(e){return new function(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r}),this.resolve=r(t),this.reject=r(n)}(e)}},function(e,t,n){var r=n(37),i=n(593),o=n(179),a=n(186)("IE_PROTO"),s=function(){},u=function(){var e,t=n(178)("iframe"),r=o.length;for(t.style.display="none",n(329).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + + + + diff --git a/tox.ini b/tox.ini index 9b5b2b0c2..a67da08a6 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ basepython = python2 skip_install = true deps = -rrequirements/test.txt commands = - py.test + py.test {posargs} setenv = PYTHONPATH = ./storage_service DJANGO_SETTINGS_MODULE = storage_service.settings.test @@ -31,7 +31,7 @@ basepython = python3 skip_install = true deps = -rrequirements/test.txt commands = - py.test + py.test {posargs} setenv = PYTHONPATH = ./storage_service DJANGO_SETTINGS_MODULE = storage_service.settings.test