# Copyright (C) 2017-2025 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
import functools
from typing import Dict, List, Literal, Optional
from django.http.response import HttpResponseBase
from rest_framework import serializers
from rest_framework.decorators import api_view
from swh.web.api import throttling
from swh.web.api.apiresponse import make_api_response
from swh.web.utils.urlsindex import UrlsIndex
CategoryId = Literal[
    "Archive",
    "Batch download",
    "Metadata",
    "Request archival",
    "Miscellaneous",
    "test",
    "External IDentifiers",
    "Provenance",
]
[docs]
class APIUrls(UrlsIndex):
    """Class to manage API URLs and endpoint documentation URLs."""
    apidoc_routes: Dict[str, Dict[str, str]] = {}
[docs]
    def get_app_endpoints(self) -> Dict[str, Dict[str, str]]:
        return self.apidoc_routes 
[docs]
    def add_doc_route(
        self,
        route: str,
        category: CategoryId,
        docstring: str,
        noargs: bool = False,
        api_version: str = "1",
        **kwargs,
    ) -> None:
        """
        Add a route to the self-documenting API reference
        """
        route_name = route[1:-1].replace("/", "-")
        if not noargs:
            route_name = "%s-doc" % route_name
        route_view_name = "api-%s-%s" % (api_version, route_name)
        if route not in self.apidoc_routes:
            d = {
                "category": category,
                "docstring": docstring,
                "route": "/api/%s%s" % (api_version, route),
                "route_view_name": route_view_name,
            }
            for k, v in kwargs.items():
                d[k] = v
            self.apidoc_routes[route] = d 
 
api_urls = APIUrls()
[docs]
def api_route(
    url_pattern: str,
    view_name: str,
    methods: List[str] = ["GET", "HEAD", "OPTIONS"],
    throttle_scope: str = "swh_api",
    api_version: str = "1",
    checksum_args: Optional[List[str]] = None,
    never_cache: bool = False,
    api_urls: APIUrls = api_urls,
    query_params_serializer: Optional[type[serializers.Serializer]] = None,
):
    """
    Decorator to ease the registration of an API endpoint
    using the Django REST Framework.
    Args:
        url_pattern: the url pattern used by DRF to identify the API route
        view_name: the name of the API view associated to the route used to
            reverse the url
        methods: array of HTTP methods supported by the API route
        throttle_scope: Named scope for rate limiting
        api_version: web API version
        checksum_args: list of view argument names holding checksum values
        never_cache: define if api response must be cached
        query_params_serializer: an optional DRF serializer to validate the API call
            query parameters
    """
    url_pattern = "api/" + api_version + url_pattern
    def decorator(f):
        # create a DRF view from the wrapped function
        @api_view(methods)
        @throttling.throttle_scope(throttle_scope)
        @functools.wraps(f)
        def api_view_f(request, **kwargs) -> HttpResponseBase:
            """Creates and calls a DRF view from the wrapped function.
            If `query_params_serializer` is set an extra kwarg `validated_query_params`
            will be added to the view call containing the serializer's `validated_data`.
            Raises:
                django.core.exceptions.ValidationError: query_params_serializer was
                    unable to validate the query parameters
            Returns:
                An HttpResponse
            """
            # never_cache will be handled in apiresponse module
            request.never_cache = never_cache
            # a DRF serializer has been passed to validate the query parameters
            if query_params_serializer:
                serializer = query_params_serializer(data=request.query_params.dict())
                serializer.is_valid(raise_exception=True)
                kwargs["validated_query_params"] = serializer.validated_data
            response = f(request, **kwargs)
            doc_data = None
            # check if response has been forwarded by api_doc decorator
            if isinstance(response, dict) and "doc_data" in response:
                doc_data = response["doc_data"]
                response = response["data"]
            # check if HTTP response needs to be created
            api_response: HttpResponseBase
            if not isinstance(response, HttpResponseBase):
                api_response = make_api_response(
                    request, data=response, doc_data=doc_data
                )
            else:
                api_response = response
            return api_response
        # small hacks for correctly generating API endpoints index doc
        api_view_f.__name__ = f.__name__
        api_view_f.http_method_names = methods
        # register the route and its view in the endpoints index
        api_urls.add_url_pattern(url_pattern, api_view_f, view_name)
        if checksum_args:
            api_urls.add_redirect_for_checksum_args(
                view_name, [url_pattern], checksum_args
            )
        return f
    return decorator