Ejemplo n.º 1
0
    def describe_processes(self, headers, args, process=None):
        """
        Provide processes metadata

        :param headers: dict of HTTP headers
        :param args: dict of HTTP request parameters
        :param process: name of process

        :returns: tuple of headers, status code, content
        """

        headers_ = HEADERS.copy()

        format_ = check_format(args, headers)

        if format_ is not None and format_ not in FORMATS:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid format'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        processes_config = self.config['processes']

        if process is not None:
            if process not in processes_config.keys():
                exception = {
                    'code': 'NotFound',
                    'description': 'identifier not found'
                }
                LOGGER.error(exception)
                return headers_, 404, json.dumps(exception)

            p = load_plugin('process', processes_config[process]['processor'])
            p.metadata['jobControlOptions'] = ['sync-execute']
            p.metadata['outputTransmission'] = ['value']
            response = p.metadata
        else:
            processes = []
            for k, v in processes_config.items():
                p = load_plugin('process', processes_config[k]['processor'])
                p.metadata['jobControlOptions'] = ['sync-execute']
                p.metadata['outputTransmission'] = ['value']
                processes.append(p.metadata)
            response = {'processes': processes}

        if format_ == 'html':  # render
            headers_['Content-Type'] = 'text/html'
            if process is not None:
                response = _render_j2_template(self.config, 'process.html',
                                               p.metadata)
            else:
                response = _render_j2_template(self.config, 'processes.html',
                                               {'processes': processes})

            return headers_, 200, response

        return headers_, 200, json.dumps(response)
Ejemplo n.º 2
0
    def execute_process(self, headers, args, data, process):
        """
        Execute process

        :param headers: dict of HTTP headers
        :param args: dict of HTTP request parameters
        :param data: process data
        :param process: name of process

        :returns: tuple of headers, status code, content
        """

        headers_ = HEADERS.copy()

        data_dict = {}
        response = {}

        if not data:
            exception = {
                'code': 'MissingParameterValue',
                'description': 'missing request data'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        processes = self.config.get('processes', {})

        if process not in processes:
            exception = {
                'code': 'NotFound',
                'description': 'identifier not found'
            }
            LOGGER.error(exception)
            return headers_, 404, json.dumps(exception)

        p = load_plugin('process',
                        processes[process]['processor'])

        data_ = json.loads(data)
        for input_ in data_['inputs']:
            data_dict[input_['id']] = input_['value']

        try:
            outputs = p.execute(data_dict)
            m = p.metadata
            if 'raw' in args and str2bool(args['raw']):
                headers_['Content-Type'] = \
                    m['outputs'][0]['output']['formats'][0]['mimeType']
                response = outputs
            else:
                response['outputs'] = outputs
            return headers_, 201, json.dumps(response)
        except Exception as err:
            exception = {
                'code': 'InvalidParameterValue',
                'description': str(err)
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)
Ejemplo n.º 3
0
    def describe_processes(self, headers, args, process=None):
        """
        Provide processes metadata

        :param headers: dict of HTTP headers
        :param args: dict of HTTP request parameters
        :param process: name of process

        :returns: tuple of headers, status code, content
        """

        headers_ = HEADERS.copy()

        processes_config = self.config['processes']

        if process is not None:
            if process not in processes_config.keys():
                exception = {
                    'code': 'NotFound',
                    'description': 'identifier not found'
                }
                LOGGER.error(exception)
                return headers_, 404, json.dumps(exception)

            p = load_plugin('process', processes_config[process]['processor'])
            p.metadata['jobControlOptions'] = ['sync-execute']
            p.metadata['outputTransmission'] = ['value']
            response = p.metadata
        else:
            processes = []
            for k, v in processes_config.items():
                p = load_plugin('process', processes_config[k]['processor'])
                p.metadata['jobControlOptions'] = ['sync-execute']
                p.metadata['outputTransmission'] = ['value']
                processes.append(p.metadata)
            response = {'processes': processes}

        return headers_, 200, json.dumps(response)
Ejemplo n.º 4
0
    def get_collection_item(self, headers_, format_, dataset, identifier):
        """
        Get a single feature

        :param headers_: copy of HEADERS object
        :param format_: format of requests,
                        pre checked by pre_process decorator
        :param dataset: dataset name
        :param identifier: feature identifier

        :returns: tuple of headers, status code, content
        """

        if format_ is not None and format_ not in FORMATS:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid format'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Processing query parameters')

        if dataset not in self.config['datasets'].keys():
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid feature collection'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Loading provider')
        p = load_plugin('provider',
                        self.config['datasets'][dataset]['provider'])

        try:
            LOGGER.debug('Fetching id {}'.format(identifier))
            content = p.get(identifier)
        except ProviderQueryError as err:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'query error (check logs)'
            }
            LOGGER.error(err)
            return headers_, 500, json.dumps(exception)
        except ProviderGenericError as err:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'generic error (check logs)'
            }
            LOGGER.error(err)
            return headers_, 500, json.dumps(exception)

        if content is None:
            exception = {
                'code': 'NotFound',
                'description': 'identifier not found'
            }
            LOGGER.error(exception)
            return headers_, 404, json.dumps(exception)

        content['links'] = [{
            'rel':
            'self' if not format_ or format_ == 'json' else 'alternate',
            'type':
            'application/geo+json',
            'title':
            'This document as GeoJSON',
            'href':
            '{}/collections/{}/items/{}?f=json'.format(
                self.config['server']['url'], dataset, identifier)
        }, {
            'rel':
            'self' if format_ == 'jsonld' else 'alternate',
            'type':
            'application/ld+json',
            'title':
            'This document as RDF (JSON-LD)',
            'href':
            '{}/collections/{}/items/{}?f=jsonld'.format(
                self.config['server']['url'], dataset, identifier)
        }, {
            'rel':
            'self' if format_ == 'html' else 'alternate',
            'type':
            'text/html',
            'title':
            'This document as HTML',
            'href':
            '{}/collections/{}/items/{}?f=html'.format(
                self.config['server']['url'], dataset, identifier)
        }, {
            'rel':
            'collection',
            'type':
            'application/json',
            'title':
            self.config['datasets'][dataset]['title'],
            'href':
            '{}/collections/{}'.format(self.config['server']['url'], dataset)
        }, {
            'rel':
            'prev',
            'type':
            'application/geo+json',
            'href':
            '{}/collections/{}/items/{}'.format(self.config['server']['url'],
                                                dataset, identifier)
        }, {
            'rel':
            'next',
            'type':
            'application/geo+json',
            'href':
            '{}/collections/{}/items/{}'.format(self.config['server']['url'],
                                                dataset, identifier)
        }]

        if format_ == 'html':  # render
            headers_['Content-Type'] = 'text/html'

            content['title'] = self.config['datasets'][dataset]['title']
            content = render_j2_template(self.config, 'item.html', content)
            return headers_, 200, content
        elif format_ == 'jsonld':
            headers_['Content-Type'] = 'application/ld+json'
            content = geojson2geojsonld(self.config,
                                        content,
                                        dataset,
                                        identifier=identifier)
            return headers_, 200, content

        return headers_, 200, json.dumps(content, default=json_serial)
