1 # SPDX-License-Identifier: GPL-3.0-or-later
 
   3 # This file is part of Nominatim. (https://nominatim.org)
 
   5 # Copyright (C) 2025 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 ..core import NominatimAPIAsync
 
  16 from ..types import QueryStatistics
 
  17 from ..result_formatting import FormatDispatcher
 
  18 from .content_types import CONTENT_TEXT
 
  21 class ASGIAdaptor(abc.ABC):
 
  22     """ Adapter class for the different ASGI frameworks.
 
  23         Wraps functionality over concrete requests and responses.
 
  25     content_type: str = CONTENT_TEXT
 
  28     def get(self, name: str, default: Optional[str] = None) -> Optional[str]:
 
  29         """ Return an input parameter as a string. If the parameter was
 
  30             not provided, return the 'default' value.
 
  34     def get_header(self, name: str, default: Optional[str] = None) -> Optional[str]:
 
  35         """ Return a HTTP header parameter as a string. If the parameter was
 
  36             not provided, return the 'default' value.
 
  40     def error(self, msg: str, status: int = 400) -> Exception:
 
  41         """ Construct an appropriate exception from the given error message.
 
  42             The exception must result in a HTTP error with the given status.
 
  46     def create_response(self, status: int, output: str, num_results: int) -> Any:
 
  47         """ Create a response from the given parameters. The result will
 
  48             be returned by the endpoint functions. The adaptor may also
 
  49             return None when the response is created internally with some
 
  52             The response must return the HTTP given status code 'status', set
 
  53             the HTTP content-type headers to the string provided and the
 
  54             body of the response to 'output'.
 
  58     def base_uri(self) -> str:
 
  59         """ Return the URI of the original request.
 
  63     def config(self) -> Configuration:
 
  64         """ Return the current configuration object.
 
  68     def formatting(self) -> FormatDispatcher:
 
  69         """ Return the formatting object to use.
 
  73     def query_stats(self) -> Optional[QueryStatistics]:
 
  74         """ Return the object for saving query statistics or None if
 
  75             no statistics are required.
 
  78     def get_int(self, name: str, default: Optional[int] = None) -> int:
 
  79         """ Return an input parameter as an int. Raises an exception if
 
  80             the parameter is given but not in an integer format.
 
  82             If 'default' is given, then it will be returned when the parameter
 
  83             is missing completely. When 'default' is None, an error will be
 
  84             raised on a missing parameter.
 
  86         value = self.get(name)
 
  89             if default is not None:
 
  92             self.raise_error(f"Parameter '{name}' missing.")
 
  97             self.raise_error(f"Parameter '{name}' must be a number.")
 
 101     def get_float(self, name: str, default: Optional[float] = None) -> float:
 
 102         """ Return an input parameter as a flaoting-point number. Raises an
 
 103             exception if the parameter is given but not in an float format.
 
 105             If 'default' is given, then it will be returned when the parameter
 
 106             is missing completely. When 'default' is None, an error will be
 
 107             raised on a missing parameter.
 
 109         value = self.get(name)
 
 112             if default is not None:
 
 115             self.raise_error(f"Parameter '{name}' missing.")
 
 120             self.raise_error(f"Parameter '{name}' must be a number.")
 
 122         if math.isnan(fval) or math.isinf(fval):
 
 123             self.raise_error(f"Parameter '{name}' must be a number.")
 
 127     def get_bool(self, name: str, default: Optional[bool] = None) -> bool:
 
 128         """ Return an input parameter as bool. Only '0' is accepted as
 
 129             an input for 'false' all other inputs will be interpreted as 'true'.
 
 131             If 'default' is given, then it will be returned when the parameter
 
 132             is missing completely. When 'default' is None, an error will be
 
 133             raised on a missing parameter.
 
 135         value = self.get(name)
 
 138             if default is not None:
 
 141             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         raise self.error(self.formatting().format_error(self.content_type, msg, status),
 
 154 EndpointFunc = Callable[[NominatimAPIAsync, ASGIAdaptor], Any]