1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Base abstraction for implementing based on different ASGI frameworks.
10 from typing import Optional, Any, NoReturn, Callable
14 from ..config import Configuration
15 from .. import logging as loglib
16 from ..core import NominatimAPIAsync
18 CONTENT_TEXT = 'text/plain; charset=utf-8'
19 CONTENT_XML = 'text/xml; charset=utf-8'
20 CONTENT_HTML = 'text/html; charset=utf-8'
21 CONTENT_JSON = 'application/json; charset=utf-8'
23 CONTENT_TYPE = {'text': CONTENT_TEXT, 'xml': CONTENT_XML, 'debug': CONTENT_HTML}
25 class ASGIAdaptor(abc.ABC):
26 """ Adapter class for the different ASGI frameworks.
27 Wraps functionality over concrete requests and responses.
29 content_type: str = CONTENT_TEXT
32 def get(self, name: str, default: Optional[str] = None) -> Optional[str]:
33 """ Return an input parameter as a string. If the parameter was
34 not provided, return the 'default' value.
38 def get_header(self, name: str, default: Optional[str] = None) -> Optional[str]:
39 """ Return a HTTP header parameter as a string. If the parameter was
40 not provided, return the 'default' value.
45 def error(self, msg: str, status: int = 400) -> Exception:
46 """ Construct an appropriate exception from the given error message.
47 The exception must result in a HTTP error with the given status.
52 def create_response(self, status: int, output: str, num_results: int) -> Any:
53 """ Create a response from the given parameters. The result will
54 be returned by the endpoint functions. The adaptor may also
55 return None when the response is created internally with some
58 The response must return the HTTP given status code 'status', set
59 the HTTP content-type headers to the string provided and the
60 body of the response to 'output'.
64 def base_uri(self) -> str:
65 """ Return the URI of the original request.
70 def config(self) -> Configuration:
71 """ Return the current configuration object.
75 def get_int(self, name: str, default: Optional[int] = None) -> int:
76 """ Return an input parameter as an int. Raises an exception if
77 the parameter is given but not in an integer format.
79 If 'default' is given, then it will be returned when the parameter
80 is missing completely. When 'default' is None, an error will be
81 raised on a missing parameter.
83 value = self.get(name)
86 if default is not None:
89 self.raise_error(f"Parameter '{name}' missing.")
94 self.raise_error(f"Parameter '{name}' must be a number.")
99 def get_float(self, name: str, default: Optional[float] = None) -> float:
100 """ Return an input parameter as a flaoting-point number. Raises an
101 exception if the parameter is given but not in an float format.
103 If 'default' is given, then it will be returned when the parameter
104 is missing completely. When 'default' is None, an error will be
105 raised on a missing parameter.
107 value = self.get(name)
110 if default is not None:
113 self.raise_error(f"Parameter '{name}' missing.")
118 self.raise_error(f"Parameter '{name}' must be a number.")
120 if math.isnan(fval) or math.isinf(fval):
121 self.raise_error(f"Parameter '{name}' must be a number.")
126 def get_bool(self, name: str, default: Optional[bool] = None) -> bool:
127 """ Return an input parameter as bool. Only '0' is accepted as
128 an input for 'false' all other inputs will be interpreted as 'true'.
130 If 'default' is given, then it will be returned when the parameter
131 is missing completely. When 'default' is None, an error will be
132 raised on a missing parameter.
134 value = self.get(name)
137 if default is not None:
140 self.raise_error(f"Parameter '{name}' missing.")
145 def raise_error(self, msg: str, status: int = 400) -> NoReturn:
146 """ Raise an exception resulting in the given HTTP status and
147 message. The message will be formatted according to the
148 output format chosen by the request.
150 if self.content_type == CONTENT_XML:
151 msg = f"""<?xml version="1.0" encoding="UTF-8" ?>
153 <code>{status}</code>
154 <message>{msg}</message>
157 elif self.content_type == CONTENT_JSON:
158 msg = f"""{{"error":{{"code":{status},"message":"{msg}"}}}}"""
159 elif self.content_type == CONTENT_HTML:
160 loglib.log().section('Execution error')
161 loglib.log().var_dump('Status', status)
162 loglib.log().var_dump('Message', msg)
163 msg = loglib.get_and_disable()
165 raise self.error(msg, status)
168 EndpointFunc = Callable[[NominatimAPIAsync, ASGIAdaptor], Any]