Ejemplo n.º 5
0
    def get_collection_items(self, headers, args, dataset, pathinfo=None):
        """
        Queries feature collection

        :param headers: dict of HTTP headers
        :param args: dict of HTTP request parameters
        :param dataset: dataset name
        :param pathinfo: path location

        :returns: tuple of headers, status code, content
        """

        headers_ = HEADERS.copy()

        properties = []
        reserved_fieldnames = [
            'bbox', 'f', 'limit', 'startindex', 'resulttype', 'datetime',
            'sortby'
        ]
        formats = FORMATS
        formats.extend(f.lower() for f in PLUGINS['formatter'].keys())

        if dataset not in self.config['datasets'].keys():
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid feature collection'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception, default=json_serial)

        format_ = check_format(args, headers)

        if format_ is not None and format_ not in formats:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid format'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Processing query parameters')

        LOGGER.debug('Processing startindex parameter')
        try:
            startindex = int(args.get('startindex'))
            if startindex < 0:
                exception = {
                    'code':
                    'InvalidParameterValue',
                    'description':
                    'startindex value should be positive ' + 'or zero'
                }
                LOGGER.error(exception)
                return headers_, 400, json.dumps(exception)
        except (TypeError) as err:
            LOGGER.warning(err)
            startindex = 0
        except ValueError as err:
            LOGGER.warning(err)
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'startindex value should be an integer'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Processing limit parameter')
        try:
            limit = int(args.get('limit'))
            # TODO: We should do more validation, against the min and max
            # allowed by the server configuration
            if limit <= 0:
                exception = {
                    'code': 'InvalidParameterValue',
                    'description': 'limit value should be strictly positive'
                }
                LOGGER.error(exception)
                return headers_, 400, json.dumps(exception)
        except TypeError as err:
            LOGGER.warning(err)
            limit = int(self.config['server']['limit'])
        except ValueError as err:
            LOGGER.warning(err)
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'limit value should be an integer'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        resulttype = args.get('resulttype') or 'results'

        LOGGER.debug('Processing bbox parameter')
        try:
            bbox = args.get('bbox').split(',')
            if len(bbox) != 4:
                exception = {
                    'code': 'InvalidParameterValue',
                    'description': 'bbox values should be minx,miny,maxx,maxy'
                }
                LOGGER.error(exception)
                return headers_, 400, json.dumps(exception)
        except AttributeError:
            bbox = []
        try:
            bbox = [float(c) for c in bbox]
        except ValueError:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'bbox values must be numbers'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Processing datetime parameter')
        # TODO: pass datetime to query as a `datetime` object
        # we would need to ensure partial dates work accordingly
        # as well as setting '..' values to `None` so that underlying
        # providers can just assume a `datetime.datetime` object
        #
        # NOTE: needs testing when passing partials from API to backend
        datetime_ = args.get('datetime')
        datetime_invalid = False

        if (datetime_ is not None
                and 'temporal' in self.config['datasets'][dataset]['extents']):
            te = self.config['datasets'][dataset]['extents']['temporal']

            if te['begin'].tzinfo is None:
                te['begin'] = te['begin'].replace(tzinfo=pytz.UTC)
            if te['end'].tzinfo is None:
                te['end'] = te['end'].replace(tzinfo=pytz.UTC)

            if '/' in datetime_:  # envelope
                LOGGER.debug('detected time range')
                LOGGER.debug('Validating time windows')
                datetime_begin, datetime_end = datetime_.split('/')
                if datetime_begin != '..':
                    datetime_begin = dateparse(datetime_begin)
                    if datetime_begin.tzinfo is None:
                        datetime_begin = datetime_begin.replace(
                            tzinfo=pytz.UTC)

                if datetime_end != '..':
                    datetime_end = dateparse(datetime_end)
                    if datetime_end.tzinfo is None:
                        datetime_end = datetime_end.replace(tzinfo=pytz.UTC)

                if te['begin'] is not None and datetime_begin != '..':
                    if datetime_begin < te['begin']:
                        datetime_invalid = True

                if te['end'] is not None and datetime_end != '..':
                    if datetime_end > te['end']:
                        datetime_invalid = True

            else:  # time instant
                datetime__ = dateparse(datetime_)
                if datetime__ != '..':
                    if datetime__.tzinfo is None:
                        datetime__ = datetime__.replace(tzinfo=pytz.UTC)
                LOGGER.debug('detected time instant')
                if te['begin'] is not None and datetime__ != '..':
                    if datetime__ < te['begin']:
                        datetime_invalid = True
                if te['end'] is not None and datetime__ != '..':
                    if datetime__ > te['end']:
                        datetime_invalid = True

        if datetime_invalid:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'datetime parameter out of range'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Loading provider')
        try:
            p = load_plugin('provider',
                            self.config['datasets'][dataset]['provider'])
        except ProviderConnectionError:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'connection error (check logs)'
            }
            LOGGER.error(exception)
            return headers_, 500, json.dumps(exception)
        except ProviderQueryError:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'query error (check logs)'
            }
            LOGGER.error(exception)
            return headers_, 500, json.dumps(exception)

        LOGGER.debug('processing property parameters')
        for k, v in args.items():
            if k not in reserved_fieldnames and k not in p.fields.keys():
                exception = {
                    'code': 'InvalidParameterValue',
                    'description': 'unknown query parameter'
                }
                LOGGER.error(exception)
                return headers_, 400, json.dumps(exception)
            elif k not in reserved_fieldnames and k in p.fields.keys():
                LOGGER.debug('Add property filter {}={}'.format(k, v))
                properties.append((k, v))

        LOGGER.debug('processing sort parameter')
        val = args.get('sortby')

        if val is not None:
            sortby = []
            sorts = val.split(',')
            for s in sorts:
                if ':' in s:
                    prop, order = s.split(':')
                    if order not in ['A', 'D']:
                        exception = {
                            'code': 'InvalidParameterValue',
                            'description': 'sort order should be A or D'
                        }
                        LOGGER.error(exception)
                        return headers_, 400, json.dumps(exception)
                    sortby.append({'property': prop, 'order': order})
                else:
                    sortby.append({'property': s, 'order': 'A'})
            for s in sortby:
                if s['property'] not in p.fields.keys():
                    exception = {
                        'code': 'InvalidParameterValue',
                        'description': 'bad sort property'
                    }
                    LOGGER.error(exception)
                    return headers_, 400, json.dumps(exception)
        else:
            sortby = []

        LOGGER.debug('Querying provider')
        LOGGER.debug('startindex: {}'.format(startindex))
        LOGGER.debug('limit: {}'.format(limit))
        LOGGER.debug('resulttype: {}'.format(resulttype))
        LOGGER.debug('sortby: {}'.format(sortby))

        try:
            content = p.query(startindex=startindex,
                              limit=limit,
                              resulttype=resulttype,
                              bbox=bbox,
                              datetime=datetime_,
                              properties=properties,
                              sortby=sortby)
        except ProviderConnectionError as err:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'connection error (check logs)'
            }
            LOGGER.error(err)
            return headers_, 500, json.dumps(exception)
        except ProviderQueryError as err:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'query error (check logs)'
            }
            LOGGER.error(err)
            return headers_, 500, json.dumps(exception)
        except ProviderGenericError as err:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'generic error (check logs)'
            }
            LOGGER.error(err)
            return headers_, 500, json.dumps(exception)

        serialized_query_params = ''
        for k, v in args.items():
            if k not in ('f', 'startindex'):
                serialized_query_params += '&'
                serialized_query_params += urllib.parse.quote(k, safe='')
                serialized_query_params += '='
                serialized_query_params += urllib.parse.quote(str(v), safe=',')

        content['links'] = [{
            'type':
            'application/geo+json',
            'rel':
            'self' if not format_ or format_ == 'json' else 'alternate',
            'title':
            'This document as GeoJSON',
            'href':
            '{}/collections/{}/items?f=json{}'.format(
                self.config['server']['url'], dataset, serialized_query_params)
        }, {
            'rel':
            'self' if format_ == 'jsonld' else 'alternate',
            'type':
            'application/ld+json',
            'title':
            'This document as RDF (JSON-LD)',
            'href':
            '{}/collections/{}/items?f=jsonld{}'.format(
                self.config['server']['url'], dataset, serialized_query_params)
        }, {
            'type':
            'text/html',
            'rel':
            'self' if format_ == 'html' else 'alternate',
            'title':
            'This document as HTML',
            'href':
            '{}/collections/{}/items?f=html{}'.format(
                self.config['server']['url'], dataset, serialized_query_params)
        }]

        if startindex > 0:
            prev = max(0, startindex - limit)
            content['links'].append({
                'type':
                'application/geo+json',
                'rel':
                'prev',
                'title':
                'items (prev)',
                'href':
                '{}/collections/{}/items?startindex={}{}'.format(
                    self.config['server']['url'], dataset, prev,
                    serialized_query_params)
            })

        if len(content['features']) == limit:
            next_ = startindex + limit
            content['links'].append({
                'type':
                'application/geo+json',
                'rel':
                'next',
                'title':
                'items (next)',
                'href':
                '{}/collections/{}/items?startindex={}{}'.format(
                    self.config['server']['url'], dataset, next_,
                    serialized_query_params)
            })

        content['links'].append({
            'type':
            'application/json',
            'title':
            self.config['datasets'][dataset]['title'],
            'rel':
            'collection',
            'href':
            '{}/collections/{}'.format(self.config['server']['url'], dataset)
        })

        content['timeStamp'] = datetime.utcnow().strftime(
            '%Y-%m-%dT%H:%M:%S.%fZ')

        if format_ == 'html':  # render
            headers_['Content-Type'] = 'text/html'

            # For constructing proper URIs to items
            if pathinfo:
                path_info = '/'.join([
                    self.config['server']['url'].rstrip('/'),
                    pathinfo.strip('/')
                ])
            else:
                path_info = '/'.join([
                    self.config['server']['url'].rstrip('/'),
                    headers.environ['PATH_INFO'].strip('/')
                ])

            content['items_path'] = path_info
            content['dataset_path'] = '/'.join(path_info.split('/')[:-1])
            content['collections_path'] = '/'.join(path_info.split('/')[:-2])
            content['startindex'] = startindex

            content = render_j2_template(self.config, 'items.html', content)
            return headers_, 200, content
        elif format_ == 'csv':  # render
            formatter = load_plugin('formatter', {'name': 'CSV', 'geom': True})

            content = formatter.write(
                data=content,
                options={
                    'provider_def':
                    self.config['datasets'][dataset]['provider']
                })

            headers_['Content-Type'] = '{}; charset={}'.format(
                formatter.mimetype, self.config['server']['encoding'])

            cd = 'attachment; filename="{}.csv"'.format(dataset)
            headers_['Content-Disposition'] = cd

            return headers_, 200, content
        elif format_ == 'jsonld':
            headers_['Content-Type'] = 'application/ld+json'
            content = geojson2geojsonld(self.config, content, dataset)
            return headers_, 200, content

        return headers_, 200, json.dumps(content, default=json_serial)
