]> git.openstreetmap.org Git - nominatim.git/blob - test/bdd/utils/api_result.py
release 5.1.0.post5
[nominatim.git] / test / bdd / utils / api_result.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2025 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Wrapper for results from the API
9 """
10 import json
11 import xml.etree.ElementTree as ET
12
13
14 class APIResult:
15
16     def __init__(self, fmt, endpoint, body):
17         getattr(self, '_parse_' + fmt)(endpoint, body)
18
19     def is_simple(self):
20         return not isinstance(self.result, list)
21
22     def __len__(self):
23         return 1 if self.is_simple() else len(self.result)
24
25     def __str__(self):
26         return json.dumps({'meta': self.meta, 'result': self.result}, indent=2)
27
28     def _parse_json(self, _, body):
29         self.meta = {}
30         self.result = json.loads(body)
31
32     def _parse_xml(self, endpoint, body):
33         xml_tree = ET.fromstring(body)
34
35         self.meta = dict(xml_tree.attrib)
36
37         if xml_tree.tag == 'reversegeocode':
38             self._parse_xml_simple(xml_tree)
39         elif xml_tree.tag == 'searchresults':
40             self._parse_xml_multi(xml_tree)
41         elif xml_tree.tag == 'error':
42             self.result = {'error': {sub.tag: sub.text for sub in xml_tree}}
43
44     def _parse_xml_simple(self, xml):
45         self.result = {}
46
47         for child in xml:
48             if child.tag == 'result':
49                 assert not self.result, "More than one result in reverse result"
50                 self.result.update(child.attrib)
51                 assert 'display_name' not in self.result
52                 self.result['display_name'] = child.text
53             elif child.tag == 'addressparts':
54                 assert 'address' not in self.result
55                 self.result['address'] = {sub.tag: sub.text for sub in child}
56             elif child.tag == 'extratags':
57                 assert 'extratags' not in self.result
58                 self.result['extratags'] = {tag.attrib['key']: tag.attrib['value'] for tag in child}
59             elif child.tag == 'namedetails':
60                 assert 'namedetails' not in self.result
61                 self.result['namedetails'] = {tag.attrib['desc']: tag.text for tag in child}
62             elif child.tag == 'geokml':
63                 assert 'geokml' not in self.result
64                 self.result['geokml'] = ET.tostring(child, encoding='unicode')
65             elif child.tag == 'error':
66                 assert not self.result
67                 self.result['error'] = child.text
68             else:
69                 assert False, f"Unknown XML tag {child.tag} on page: {self.page}"
70
71     def _parse_xml_multi(self, xml):
72         self.result = []
73
74         for child in xml:
75             assert child.tag == "place"
76             res = dict(child.attrib)
77
78             address = {}
79             for sub in child:
80                 if sub.tag == 'extratags':
81                     assert 'extratags' not in res
82                     res['extratags'] = {tag.attrib['key']: tag.attrib['value'] for tag in sub}
83                 elif sub.tag == 'namedetails':
84                     assert 'namedetails' not in res
85                     res['namedetails'] = {tag.attrib['desc']: tag.text for tag in sub}
86                 elif sub.tag == 'geokml':
87                     res['geokml'] = ET.tostring(sub, encoding='utf-8')
88                 else:
89                     address[sub.tag] = sub.text
90
91             if address:
92                 res['address'] = address
93
94             self.result.append(res)
95
96     def _parse_geojson(self, _, body):
97         geojson = json.loads(body)
98
99         assert geojson.get('type') == 'FeatureCollection'
100         assert isinstance(geojson.get('features'), list)
101
102         self.meta = {k: v for k, v in geojson.items() if k not in ('type', 'features')}
103         self.result = []
104
105         for obj in geojson['features']:
106             assert isinstance(obj, dict)
107             assert obj.get('type') == 'Feature'
108
109             assert isinstance(obj.get('properties'), dict)
110             result = obj['properties']
111             assert 'geojson' not in result
112             result['geojson'] = obj['geometry']
113             if 'bbox' in obj:
114                 assert 'boundingbox' not in result
115                 # bbox is  minlon, minlat, maxlon, maxlat
116                 # boundingbox is minlat, maxlat, minlon, maxlon
117                 result['boundingbox'] = [obj['bbox'][1], obj['bbox'][3],
118                                          obj['bbox'][0], obj['bbox'][2]]
119             self.result.append(result)
120
121     def _parse_geocodejson(self, endpoint, body):
122         self._parse_geojson(endpoint, body)
123
124         assert set(self.meta.keys()) == {'geocoding'}
125         assert isinstance(self.meta['geocoding'], dict)
126         self.meta = self.meta['geocoding']
127
128         for r in self.result:
129             assert set(r.keys()) == {'geocoding', 'geojson'}
130             inner = r.pop('geocoding')
131             assert isinstance(inner, dict)
132             assert 'geojson' not in inner
133             r.update(inner)