Ejemplo n.º 6
0
def get_oas_30(cfg):
    """
    Generates an OpenAPI 3.0 Document

    :param cfg: configuration object

    :returns: OpenAPI definition YAML dict
    """

    paths = {}

    osl = get_ogc_schemas_location(cfg['server'])
    OPENAPI_YAML['oapif'] = os.path.join(osl, 'ogcapi/features/part1/1.0/openapi/ogcapi-features-1.yaml')  # noqa

    LOGGER.debug('setting up server info')
    oas = {
        'openapi': '3.0.2',
        'tags': []
    }
    info = {
        'title': cfg['metadata']['identification']['title'],
        'description': cfg['metadata']['identification']['description'],
        'x-keywords': cfg['metadata']['identification']['keywords'],
        'termsOfService':
            cfg['metadata']['identification']['terms_of_service'],
        'contact': {
            'name': cfg['metadata']['provider']['name'],
            'url': cfg['metadata']['provider']['url'],
            'email': cfg['metadata']['contact']['email']
        },
        'license': {
            'name': cfg['metadata']['license']['name'],
            'url': cfg['metadata']['license']['url']
        },
        'version': '3.0.2'
    }
    oas['info'] = info

    oas['servers'] = [{
        'url': cfg['server']['url'],
        'description': cfg['metadata']['identification']['description']
    }]

    paths['/'] = {
        'get': {
            'summary': 'Landing page',
            'description': 'Landing page',
            'tags': ['server'],
            'operationId': 'getLandingPage',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '{}#/components/responses/LandingPage'.format(OPENAPI_YAML['oapif'])},  # noqa
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
            }
        }
    }

    paths['/openapi'] = {
        'get': {
            'summary': 'This document',
            'description': 'This document',
            'tags': ['server'],
            'operationId': 'getOpenapi',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '#/components/responses/200'},
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                'default': {'$ref': '#/components/responses/default'}
            }
        }
    }

    paths['/conformance'] = {
        'get': {
            'summary': 'API conformance definition',
            'description': 'API conformance definition',
            'tags': ['server'],
            'operationId': 'getConformanceDeclaration',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '{}#/components/responses/ConformanceDeclaration'.format(OPENAPI_YAML['oapif'])},  # noqa
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
            }
        }
    }

    paths['/collections'] = {
        'get': {
            'summary': 'Collections',
            'description': 'Collections',
            'tags': ['server'],
            'operationId': 'getCollections',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '{}#/components/responses/Collections'.format(OPENAPI_YAML['oapif'])},  # noqa
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
            }
        }
    }

    oas['tags'].append({
            'name': 'server',
            'description': cfg['metadata']['identification']['description'],
            'externalDocs': {
                'description': 'information',
                'url': cfg['metadata']['identification']['url']}
        }
    )
    oas['tags'].append({
            'name': 'stac',
            'description': 'SpatioTemporal Asset Catalog'
        }
    )

    oas['components'] = {
        'responses': {
            '200': {
                'description': 'successful operation',
            },
            'default': {
                'description': 'Unexpected error',
                'content': gen_media_type_object('application/json', 'oapip', 'schemas/exception.yaml')  # noqa
            },
            'Queryables': {
                'description': 'successful queryables operation',
                'content': {
                    'application/json': {
                        'schema': {'$ref': '#/components/schemas/queryables'}
                    }
                }
            }
        },
        'parameters': {
            'f': {
                'name': 'f',
                'in': 'query',
                'description': 'The optional f parameter indicates the output format which the server shall provide as part of the response document.  The default format is GeoJSON.',  # noqa
                'required': False,
                'schema': {
                    'type': 'string',
                    'enum': ['json', 'html', 'jsonld'],
                    'default': 'json'
                },
                'style': 'form',
                'explode': False
            },
            'sortby': {
                'name': 'sortby',
                'in': 'query',
                'description': 'The optional sortby parameter indicates the sort property and order on which the server shall present results in the response document using the convention `sortby=PROPERTY:X`, where `PROPERTY` is the sort property and `X` is the sort order (`A` is ascending, `D` is descending). Sorting by multiple properties is supported by providing a comma-separated list.',  # noqa
                'required': False,
                'schema': {
                    'type': 'string',
                },
                'style': 'form',
                'explode': False
            },
            'startindex': {
                'name': 'startindex',
                'in': 'query',
                'description': 'The optional startindex parameter indicates the index within the result set from which the server shall begin presenting results in the response document.  The first element has an index of 0 (default).',  # noqa
                'required': False,
                'schema': {
                    'type': 'integer',
                    'minimum': 0,
                    'default': 0
                },
                'style': 'form',
                'explode': False
            }
        },
        'schemas': {
            # TODO: change this schema once OGC will definitively publish it
            'queryable': {
                'type': 'object',
                'required': [
                    'queryable',
                    'type'
                ],
                'properties': {
                    'queryable': {
                        'description': 'the token that may be used in a CQL predicate', # noqa
                        'type': 'string'
                    },
                    'title': {
                        'description': 'a human readable title for the queryable', # noqa
                        'type': 'string'
                    },
                    'description': {
                        'description': 'a human-readable narrative describing the queryable', # noqa
                        'type': 'string'
                    },
                    'language': {
                        'description': 'the language used for the title and description', # noqa
                        'type': 'string',
                        'default': [
                            'en'
                        ]
                    },
                    'type': {
                        'description': 'the data type of the queryable', # noqa
                        'type': 'string'
                    },
                    'type-ref': {
                        'description': 'a reference to the formal definition of the type', # noqa
                        'type': 'string',
                        'format': 'url'
                    }
                }
            },
            'queryables': {
                'type': 'object',
                'required': [
                    'queryables'
                ],
                'properties': {
                    'queryables': {
                        'type': 'array',
                        'items': {'$ref': '#/components/schemas/queryable'}
                    }
                }
            }
        }
    }

    items_f = deepcopy(oas['components']['parameters']['f'])
    items_f['schema']['enum'].append('csv')

    LOGGER.debug('setting up datasets')
    collections = filter_dict_by_key_value(cfg['resources'],
                                           'type', 'collection')

    for k, v in collections.items():
        collection_name_path = '/collections/{}'.format(k)
        tag = {
            'name': k,
            'description': v['description'],
            'externalDocs': {}
        }
        for link in v['links']:
            if link['type'] == 'information':
                tag['externalDocs']['description'] = link['type']
                tag['externalDocs']['url'] = link['url']
                break
        if len(tag['externalDocs']) == 0:
            del tag['externalDocs']

        oas['tags'].append(tag)

        paths[collection_name_path] = {
            'get': {
                'summary': 'Get collection metadata'.format(v['title']),  # noqa
                'description': v['description'],
                'tags': [k],
                'operationId': 'describe{}Collection'.format(k.capitalize()),
                'parameters': [
                    {'$ref': '#/components/parameters/f'}
                ],
                'responses': {
                    '200': {'$ref': '{}#/components/responses/Collection'.format(OPENAPI_YAML['oapif'])},  # noqa
                    '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                    '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                    '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                }
            }
        }

        LOGGER.debug('setting up feature endpoints')
        try:
            p = load_plugin('provider', get_provider_by_type(
                            collections[k]['providers'], 'feature'))

            items_path = '{}/items'.format(collection_name_path)

            paths[items_path] = {
                'get': {
                    'summary': 'Get {} items'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}Features'.format(k.capitalize()),
                    'parameters': [
                        items_f,
                        {'$ref': '{}#/components/parameters/bbox'.format(OPENAPI_YAML['oapif'])},  # noqa
                        {'$ref': '{}#/components/parameters/limit'.format(OPENAPI_YAML['oapif'])},  # noqa
                        {'$ref': '#/components/parameters/sortby'},
                        {'$ref': '#/components/parameters/startindex'}
                    ],
                    'responses': {
                        '200': {'$ref': '{}#/components/responses/Features'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }

            if p.fields:
                queryables_path = '{}/queryables'.format(collection_name_path)

                paths[queryables_path] = {
                    'get': {
                        'summary': 'Get {} queryables'.format(v['title']),
                        'description': v['description'],
                        'tags': [k],
                        'operationId': 'get{}Queryables'.format(
                            k.capitalize()),
                        'parameters': [
                            items_f,
                        ],
                        'responses': {
                            '200': {'$ref': '#/components/responses/Queryables'},  # noqa
                            '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                            '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                            '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                        }
                    }
                }

            if p.time_field is not None:
                paths[items_path]['get']['parameters'].append(
                    {'$ref': '{}#/components/parameters/datetime'.format(OPENAPI_YAML['oapif'])})  # noqa

            for field, type in p.fields.items():

                if p.properties and field not in p.properties:
                    LOGGER.debug('Provider specified not to advertise property')  # noqa
                    continue

                if type == 'date':
                    schema = {
                        'type': 'string',
                        'format': 'date'
                    }
                elif type == 'float':
                    schema = {
                        'type': 'number',
                        'format': 'float'
                    }
                elif type == 'long':
                    schema = {
                        'type': 'integer',
                        'format': 'int64'
                    }
                else:
                    schema = {
                        'type': type
                    }

                path_ = '{}/items'.format(collection_name_path)
                paths['{}'.format(path_)]['get']['parameters'].append({
                    'name': field,
                    'in': 'query',
                    'required': False,
                    'schema': schema,
                    'style': 'form',
                    'explode': False
                })

            paths['{}/items/{{featureId}}'.format(collection_name_path)] = {
                'get': {
                    'summary': 'Get {} item by id'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}Feature'.format(k.capitalize()),
                    'parameters': [
                        {'$ref': '{}#/components/parameters/featureId'.format(OPENAPI_YAML['oapif'])},  # noqa
                        {'$ref': '#/components/parameters/f'}
                    ],
                    'responses': {
                        '200': {'$ref': '{}#/components/responses/Feature'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }
        except ProviderTypeError:
            LOGGER.debug('collection is not feature based')

        LOGGER.debug('setting up coverage endpoints')
        try:
            load_plugin('provider', get_provider_by_type(
                        collections[k]['providers'], 'coverage'))

            coverage_path = '{}/coverage'.format(collection_name_path)

            paths[coverage_path] = {
                'get': {
                    'summary': 'Get {} coverage'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}Coverage'.format(k.capitalize()),
                    'parameters': [
                        items_f,
                    ],
                    'responses': {
                        '200': {'$ref': '{}#/components/responses/Features'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }

            coverage_domainset_path = '{}/coverage/domainset'.format(
                collection_name_path)

            paths[coverage_domainset_path] = {
                'get': {
                    'summary': 'Get {} coverage domain set'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}CoverageDomainSet'.format(
                        k.capitalize()),
                    'parameters': [
                        items_f,
                    ],
                    'responses': {
                        '200': {'$ref': '{}/schemas/cis_1.1/domainSet.yaml'.format(OPENAPI_YAML['oacov'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }

            coverage_rangetype_path = '{}/coverage/rangetype'.format(
                collection_name_path)

            paths[coverage_rangetype_path] = {
                'get': {
                    'summary': 'Get {} coverage range type'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}CoverageRangeType'.format(
                        k.capitalize()),
                    'parameters': [
                        items_f,
                    ],
                    'responses': {
                        '200': {'$ref': '{}/schemas/cis_1.1/rangeType.yaml'.format(OPENAPI_YAML['oacov'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }
        except ProviderTypeError:
            LOGGER.debug('collection is not coverage based')

    LOGGER.debug('setting up STAC')
    stac_collections = filter_dict_by_key_value(cfg['resources'],
                                                'type', 'stac-collection')
    if stac_collections:
        paths['/stac'] = {
            'get': {
                'summary': 'SpatioTemporal Asset Catalog',
                'description': 'SpatioTemporal Asset Catalog',
                'tags': ['stac'],
                'operationId': 'getStacCatalog',
                'parameters': [],
                'responses': {
                    '200': {'$ref': '#/components/responses/200'},
                    'default': {'$ref': '#/components/responses/default'}
                }
            }
        }

    LOGGER.debug('setting up processes')
    processes = filter_dict_by_key_value(cfg['resources'], 'type', 'process')

    if processes:
        paths['/processes'] = {
            'get': {
                'summary': 'Processes',
                'description': 'Processes',
                'tags': ['server'],
                'operationId': 'getProcesses',
                'parameters': [
                    {'$ref': '#/components/parameters/f'}
                ],
                'responses': {
                    '200': {'$ref': '{}/responses/ProcessList.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                    'default': {'$ref': '#/components/responses/default'}
                }
            }
        }

        for k, v in processes.items():
            p = load_plugin('process', v['processor'])

            process_name_path = '/processes/{}'.format(k)
            tag = {
                'name': k,
                'description': p.metadata['description'],
                'externalDocs': {}
            }
            for link in p.metadata['links']:
                if link['type'] == 'information':
                    tag['externalDocs']['description'] = link['type']
                    tag['externalDocs']['url'] = link['url']
                    break
            if len(tag['externalDocs']) == 0:
                del tag['externalDocs']

            oas['tags'].append(tag)

            paths[process_name_path] = {
                'get': {
                    'summary': 'Get process metadata',
                    'description': p.metadata['description'],
                    'tags': [k],
                    'operationId': 'describe{}Process'.format(k.capitalize()),
                    'parameters': [
                        {'$ref': '#/components/parameters/f'}
                    ],
                    'responses': {
                        '200': {'$ref': '#/components/responses/200'},
                        'default': {'$ref': '#/components/responses/default'}
                    }
                }
            }
            paths['{}/jobs'.format(process_name_path)] = {
                'get': {
                    'summary': 'Retrieve job list for process',
                    'description': p.metadata['description'],
                    'tags': [k],
                    'operationId': 'get{}Jobs'.format(k.capitalize()),
                    'responses': {
                        '200': {'$ref': '#/components/responses/200'},
                        '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        'default': {'$ref': '#/components/responses/default'}
                    }
                },
                'post': {
                    'summary': 'Process {} execution'.format(
                        p.metadata['title']),
                    'description': p.metadata['description'],
                    'tags': [k],
                    'operationId': 'execute{}Job'.format(k.capitalize()),
                    'parameters': [{
                        'name': 'response',
                        'in': 'query',
                        'description': 'Response type',
                        'required': False,
                        'schema': {
                            'type': 'string',
                            'enum': ['raw', 'document'],
                            'default': 'document'
                        }
                    }],
                    'responses': {
                        '200': {'$ref': '#/components/responses/200'},
                        '201': {'$ref': '{}/responses/ExecuteAsync.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        '500': {'$ref': '{}/responses/ServerError.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        'default': {'$ref': '#/components/responses/default'}
                    },
                    'requestBody': {
                        'description': 'Mandatory execute request JSON',
                        'required': True,
                        'content': {
                            'application/json': {
                                'schema': {
                                    '$ref': '{}/schemas/execute.yaml'.format(OPENAPI_YAML['oapip'])  # noqa
                                }
                            }
                        }
                    }
                }
            }
            if 'example' in p.metadata:
                paths['{}/jobs'.format(process_name_path)]['post']['requestBody']['content']['application/json']['example'] = p.metadata['example']  # noqa

    oas['paths'] = paths

    return oas
Ejemplo n.º 7
0
def get_oas_30(cfg):
    """
    Generates an OpenAPI 3.0 Document

    :param cfg: configuration object

    :returns: OpenAPI definition YAML dict
    """

    paths = {}
    LOGGER.debug('setting up server info')
    oas = {
        'openapi': '3.0.2',
        'tags': []
    }
    info = {
        'title': cfg['metadata']['identification']['title'],
        'description': cfg['metadata']['identification']['description'],
        'x-keywords': cfg['metadata']['identification']['keywords'],
        'termsOfService':
            cfg['metadata']['identification']['terms_of_service'],
        'contact': {
            'name': cfg['metadata']['provider']['name'],
            'url': cfg['metadata']['provider']['url'],
            'email': cfg['metadata']['contact']['email']
        },
        'license': {
            'name': cfg['metadata']['license']['name'],
            'url': cfg['metadata']['license']['url']
        },
        'version': '3.0.2'
    }
    oas['info'] = info

    oas['servers'] = [{
        'url': cfg['server']['url'],
        'description': cfg['metadata']['identification']['description']
    }]

    paths['/'] = {
        'get': {
            'summary': 'API',
            'description': 'API',
            'tags': ['server'],
            'responses': {
                200: {
                    'description': 'successful operation'
                }
            }
        }
    }

    paths['/api'] = {
        'get': {
            'summary': 'This document',
            'description': 'This document',
            'tags': ['server'],
            'responses': {
                200: {
                    'description': 'successful operation'
                }
            }
        }
    }

    paths['/conformance'] = {
        'get': {
            'summary': 'API conformance definition',
            'description': 'API conformance definition',
            'tags': ['server'],
            'responses': {
                200: {
                    'description': 'successful operation'
                }
            }
        }
    }

    paths['/collections'] = {
        'get': {
            'summary': 'Feature Collections',
            'description': 'Feature Collections',
            'tags': ['server'],
            'responses': {
                200: {
                    'description': 'successful operation'
                }
            }
        }
    }

    oas['tags'].append({
        'name': 'server',
        'description': cfg['metadata']['identification']['description'],
        'externalDocs': {
            'description': 'information',
            'url': cfg['metadata']['identification']['url']}
        }
    )
    LOGGER.debug('setting up datasets')
    for k, v in cfg['datasets'].items():
        collection_name_path = '/collections/{}'.format(k)
        tag = {
            'name': k,
            'description': v['description'],
            'externalDocs': {}
        }
        for link in v['links']:
            if link['type'] == 'information':
                tag['externalDocs']['description'] = link['type']
                tag['externalDocs']['url'] = link['url']
                break
        if len(tag['externalDocs']) == 0:
            del tag['externalDocs']

        oas['tags'].append(tag)

        paths[collection_name_path] = {
            'get': {
                'summary': 'Get feature collection metadata'.format(v['title']),  # noqa
                'description': v['description'],
                'tags': [k],
                'responses': {
                    200: {
                        'description': 'successful operation'
                    },
                    400: {
                        'description': 'Invalid ID supplied'
                    },
                    404: {
                        'description': 'not found'
                    }
                }
            }
        }

        paths['{}/items'.format(collection_name_path)] = {
            'get': {
                'summary': 'Get {} features'.format(v['title']),
                'description': v['description'],
                'tags': [k],
                'parameters': [
                    {'$ref': '#/components/parameters/f'},
                    {'$ref': '#/components/parameters/limit'},
                    {'$ref': '#/components/parameters/sortby'},
                    {'$ref': '#/components/parameters/startindex'}
                ],
                'responses': {
                    200: {
                        'description': 'successful operation'
                    },
                    400: {
                        'description': 'Invalid ID supplied'
                    },
                    404: {
                        'description': 'not found'
                    }
                }
            }
        }

        p = load_plugin('provider', cfg['datasets'][k]['provider'])

        for k2, v2 in p.fields.items():
            path_ = '{}/items'.format(collection_name_path)

            if v2['type'] == 'date':
                schema = {
                    'type': 'string',
                    'format': 'date'
                }
            elif v2['type'] == 'float':
                schema = {
                    'type': 'number',
                    'format': 'float'
                }
            else:
                schema = {
                    'type': v2['type']
                }

            paths['{}'.format(path_)]['get']['parameters'].append({
                'name': k2,
                'in': 'query',
                'required': False,
                'schema': schema,
                'style': 'form',
                'explode': False
            })

        paths['{}/items/{{id}}'.format(collection_name_path)] = {
            'get': {
                'summary': 'Get {} feature by ID'.format(v['title']),
                'description': v['description'],
                'tags': [k],
                'parameters': [
                    {'$ref': '#/components/parameters/id'},
                    {'$ref': '#/components/parameters/f'}
                ],
                'responses': {
                    200: {
                        'description': 'successful operation'
                    },
                    400: {
                        'description': 'Invalid ID supplied'
                    },
                    404: {
                        'description': 'not found'
                    }
                }
            }
        }

    paths['/processes'] = {
        'get': {
            'summary': 'Processes',
            'description': 'Processes',
            'tags': ['server'],
            'responses': {
                200: {
                    'description': 'successful operation'
                }
            }
        }
    }

    LOGGER.debug('setting up processes')
    for k, v in cfg['processes'].items():
        p = load_plugin('process', v['processor'])

        process_name_path = '/processes/{}'.format(k)
        tag = {
            'name': k,
            'description': p.metadata['description'],
            'externalDocs': {}
        }
        for link in p.metadata['links']:
            if link['type'] == 'information':
                tag['externalDocs']['description'] = link['type']
                tag['externalDocs']['url'] = link['url']
                break
        if len(tag['externalDocs']) == 0:
            del tag['externalDocs']

        oas['tags'].append(tag)

        paths[process_name_path] = {
            'get': {
                'summary': 'Get process metadata'.format(p.metadata['title']),
                'description': p.metadata['description'],
                'tags': [k],
                'responses': {
                    200: {
                        'description': 'successful operation'
                    },
                    400: {
                        'description': 'Invalid ID supplied'
                    },
                    404: {
                        'description': 'not found'
                    }
                }
            }
        }
        paths['{}/jobs'.format(process_name_path)] = {
            'get': {
                'summary': 'Retrieve job list for process',
                'description': p.metadata['description'],
                'tags': [k],
                'responses': {
                    200: {
                        'description': 'successful operation'
                    }
                }
            },
            'post': {
                'summary': 'Process {} execution'.format(p.metadata['title']),
                'description': p.metadata['description'],
                'tags': [k],
                'parameters': [],
                'responses': {
                    200: {
                        'description': 'successful operation'
                    },
                    400: {
                        'description': 'Invalid ID supplied'
                    },
                    404: {
                        'description': 'not found'
                    },
                },
                'requestBody': {
                    'description': 'Mandatory execute request JSON',
                    'required': True,
                    'content': {
                        'application/json': {
                            'schema': {
                                '$ref': 'execute.yaml'
                            }
                        }
                    }
                }
            }
        }

    oas['paths'] = paths

    oas['components'] = {
        'parameters': {
            'id': {
                'name': 'id',
                'in': 'path',
                'description': 'The id of a feature',
                'required': True,
                'schema': {
                    'type': 'string'
                }
            },
            'f': {
                'name': 'f',
                'in': 'query',
                'description': 'The optional f parameter indicates the output format which the server shall provide as part of the response document.  The default format is GeoJSON.',  # noqa
                'required': False,
                'schema': {
                    'type': 'string',
                    'enum': ['GeoJSON', 'CSV'],
                    'default': 'GeoJSON'
                },
                'style': 'form',
                'explode': False
            },
            'limit': {
                'name': 'limit',
                'in': 'query',
                'description': 'The optional limit parameter limits the number of items that are presented in the response document. Only items are counted that are on the first level of the collection in the response document. Nested objects contained within the explicitly requested items shall not be counted. Minimum = 1. Maximum = 10000. Default = {}.'.format(cfg['server']['limit']),  # noqa
                'required': False,
                'schema': {
                    'type': 'integer',
                    'minimum': 1,
                    'maximum': 10000,
                    'default': cfg['server']['limit']
                },
                'style': 'form',
                'explode': False
            },
            'sortby': {
                'name': 'sortby',
                'in': 'query',
                'description': 'The optional sortby parameter indicates the sort property and order on which the server shall present results in the response document using the convention `sortby=PROPERTY:X`, where `PROPERTY` is the sort property and `X` is the sort order (`A` is ascending, `D` is descending). Sorting by multiple properties is supported by providing a comma-separated list.',  # noqa
                'required': False,
                'schema': {
                    'type': 'string',
                },
                'style': 'form',
                'explode': False
            },
            'startindex': {
                'name': 'startindex',
                'in': 'query',
                'description': 'The optional startindex parameter indicates the index within the result set from which the server shall begin presenting results in the response document.  The first element has an index of 0 (default).',  # noqa
                'required': False,
                'schema': {
                    'type': 'integer',
                    'minimum': 0,
                    'default': 0
                },
                'style': 'form',
                'explode': False
            }
        }
    }

    return oas
Ejemplo n.º 8
0
def get_oas_30(cfg):
    """
    Generates an OpenAPI 3.0 Document

    :param cfg: configuration object

    :returns: OpenAPI definition YAML dict
    """

    paths = {}

    osl = get_ogc_schemas_location(cfg['server'])
    OPENAPI_YAML['oapif'] = os.path.join(
        osl,
        'ogcapi/features/part1/1.0/openapi/ogcapi-features-1.yaml')  # noqa

    LOGGER.debug('setting up server info')
    oas = {'openapi': '3.0.2', 'tags': []}
    info = {
        'title': cfg['metadata']['identification']['title'],
        'description': cfg['metadata']['identification']['description'],
        'x-keywords': cfg['metadata']['identification']['keywords'],
        'termsOfService':
        cfg['metadata']['identification']['terms_of_service'],
        'contact': {
            'name': cfg['metadata']['provider']['name'],
            'url': cfg['metadata']['provider']['url'],
            'email': cfg['metadata']['contact']['email']
        },
        'license': {
            'name': cfg['metadata']['license']['name'],
            'url': cfg['metadata']['license']['url']
        },
        'version': '3.0.2'
    }
    oas['info'] = info

    oas['servers'] = [{
        'url':
        cfg['server']['url'],
        'description':
        cfg['metadata']['identification']['description']
    }]

    paths['/'] = {
        'get': {
            'summary': 'Landing page',
            'description': 'Landing page',
            'tags': ['server'],
            'parameters': [{
                '$ref': '#/components/parameters/f'
            }],
            'responses': {
                200: {
                    '$ref':
                    '{}#/components/responses/LandingPage'.format(
                        OPENAPI_YAML['oapif'])
                },  # noqa
                400: {
                    '$ref':
                    '{}#/components/responses/InvalidParameter'.format(
                        OPENAPI_YAML['oapif'])
                },  # noqa
                500: {
                    '$ref':
                    '{}#/components/responses/ServerError'.format(
                        OPENAPI_YAML['oapif'])
                }  # noqa
            }
        }
    }

    paths['/openapi'] = {
        'get': {
            'summary': 'This document',
            'description': 'This document',
            'tags': ['server'],
            'parameters': [{
                '$ref': '#/components/parameters/f'
            }],
            'responses': {
                200: {
                    '$ref': '#/components/responses/200'
                },
                400: {
                    '$ref':
                    '{}#/components/responses/InvalidParameter'.format(
                        OPENAPI_YAML['oapif'])
                },  # noqa
                'default': {
                    '$ref': '#/components/responses/default'
                }
            }
        }
    }

    paths['/conformance'] = {
        'get': {
            'summary': 'API conformance definition',
            'description': 'API conformance definition',
            'tags': ['server'],
            'parameters': [{
                '$ref': '#/components/parameters/f'
            }],
            'responses': {
                200: {
                    '$ref':
                    '{}#/components/responses/ConformanceDeclaration'.format(
                        OPENAPI_YAML['oapif'])
                },  # noqa
                400: {
                    '$ref':
                    '{}#/components/responses/InvalidParameter'.format(
                        OPENAPI_YAML['oapif'])
                },  # noqa
                500: {
                    '$ref':
                    '{}#/components/responses/ServerError'.format(
                        OPENAPI_YAML['oapif'])
                }  # noqa
            }
        }
    }

    paths['/collections'] = {
        'get': {
            'summary': 'Feature Collections',
            'description': 'Feature Collections',
            'tags': ['server'],
            'parameters': [{
                '$ref': '#/components/parameters/f'
            }],
            'responses': {
                200: {
                    '$ref':
                    '{}#/components/responses/Collections'.format(
                        OPENAPI_YAML['oapif'])
                },  # noqa
                400: {
                    '$ref':
                    '{}#/components/responses/InvalidParameter'.format(
                        OPENAPI_YAML['oapif'])
                },  # noqa
                500: {
                    '$ref':
                    '{}#/components/responses/ServerError'.format(
                        OPENAPI_YAML['oapif'])
                }  # noqa
            }
        }
    }

    oas['tags'].append({
        'name':
        'server',
        'description':
        cfg['metadata']['identification']['description'],
        'externalDocs': {
            'description': 'information',
            'url': cfg['metadata']['identification']['url']
        }
    })

    oas['components'] = {
        'responses': {
            '200': {
                'description': 'successful operation',
            },
            'default': {
                'description':
                'Unexpected error',
                'content':
                gen_media_type_object('application/json', 'oapip',
                                      'schemas/exception.yaml')  # noqa
            }
        },
        'parameters': {
            'f': {
                'name': 'f',
                'in': 'query',
                'description':
                'The optional f parameter indicates the output format which the server shall provide as part of the response document.  The default format is GeoJSON.',  # noqa
                'required': False,
                'schema': {
                    'type': 'string',
                    'enum': ['json', 'html', 'jsonld'],
                    'default': 'json'
                },
                'style': 'form',
                'explode': False
            },
            'sortby': {
                'name': 'sortby',
                'in': 'query',
                'description':
                'The optional sortby parameter indicates the sort property and order on which the server shall present results in the response document using the convention `sortby=PROPERTY:X`, where `PROPERTY` is the sort property and `X` is the sort order (`A` is ascending, `D` is descending). Sorting by multiple properties is supported by providing a comma-separated list.',  # noqa
                'required': False,
                'schema': {
                    'type': 'string',
                },
                'style': 'form',
                'explode': False
            },
            'startindex': {
                'name': 'startindex',
                'in': 'query',
                'description':
                'The optional startindex parameter indicates the index within the result set from which the server shall begin presenting results in the response document.  The first element has an index of 0 (default).',  # noqa
                'required': False,
                'schema': {
                    'type': 'integer',
                    'minimum': 0,
                    'default': 0
                },
                'style': 'form',
                'explode': False
            }
        }
    }

    items_f = deepcopy(oas['components']['parameters']['f'])
    items_f['schema']['enum'].append('csv')

    LOGGER.debug('setting up datasets')
    for k, v in cfg['datasets'].items():
        collection_name_path = '/collections/{}'.format(k)
        tag = {'name': k, 'description': v['description'], 'externalDocs': {}}
        for link in v['links']:
            if link['type'] == 'information':
                tag['externalDocs']['description'] = link['type']
                tag['externalDocs']['url'] = link['url']
                break
        if len(tag['externalDocs']) == 0:
            del tag['externalDocs']

        oas['tags'].append(tag)

        paths[collection_name_path] = {
            'get': {
                'summary':
                'Get feature collection metadata'.format(v['title']),  # noqa
                'description': v['description'],
                'tags': [k],
                'parameters': [{
                    '$ref': '#/components/parameters/f'
                }],
                'responses': {
                    200: {
                        '$ref':
                        '{}#/components/responses/Collection'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    400: {
                        '$ref':
                        '{}#/components/responses/InvalidParameter'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    404: {
                        '$ref':
                        '{}#/components/responses/NotFound'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    500: {
                        '$ref':
                        '{}#/components/responses/ServerError'.format(
                            OPENAPI_YAML['oapif'])
                    }  # noqa
                }
            }
        }

        items_path = '{}/items'.format(collection_name_path)

        paths[items_path] = {
            'get': {
                'summary':
                'Get {} features'.format(v['title']),
                'description':
                v['description'],
                'tags': [k],
                'parameters': [
                    items_f,
                    {
                        '$ref':
                        '{}#/components/parameters/bbox'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    {
                        '$ref':
                        '{}#/components/parameters/limit'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    {
                        '$ref': '#/components/parameters/sortby'
                    },
                    {
                        '$ref': '#/components/parameters/startindex'
                    }
                ],
                'responses': {
                    200: {
                        '$ref':
                        '{}#/components/responses/Features'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    400: {
                        '$ref':
                        '{}#/components/responses/InvalidParameter'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    404: {
                        '$ref':
                        '{}#/components/responses/NotFound'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    500: {
                        '$ref':
                        '{}#/components/responses/ServerError'.format(
                            OPENAPI_YAML['oapif'])
                    }  # noqa
                }
            }
        }

        p = load_plugin('provider', cfg['datasets'][k]['provider'])

        if p.time_field is not None:
            paths[items_path]['get']['parameters'].append({
                '$ref':
                '{}#/components/parameters/datetime'.format(
                    OPENAPI_YAML['oapif'])
            })  # noqa

        for k2, v2 in p.fields.items():
            if p.properties and k2 in p.properties:
                path_ = '{}/items'.format(collection_name_path)

                if v2['type'] == 'date':
                    schema = {'type': 'string', 'format': 'date'}
                elif v2['type'] == 'float':
                    schema = {'type': 'number', 'format': 'float'}
                elif v2['type'] == 'long':
                    schema = {'type': 'integer', 'format': 'int64'}
                else:
                    schema = {'type': v2['type']}

                paths['{}'.format(path_)]['get']['parameters'].append({
                    'name':
                    k2,
                    'in':
                    'query',
                    'required':
                    False,
                    'schema':
                    schema,
                    'style':
                    'form',
                    'explode':
                    False
                })

        paths['{}/items/{{featureId}}'.format(collection_name_path)] = {
            'get': {
                'summary':
                'Get {} feature by id'.format(v['title']),
                'description':
                v['description'],
                'tags': [k],
                'parameters': [
                    {
                        '$ref':
                        '{}#/components/parameters/featureId'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    {
                        '$ref': '#/components/parameters/f'
                    }
                ],
                'responses': {
                    200: {
                        '$ref':
                        '{}#/components/responses/Feature'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    400: {
                        '$ref':
                        '{}#/components/responses/InvalidParameter'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    404: {
                        '$ref':
                        '{}#/components/responses/NotFound'.format(
                            OPENAPI_YAML['oapif'])
                    },  # noqa
                    500: {
                        '$ref':
                        '{}#/components/responses/ServerError'.format(
                            OPENAPI_YAML['oapif'])
                    }  # noqa
                }
            }
        }

    paths['/processes'] = {
        'get': {
            'summary': 'Processes',
            'description': 'Processes',
            'tags': ['server'],
            'parameters': [{
                '$ref': '#/components/parameters/f'
            }],
            'responses': {
                200: {
                    '$ref': '#/components/responses/200'
                },
                'default': {
                    '$ref': '#/components/responses/default'
                }
            }
        }
    }

    LOGGER.debug('setting up processes')

    processes = cfg.get('processes', {})

    if processes:
        for k, v in processes.items():
            p = load_plugin('process', v['processor'])

            process_name_path = '/processes/{}'.format(k)
            tag = {
                'name': k,
                'description': p.metadata['description'],
                'externalDocs': {}
            }
            for link in p.metadata['links']:
                if link['type'] == 'information':
                    tag['externalDocs']['description'] = link['type']
                    tag['externalDocs']['url'] = link['url']
                    break
            if len(tag['externalDocs']) == 0:
                del tag['externalDocs']

            oas['tags'].append(tag)

            paths[process_name_path] = {
                'get': {
                    'summary':
                    'Get process metadata'.format(p.metadata['title']),
                    'description': p.metadata['description'],
                    'tags': [k],
                    'parameters': [{
                        '$ref': '#/components/parameters/f'
                    }],
                    'responses': {
                        200: {
                            '$ref': '#/components/responses/200'
                        },
                        'default': {
                            '$ref': '#/components/responses/default'
                        }
                    }
                }
            }
            paths['{}/jobs'.format(process_name_path)] = {
                'get': {
                    'summary': 'Retrieve job list for process',
                    'description': p.metadata['description'],
                    'tags': [k],
                    'responses': {
                        200: {
                            '$ref': '#/components/responses/200'
                        },
                        'default': {
                            '$ref': '#/components/responses/default'
                        }
                    }
                },
                'post': {
                    'summary':
                    'Process {} execution'.format(p.metadata['title']),
                    'description': p.metadata['description'],
                    'tags': [k],
                    'parameters': [],
                    'responses': {
                        200: {
                            '$ref': '#/components/responses/200'
                        },
                        'default': {
                            '$ref': '#/components/responses/default'
                        }
                    },
                    'requestBody': {
                        'description': 'Mandatory execute request JSON',
                        'required': True,
                        'content': {
                            'application/json': {
                                'schema': {
                                    '$ref':
                                    '{}/schemas/execute.yaml'.format(
                                        OPENAPI_YAML['oapip'])  # noqa
                                }
                            }
                        }
                    }
                }
            }
            if 'example' in p.metadata:
                paths['{}/jobs'.format(process_name_path)]['post'][
                    'requestBody']['content']['application/json'][
                        'example'] = p.metadata['example']  # noqa

    oas['paths'] = paths

    return oas
Ejemplo n.º 9
0
    def get_feature(self, headers, args, dataset, identifier):
        """
        Get a single feature

        :param headers: dict of HTTP headers
        :param args: dict of HTTP request parameters
        :param dataset: dataset name
        :param identifier: feature identifier

        :returns: tuple of headers, status code, content
        """

        headers_ = HEADERS.copy()

        format_ = check_format(args, headers)

        if format_ is not None and format_ not in FORMATS:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid format'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Processing query parameters')

        if dataset not in self.config['datasets'].keys():
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid feature collection'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Loading provider')
        p = load_plugin('provider',
                        self.config['datasets'][dataset]['provider'])

        LOGGER.debug('Fetching id {}'.format(identifier))
        content = p.get(identifier)

        if content is None:
            exception = {
                'code': 'NotFound',
                'description': 'identifier not found'
            }
            LOGGER.error(exception)
            return headers_, 404, json.dumps(exception)

        content['links'] = [{
            'rel':
            'self',
            'type':
            'application/json',
            'href':
            '{}/collections/{}/items/{}'.format(self.config['server']['url'],
                                                dataset, identifier)
        }, {
            'rel':
            'collection',
            'type':
            'application/json',
            'href':
            '{}/collections/{}'.format(self.config['server']['url'], dataset)
        }]

        if format_ == 'html':  # render
            headers_['Content-Type'] = 'text/html'
            content = _render_j2_template(self.config, 'item.html', content)
            return headers_, 200, content

        return headers_, 200, json.dumps(content)
Ejemplo n.º 10
0
    def get_features(self, headers, args, dataset):
        """
        Queries feature collection

        :param headers: dict of HTTP headers
        :param args: dict of HTTP request parameters
        :param dataset: dataset name

        :returns: tuple of headers, status code, content
        """

        headers_ = HEADERS.copy()

        properties = []
        reserved_fieldnames = [
            'bbox', 'f', 'limit', 'startindex', 'resulttype', 'time'
        ]
        formats = FORMATS
        formats.extend(f.lower() for f in PLUGINS['formatter'].keys())

        if dataset not in self.config['datasets'].keys():
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid feature collection'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        format_ = check_format(args, headers)

        if format_ is not None and format_ not in formats:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid format'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Processing query parameters')
        try:
            startindex = int(args.get('startindex'))
        except TypeError:
            startindex = 0
        try:
            limit = int(args.get('limit'))
        except TypeError:
            limit = self.config['server']['limit']

        resulttype = args.get('resulttype') or 'results'

        try:
            bbox = args.get('bbox').split(',')
            if len(bbox) != 4:
                exception = {
                    'code': 'InvalidParameterValue',
                    'description': 'bbox values should be minx,miny,maxx,maxy'
                }
                LOGGER.error(exception)
                return headers_, 400, json.dumps(exception)
        except AttributeError:
            bbox = []

        time = args.get('time')

        LOGGER.debug('Loading provider')
        try:
            p = load_plugin('provider',
                            self.config['datasets'][dataset]['provider'])
        except ProviderConnectionError:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'connection error (check logs)'
            }
            LOGGER.error(exception)
            return headers_, 500, json.dumps(exception)
        except ProviderQueryError:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'query error (check logs)'
            }
            LOGGER.error(exception)
            return headers_, 500, json.dumps(exception)

        LOGGER.debug('processing property parameters')
        for k, v in args.items():
            if k not in reserved_fieldnames and k in p.fields.keys():
                properties.append((k, v))

        LOGGER.debug('processing sort parameter')
        val = args.get('sortby')

        if val is not None:
            sortby = []
            sorts = val.split(',')
            for s in sorts:
                if ':' in s:
                    prop, order = s.split(':')
                    if order not in ['A', 'D']:
                        exception = {
                            'code': 'InvalidParameterValue',
                            'description': 'sort order should be A or D'
                        }
                        LOGGER.error(exception)
                        return headers_, 400, json.dumps(exception)
                    sortby.append({'property': prop, 'order': order})
                else:
                    sortby.append({'property': s, 'order': 'A'})
            for s in sortby:
                if s['property'] not in p.fields.keys():
                    exception = {
                        'code': 'InvalidParameterValue',
                        'description': 'bad sort property'
                    }
                    LOGGER.error(exception)
                    return headers_, 400, json.dumps(exception)
        else:
            sortby = []

        LOGGER.debug('Querying provider')
        LOGGER.debug('startindex: {}'.format(startindex))
        LOGGER.debug('limit: {}'.format(limit))
        LOGGER.debug('resulttype: {}'.format(resulttype))
        LOGGER.debug('sortby: {}'.format(sortby))

        try:
            content = p.query(startindex=int(startindex),
                              limit=int(limit),
                              resulttype=resulttype,
                              bbox=bbox,
                              time=time,
                              properties=properties,
                              sortby=sortby)
        except ProviderConnectionError:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'connection error (check logs)'
            }
            LOGGER.error(exception)
            return headers_, 500, json.dumps(exception)
        except ProviderQueryError:
            exception = {
                'code': 'NoApplicableCode',
                'description': 'query error (check logs)'
            }
            LOGGER.error(exception)
            return headers_, 500, json.dumps(exception)

        prev = startindex - self.config['server']['limit']
        if prev < 0:
            prev = 0

        next_ = startindex + self.config['server']['limit']

        content['links'] = [{
            'type':
            'application/json',
            'rel':
            'self',
            'title':
            'Collection items',
            'href':
            '{}collections/{}/items'.format(self.config['server']['url'],
                                            dataset)
        }, {
            'type':
            'application/json',
            'rel':
            'prev',
            'title':
            'items (prev)',
            'href':
            '{}/collections/{}/items/?startindex={}'.format(
                self.config['server']['url'], dataset, prev)
        }, {
            'type':
            'application/json',
            'rel':
            'next',
            'title':
            'items (next)',
            'href':
            '{}/collections/{}/items/?startindex={}'.format(
                self.config['server']['url'], dataset, next_)
        }, {
            'type':
            'application/json',
            'title':
            'Collection',
            'rel':
            'collection',
            'href':
            '{}/collections/{}'.format(self.config['server']['url'], dataset)
        }]

        content['timeStamp'] = datetime.utcnow().isoformat()

        if format_ == 'html':  # render
            headers_['Content-Type'] = 'text/html'
            content = _render_j2_template(self.config, 'items.html', content)
            return headers_, 200, content
        elif format_ == 'csv':  # render
            formatter = load_plugin('formatter', {'name': 'CSV', 'geom': True})

            content = formatter.write(
                data=content,
                options={
                    'provider_def':
                    self.config['datasets'][dataset]['provider']
                })

            headers_['Content-Type'] = '{}; charset={}'.format(
                formatter.mimetype, self.config['server']['encoding'])

            cd = 'attachment; filename="{}.csv"'.format(dataset)
            headers_['Content-Disposition'] = cd

            return headers_, 200, content

        return headers_, 200, json.dumps(content)
Ejemplo n.º 11
0
def get_oas_30(cfg):
    """
    Generates an OpenAPI 3.0 Document

    :param cfg: configuration object

    :returns: OpenAPI definition YAML dict
    """

    paths = {}

    osl = get_ogc_schemas_location(cfg['server'])
    OPENAPI_YAML['oapif'] = os.path.join(osl, 'ogcapi/features/part1/1.0/openapi/ogcapi-features-1.yaml')  # noqa

    LOGGER.debug('setting up server info')
    oas = {
        'openapi': '3.0.2',
        'tags': []
    }
    info = {
        'title': cfg['metadata']['identification']['title'],
        'description': cfg['metadata']['identification']['description'],
        'x-keywords': cfg['metadata']['identification']['keywords'],
        'termsOfService':
            cfg['metadata']['identification']['terms_of_service'],
        'contact': {
            'name': cfg['metadata']['provider']['name'],
            'url': cfg['metadata']['provider']['url'],
            'email': cfg['metadata']['contact']['email']
        },
        'license': {
            'name': cfg['metadata']['license']['name'],
            'url': cfg['metadata']['license']['url']
        },
        'version': __version__
    }
    oas['info'] = info

    oas['servers'] = [{
        'url': cfg['server']['url'],
        'description': cfg['metadata']['identification']['description']
    }]

    paths['/'] = {
        'get': {
            'summary': 'Landing page',
            'description': 'Landing page',
            'tags': ['server'],
            'operationId': 'getLandingPage',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '{}#/components/responses/LandingPage'.format(OPENAPI_YAML['oapif'])},  # noqa
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
            }
        }
    }

    paths['/openapi'] = {
        'get': {
            'summary': 'This document',
            'description': 'This document',
            'tags': ['server'],
            'operationId': 'getOpenapi',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '#/components/responses/200'},
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                'default': {'$ref': '#/components/responses/default'}
            }
        }
    }

    paths['/conformance'] = {
        'get': {
            'summary': 'API conformance definition',
            'description': 'API conformance definition',
            'tags': ['server'],
            'operationId': 'getConformanceDeclaration',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '{}#/components/responses/ConformanceDeclaration'.format(OPENAPI_YAML['oapif'])},  # noqa
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
            }
        }
    }

    paths['/collections'] = {
        'get': {
            'summary': 'Collections',
            'description': 'Collections',
            'tags': ['server'],
            'operationId': 'getCollections',
            'parameters': [
                {'$ref': '#/components/parameters/f'}
            ],
            'responses': {
                '200': {'$ref': '{}#/components/responses/Collections'.format(OPENAPI_YAML['oapif'])},  # noqa
                '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
            }
        }
    }

    oas['tags'].append({
            'name': 'server',
            'description': cfg['metadata']['identification']['description'],
            'externalDocs': {
                'description': 'information',
                'url': cfg['metadata']['identification']['url']}
        }
    )
    oas['tags'].append({
            'name': 'stac',
            'description': 'SpatioTemporal Asset Catalog'
        }
    )

    oas['components'] = {
        'responses': {
            '200': {
                'description': 'successful operation',
            },
            'default': {
                'description': 'Unexpected error',
                'content': gen_media_type_object('application/json', 'oapip', 'schemas/exception.yaml')  # noqa
            },
            'Queryables': {
                'description': 'successful queryables operation',
                'content': {
                    'application/json': {
                        'schema': {'$ref': '#/components/schemas/queryables'}
                    }
                }
            }
        },
        'parameters': {
            'f': {
                'name': 'f',
                'in': 'query',
                'description': 'The optional f parameter indicates the output format which the server shall provide as part of the response document.  The default format is GeoJSON.',  # noqa
                'required': False,
                'schema': {
                    'type': 'string',
                    'enum': ['json', 'html', 'jsonld'],
                    'default': 'json'
                },
                'style': 'form',
                'explode': False
            },
            'properties': {
                'name': 'properties',
                'in': 'query',
                'description': 'The properties that should be included for each feature. The parameter value is a comma-separated list of property names.',  # noqa
                'required': False,
                'style': 'form',
                'explode': False,
                'schema': {
                    'type': 'array',
                    'items': {
                        'type': 'string'
                    }
                }
            },
            'skipGeometry': {
                'name': 'skipGeometry',
                'in': 'query',
                'description': 'This option can be used to skip response geometries for each feature.',  # noqa
                'required': False,
                'style': 'form',
                'explode': False,
                'schema': {
                    'type': 'boolean',
                    'default': False
                }
            },
            'startindex': {
                'name': 'startindex',
                'in': 'query',
                'description': 'The optional startindex parameter indicates the index within the result set from which the server shall begin presenting results in the response document.  The first element has an index of 0 (default).',  # noqa
                'required': False,
                'schema': {
                    'type': 'integer',
                    'minimum': 0,
                    'default': 0
                },
                'style': 'form',
                'explode': False
            }
        },
        'schemas': {
            # TODO: change this schema once OGC will definitively publish it
            'queryable': {
                'type': 'object',
                'required': [
                    'queryable',
                    'type'
                ],
                'properties': {
                    'queryable': {
                        'description': 'the token that may be used in a CQL predicate', # noqa
                        'type': 'string'
                    },
                    'title': {
                        'description': 'a human readable title for the queryable', # noqa
                        'type': 'string'
                    },
                    'description': {
                        'description': 'a human-readable narrative describing the queryable', # noqa
                        'type': 'string'
                    },
                    'language': {
                        'description': 'the language used for the title and description', # noqa
                        'type': 'string',
                        'default': [
                            'en'
                        ]
                    },
                    'type': {
                        'description': 'the data type of the queryable', # noqa
                        'type': 'string'
                    },
                    'type-ref': {
                        'description': 'a reference to the formal definition of the type', # noqa
                        'type': 'string',
                        'format': 'url'
                    }
                }
            },
            'queryables': {
                'type': 'object',
                'required': [
                    'queryables'
                ],
                'properties': {
                    'queryables': {
                        'type': 'array',
                        'items': {'$ref': '#/components/schemas/queryable'}
                    }
                }
            }
        }
    }

    items_f = deepcopy(oas['components']['parameters']['f'])
    items_f['schema']['enum'].append('csv')

    LOGGER.debug('setting up datasets')
    collections = filter_dict_by_key_value(cfg['resources'],
                                           'type', 'collection')

    for k, v in collections.items():
        collection_name_path = '/collections/{}'.format(k)
        tag = {
            'name': k,
            'description': v['description'],
            'externalDocs': {}
        }
        for link in v['links']:
            if link['type'] == 'information':
                tag['externalDocs']['description'] = link['type']
                tag['externalDocs']['url'] = link['url']
                break
        if len(tag['externalDocs']) == 0:
            del tag['externalDocs']

        oas['tags'].append(tag)

        paths[collection_name_path] = {
            'get': {
                'summary': 'Get collection metadata'.format(v['title']),  # noqa
                'description': v['description'],
                'tags': [k],
                'operationId': 'describe{}Collection'.format(k.capitalize()),
                'parameters': [
                    {'$ref': '#/components/parameters/f'}
                ],
                'responses': {
                    '200': {'$ref': '{}#/components/responses/Collection'.format(OPENAPI_YAML['oapif'])},  # noqa
                    '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                    '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                    '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                }
            }
        }

        LOGGER.debug('setting up collection endpoints')
        try:
            ptype = None

            if filter_providers_by_type(
                    collections[k]['providers'], 'feature'):
                ptype = 'feature'

            if filter_providers_by_type(
                    collections[k]['providers'], 'record'):
                ptype = 'record'

            p = load_plugin('provider', get_provider_by_type(
                            collections[k]['providers'], ptype))

            items_path = '{}/items'.format(collection_name_path)

            coll_properties = deepcopy(oas['components']['parameters']['properties'])  # noqa

            coll_properties['schema']['items']['enum'] = list(p.fields.keys())

            paths[items_path] = {
                'get': {
                    'summary': 'Get {} items'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}Features'.format(k.capitalize()),
                    'parameters': [
                        items_f,
                        {'$ref': '{}#/components/parameters/bbox'.format(OPENAPI_YAML['oapif'])},  # noqa
                        {'$ref': '{}#/components/parameters/limit'.format(OPENAPI_YAML['oapif'])},  # noqa
                        coll_properties,
                        {'$ref': '#/components/parameters/skipGeometry'},
                        {'$ref': '{}/parameters/sortby.yaml'.format(OPENAPI_YAML['oapir'])},  # noqa
                        {'$ref': '#/components/parameters/startindex'},
                    ],
                    'responses': {
                        '200': {'$ref': '{}#/components/responses/Features'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }

            if ptype == 'record':
                paths[items_path]['get']['parameters'].append(
                    {'$ref': '{}/parameters/q.yaml'.format(OPENAPI_YAML['oapir'])})  # noqa
            if p.fields:
                queryables_path = '{}/queryables'.format(collection_name_path)

                paths[queryables_path] = {
                    'get': {
                        'summary': 'Get {} queryables'.format(v['title']),
                        'description': v['description'],
                        'tags': [k],
                        'operationId': 'get{}Queryables'.format(
                            k.capitalize()),
                        'parameters': [
                            items_f,
                        ],
                        'responses': {
                            '200': {'$ref': '#/components/responses/Queryables'},  # noqa
                            '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                            '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                            '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                        }
                    }
                }

            if p.time_field is not None:
                paths[items_path]['get']['parameters'].append(
                    {'$ref': '{}#/components/parameters/datetime'.format(OPENAPI_YAML['oapif'])})  # noqa

            for field, type in p.fields.items():

                if p.properties and field not in p.properties:
                    LOGGER.debug('Provider specified not to advertise property')  # noqa
                    continue

                if field == 'q' and ptype == 'record':
                    LOGGER.debug('q parameter already declared, skipping')
                    continue

                if type == 'date':
                    schema = {
                        'type': 'string',
                        'format': 'date'
                    }
                elif type == 'float':
                    schema = {
                        'type': 'number',
                        'format': 'float'
                    }
                elif type == 'long':
                    schema = {
                        'type': 'integer',
                        'format': 'int64'
                    }
                else:
                    schema = {
                        'type': type
                    }

                path_ = '{}/items'.format(collection_name_path)
                paths['{}'.format(path_)]['get']['parameters'].append({
                    'name': field,
                    'in': 'query',
                    'required': False,
                    'schema': schema,
                    'style': 'form',
                    'explode': False
                })

            paths['{}/items/{{featureId}}'.format(collection_name_path)] = {
                'get': {
                    'summary': 'Get {} item by id'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}Feature'.format(k.capitalize()),
                    'parameters': [
                        {'$ref': '{}#/components/parameters/featureId'.format(OPENAPI_YAML['oapif'])},  # noqa
                        {'$ref': '#/components/parameters/f'}
                    ],
                    'responses': {
                        '200': {'$ref': '{}#/components/responses/Feature'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }
        except ProviderTypeError:
            LOGGER.debug('collection is not feature based')

        LOGGER.debug('setting up coverage endpoints')
        try:
            load_plugin('provider', get_provider_by_type(
                        collections[k]['providers'], 'coverage'))

            coverage_path = '{}/coverage'.format(collection_name_path)

            paths[coverage_path] = {
                'get': {
                    'summary': 'Get {} coverage'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}Coverage'.format(k.capitalize()),
                    'parameters': [
                        items_f,
                    ],
                    'responses': {
                        '200': {'$ref': '{}#/components/responses/Features'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }

            coverage_domainset_path = '{}/coverage/domainset'.format(
                collection_name_path)

            paths[coverage_domainset_path] = {
                'get': {
                    'summary': 'Get {} coverage domain set'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}CoverageDomainSet'.format(
                        k.capitalize()),
                    'parameters': [
                        items_f,
                    ],
                    'responses': {
                        '200': {'$ref': '{}/schemas/cis_1.1/domainSet.yaml'.format(OPENAPI_YAML['oacov'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }

            coverage_rangetype_path = '{}/coverage/rangetype'.format(
                collection_name_path)

            paths[coverage_rangetype_path] = {
                'get': {
                    'summary': 'Get {} coverage range type'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}CoverageRangeType'.format(
                        k.capitalize()),
                    'parameters': [
                        items_f,
                    ],
                    'responses': {
                        '200': {'$ref': '{}/schemas/cis_1.1/rangeType.yaml'.format(OPENAPI_YAML['oacov'])},  # noqa
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }
        except ProviderTypeError:
            LOGGER.debug('collection is not coverage based')

        LOGGER.debug('setting up tiles endpoints')
        tile_extension = filter_providers_by_type(
            collections[k]['providers'], 'tile')

        if tile_extension:
            tp = load_plugin('provider', tile_extension)
            oas['components']['responses'].update({
                    'Tiles': {
                        'description': 'Retrieves the tiles description for this collection', # noqa
                        'content': {
                            'application/json': {
                                'schema': {
                                    '$ref': '#/components/schemas/tiles'
                                }
                            }
                        }
                    }
                }
            )

            oas['components']['schemas'].update({
                    'tilematrixsetlink': {
                        'type': 'object',
                        'required': ['tileMatrixSet'],
                        'properties': {
                            'tileMatrixSet': {
                                'type': 'string'
                            },
                            'tileMatrixSetURI': {
                                'type': 'string'
                            }
                        }
                    },
                    'tiles': {
                        'type': 'object',
                        'required': [
                            'tileMatrixSetLinks',
                            'links'
                        ],
                        'properties': {
                            'tileMatrixSetLinks': {
                                'type': 'array',
                                'items': {
                                    '$ref': '#/components/schemas/tilematrixsetlink' # noqa
                                }
                            },
                            'links': {
                                'type': 'array',
                                'items': {'$ref': '{}#/components/schemas/link'.format(OPENAPI_YAML['oapit'])},  # noqa
                            }
                        }
                    }
                }
            )

            tiles_path = '{}/tiles'.format(collection_name_path)

            paths[tiles_path] = {
                'get': {
                    'summary': 'Fetch a {} tiles description'.format(v['title']), # noqa
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'describe{}Tiles'.format(k.capitalize()),
                    'parameters': [
                        items_f,
                    ],
                    'responses': {
                        '200': {'$ref': '#/components/responses/Tiles'},
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }

            tiles_data_path = '{}/tiles/{{tileMatrixSetId}}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}'.format(collection_name_path)  # noqa

            paths[tiles_data_path] = {
                'get': {
                    'summary': 'Get a {} tile'.format(v['title']),
                    'description': v['description'],
                    'tags': [k],
                    'operationId': 'get{}Tiles'.format(k.capitalize()),
                    'parameters': [{
                        'name': 'f',
                        'in': 'query',
                        'description': 'The optional f parameter indicates the output format which the server shall provide as part of the response document.',  # noqa
                        'required': False,
                        'schema': {
                            'type': 'string',
                            'enum': [tp.format_type],
                            'default': tp.format_type
                        },
                        'style': 'form',
                        'explode': False
                    }],
                    'responses': {
                        '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])},  # noqa
                        '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])}  # noqa
                    }
                }
            }
            mimetype = tile_extension['format']['mimetype']
            paths[tiles_data_path]['get']['responses']['200'] = {
                'content': {
                    mimetype: {
                        'schema': {
                            'type': 'string',
                            'format': 'binary'
                        }
                    }
                }
            }

        LOGGER.debug('setting up tiles endpoints')
        edr_extension = filter_providers_by_type(
            collections[k]['providers'], 'edr')

        if edr_extension:
            ep = load_plugin('provider', edr_extension)

            edr_query_endpoints = []

            for qt in ep.get_query_types():
                edr_query_endpoints.append({
                    'path': '{}/{}'.format(collection_name_path, qt),
                    'qt': qt,
                    'op_id': 'query{}{}'.format(qt.capitalize(), k.capitalize())  # noqa
                })
                if ep.instances:
                    edr_query_endpoints.append({
                        'path': '{}/instances/{{instanceId}}/{}'.format(collection_name_path, qt),  # noqa
                        'qt': qt,
                        'op_id': 'query{}Instance{}'.format(qt.capitalize(), k.capitalize())  # noqa
                    })

            for eqe in edr_query_endpoints:
                paths[eqe['path']] = {
                    'get': {
                        'summary': 'query {} by {}'.format(v['description'], eqe['qt']),  # noqa
                        'description': v['description'],
                        'tags': [k],
                        'operationId': eqe['op_id'],
                        'parameters': [
                            {'$ref': '{}/parameters/{}Coords.yaml'.format(OPENAPI_YAML['oaedr'], eqe['qt'])},  # noqa
                            {'$ref': '{}#/components/parameters/datetime'.format(OPENAPI_YAML['oapif'])},  # noqa
                            {'$ref': '{}/parameters/parameter-name.yaml'.format(OPENAPI_YAML['oaedr'])},  # noqa
                            {'$ref': '{}/parameters/z.yaml'.format(OPENAPI_YAML['oaedr'])},  # noqa
                            {'$ref': '#/components/parameters/f'}
                        ],
                        'responses': {
                            '200': {
                                'description': 'Response',
                                'content': {
                                    'application/prs.coverage+json': {
                                        'schema': {
                                            '$ref': '{}/schemas/coverageJSON.yaml'.format(OPENAPI_YAML['oaedr'])}  # noqa
                                        }
                                    }
                                }
                            }
                        }
                    }

    LOGGER.debug('setting up STAC')
    stac_collections = filter_dict_by_key_value(cfg['resources'],
                                                'type', 'stac-collection')
    if stac_collections:
        paths['/stac'] = {
            'get': {
                'summary': 'SpatioTemporal Asset Catalog',
                'description': 'SpatioTemporal Asset Catalog',
                'tags': ['stac'],
                'operationId': 'getStacCatalog',
                'parameters': [],
                'responses': {
                    '200': {'$ref': '#/components/responses/200'},
                    'default': {'$ref': '#/components/responses/default'}
                }
            }
        }

    processes = filter_dict_by_key_value(cfg['resources'], 'type', 'process')

    has_manager = 'manager' in cfg['server']

    if processes:
        paths['/processes'] = {
            'get': {
                'summary': 'Processes',
                'description': 'Processes',
                'tags': ['server'],
                'operationId': 'getProcesses',
                'parameters': [
                    {'$ref': '#/components/parameters/f'}
                ],
                'responses': {
                    '200': {'$ref': '{}/responses/ProcessList.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                    'default': {'$ref': '#/components/responses/default'}
                }
            }
        }
        LOGGER.debug('setting up processes')

        for k, v in processes.items():
            p = load_plugin('process', v['processor'])

            process_name_path = '/processes/{}'.format(k)
            tag = {
                'name': k,
                'description': p.metadata['description'],
                'externalDocs': {}
            }
            for link in p.metadata['links']:
                if link['type'] == 'information':
                    tag['externalDocs']['description'] = link['type']
                    tag['externalDocs']['url'] = link['url']
                    break
            if len(tag['externalDocs']) == 0:
                del tag['externalDocs']

            oas['tags'].append(tag)

            paths[process_name_path] = {
                'get': {
                    'summary': 'Get process metadata',
                    'description': p.metadata['description'],
                    'tags': [k],
                    'operationId': 'describe{}Process'.format(k.capitalize()),
                    'parameters': [
                        {'$ref': '#/components/parameters/f'}
                    ],
                    'responses': {
                        '200': {'$ref': '#/components/responses/200'},
                        'default': {'$ref': '#/components/responses/default'}
                    }
                }
            }
            paths['{}/jobs'.format(process_name_path)] = {
                'get': {
                    'summary': 'Retrieve job list for process',
                    'description': p.metadata['description'],
                    'tags': [k],
                    'operationId': 'get{}Jobs'.format(k.capitalize()),
                    'responses': {
                        '200': {'$ref': '#/components/responses/200'},
                        '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        'default': {'$ref': '#/components/responses/default'}
                    }
                },
                'post': {
                    'summary': 'Process {} execution'.format(
                        p.metadata['title']),
                    'description': p.metadata['description'],
                    'tags': [k],
                    'operationId': 'execute{}Job'.format(k.capitalize()),
                    'parameters': [{
                        'name': 'response',
                        'in': 'query',
                        'description': 'Response type',
                        'required': False,
                        'schema': {
                            'type': 'string',
                            'enum': ['raw', 'document'],
                            'default': 'document'
                        }
                    }],
                    'responses': {
                        '200': {'$ref': '#/components/responses/200'},
                        '201': {'$ref': '{}/responses/ExecuteAsync.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        '500': {'$ref': '{}/responses/ServerError.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                        'default': {'$ref': '#/components/responses/default'}
                    },
                    'requestBody': {
                        'description': 'Mandatory execute request JSON',
                        'required': True,
                        'content': {
                            'application/json': {
                                'schema': {
                                    '$ref': '{}/schemas/execute.yaml'.format(OPENAPI_YAML['oapip'])  # noqa
                                }
                            }
                        }
                    }
                }
            }
            if 'example' in p.metadata:
                paths['{}/jobs'.format(process_name_path)]['post']['requestBody']['content']['application/json']['example'] = p.metadata['example']  # noqa

            name_in_path = {
                'name': 'jobId',
                'in': 'path',
                'description': 'job identifier',
                'required': True,
                'schema': {
                    'type': 'string'
                }
            }

            if has_manager:
                # TODO: define jobId as parameter in dict
                paths[f'{process_name_path}/jobs/{{jobId}}'] = {
                    'get': {
                        'summary': 'Retrieve job details',
                        'description': '',
                        'tags': [k],
                        'parameters': [
                            name_in_path,
                            {'$ref': '#/components/parameters/f'}
                        ],
                        'operationId': f'get{k.capitalize()}Job',
                        'responses': {
                            '200': {'$ref': '#/components/responses/200'},
                            '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                            'default': {'$ref': '#/components/responses/default'}  # noqa
                        }
                    },
                    'delete': {
                        'summary': 'Cancel / delete job',
                        'description': '',
                        'tags': [k],
                        'parameters': [
                            name_in_path
                        ],
                        'operationId': f'delete{k.capitalize()}Job',
                        'responses': {
                            '204': {'$ref': '#/components/responses/204'},
                            '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                            'default': {'$ref': '#/components/responses/default'}  # noqa
                        }
                    },
                }

                paths[f'{process_name_path}/jobs/{{jobId}}/results'] = {
                    'get': {
                        'summary': 'Retrieve job results',
                        'description': '',
                        'tags': [k],
                        'parameters': [
                            name_in_path,
                            {'$ref': '#/components/parameters/f'}
                        ],
                        'operationId': f'get{k.capitalize()}JobResults',
                        'responses': {
                            '200': {'$ref': '#/components/responses/200'},
                            '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])},  # noqa
                            'default': {'$ref': '#/components/responses/default'}  # noqa
                        }
                    },
                }

    oas['paths'] = paths

    return oas
Ejemplo n.º 12
0
    def get_stac_path(self, headers_, format_, path):

        if format_ is not None and format_ not in FORMATS:
            exception = {
                'code': 'InvalidParameterValue',
                'description': 'Invalid format'
            }
            LOGGER.error(exception)
            return headers_, 400, json.dumps(exception)

        LOGGER.debug('Path: {}'.format(path))
        dir_tokens = path.split('/')
        if dir_tokens:
            dataset = dir_tokens[0]

        if dataset not in self.config['datasets']:
            exception = {
                'code': 'NotFound',
                'description': 'collection not found'
            }
            LOGGER.error(exception)
            return headers_, 404, json.dumps(exception)

        LOGGER.debug('Loading provider')
        try:
            p = load_plugin('provider',
                            self.config['datasets'][dataset]['provider'])
        except ProviderConnectionError as err:
            LOGGER.error(err)
            exception = {
                'code': 'NoApplicableCode',
                'description': 'connection error (check logs)'
            }
            LOGGER.error(exception)
            return headers_, 500, json.dumps(exception)

        id_ = '{}-stac'.format(dataset)
        stac_version = '0.6.2'
        description = self.config['datasets'][dataset]['description']

        content = {
            'id': id_,
            'stac_version': stac_version,
            'description': description,
            'links': []
        }
        try:
            stac_data = p.get_data_path(
                os.path.join(self.config['server']['url'], 'stac'),
                path,
                path.replace(dataset, '', 1)
            )
        except ProviderNotFoundError as err:
            LOGGER.error(err)
            exception = {
                'code': 'NotFound',
                'description': 'resource not found'
            }
            return headers_, 404, json.dumps(exception)
        except Exception as err:
            LOGGER.error(err)
            exception = {
                'code': 'NoApplicableCode',
                'description': 'data query error'
            }
            return headers_, 500, json.dumps(exception)

        if isinstance(stac_data, dict):
            content.update(stac_data)
            content['links'].extend(self.config['datasets'][dataset]['links'])

            if format_ == 'html':  # render
                headers_['Content-Type'] = 'text/html'
                content['path'] = path
                if 'assets' in content:  # item view
                    content = render_j2_template(self.config,
                                                 'stac/item.html',
                                                 content)
                else:
                    content = render_j2_template(self.config,
                                                 'stac/catalog.html',
                                                 content)

                return headers_, 200, content

            return headers_, 200, json.dumps(content, default=json_serial)

        else:  # send back file
            headers_.pop('Content-Type', None)
            return headers_, 200, stac_data