diff --git a/swh/web/api/apidoc.py b/swh/web/api/apidoc.py --- a/swh/web/api/apidoc.py +++ b/swh/web/api/apidoc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 The Software Heritage developers +# Copyright (C) 2015-2022 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 @@ -15,11 +15,12 @@ import docutils.parsers.rst import docutils.utils +from django.shortcuts import redirect from rest_framework.decorators import api_view from swh.web.api.apiresponse import make_api_response from swh.web.api.apiurls import APIUrls, CategoryId -from swh.web.utils import parse_rst +from swh.web.utils import parse_rst, reverse class _HTTPDomainDocVisitor(docutils.nodes.NodeVisitor): @@ -368,11 +369,22 @@ return make_api_response(request, None, doc_data) route_name = "%s-doc" % route[1:-1].replace("/", "-") - urlpattern = f"^{api_version}{route}doc/$" + urlpattern = f"^api/{api_version}{route}doc/$" view_name = "api-%s-%s" % (api_version, route_name) APIUrls.add_url_pattern(urlpattern, doc_view, view_name) + # for backward compatibility as previous apidoc URLs were missing + # the /api prefix + old_view_name = view_name.replace("api-", "") + old_urlpattern = f"^{api_version}{route}doc/$" + + @api_view(["GET", "HEAD"]) + def old_doc_view(request): + return redirect(reverse(view_name)) + + APIUrls.add_url_pattern(old_urlpattern, old_doc_view, old_view_name) + @wraps(f) def documented_view(request, **kwargs): doc_data = get_doc_data(f, route, noargs) diff --git a/swh/web/api/apiresponse.py b/swh/web/api/apiresponse.py --- a/swh/web/api/apiresponse.py +++ b/swh/web/api/apiresponse.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2021 The Software Heritage developers +# Copyright (C) 2017-2022 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 @@ -10,6 +10,7 @@ from django.http import HttpResponse from django.shortcuts import render +from django.urls import get_resolver from django.utils.cache import add_never_cache_headers from django.utils.html import escape from rest_framework.exceptions import APIException @@ -152,9 +153,16 @@ # generate breadcrumbs data if "route" in doc_data: + all_view_names = set(get_resolver().reverse_dict.keys()) doc_data["endpoint_path"] = gen_path_info(doc_data["route"]) for i in range(len(doc_data["endpoint_path"]) - 1): - doc_data["endpoint_path"][i]["path"] += "/doc/" + view_name = "api-1-" + "-".join( + [doc_data["endpoint_path"][i]["name"] for i in range(i + 1)] + ) + if view_name in all_view_names: + doc_data["endpoint_path"][i]["path"] += "/doc/" + else: + doc_data["endpoint_path"][i]["path"] = "" if not doc_data["noargs"]: doc_data["endpoint_path"][-1]["path"] += "/doc/" diff --git a/swh/web/api/templates/apidoc.html b/swh/web/api/templates/apidoc.html --- a/swh/web/api/templates/apidoc.html +++ b/swh/web/api/templates/apidoc.html @@ -1,7 +1,7 @@ {% extends "layout.html" %} {% comment %} -Copyright (C) 2015-2020 The Software Heritage developers +Copyright (C) 2015-2022 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 @@ -19,7 +19,7 @@
  • endpoints
  • {% for endpoint in endpoint_path %}
  • - {% if endpoint.name != 'stat' and endpoint.name != 'vault' and endpoint.path != 'vault/revision/doc/' %} + {% if endpoint.path %}
  • {{ endpoint.name }}
  • {% else %}
  • {{ endpoint.name }}
  • diff --git a/swh/web/tests/api/test_apidoc.py b/swh/web/tests/api/test_apidoc.py --- a/swh/web/tests/api/test_apidoc.py +++ b/swh/web/tests/api/test_apidoc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2019 The Software Heritage developers +# Copyright (C) 2015-2022 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 @@ -12,6 +12,7 @@ from swh.storage.exc import StorageAPIError, StorageDBError from swh.web.api.apidoc import _parse_httpdomain_doc, api_doc from swh.web.api.apiurls import api_route +from swh.web.tests.django_asserts import assert_contains, assert_not_contains from swh.web.tests.helpers import check_api_get_responses, check_html_get_response from swh.web.utils import prettify_html, reverse from swh.web.utils.exc import BadInputExc, ForbiddenExc, NotFoundExc @@ -96,8 +97,24 @@ def test_apidoc_route_doc(client): - url = reverse("api-1-some-doc-route-doc") - check_html_get_response(client, url, status_code=200, template_used="apidoc.html") + api_view_name = "api-1-some-doc-route-doc" + doc_url = reverse(api_view_name) + assert doc_url == "/api/1/some/doc/route/doc/" + resp = check_html_get_response( + client, doc_url, status_code=200, template_used="apidoc.html" + ) + + # check apidoc breadcrumbs links + api_view_name_split = api_view_name.split("-") + for i in range(2, len(api_view_name_split) - 1): + sub_doc_url = "/" + ("/".join(api_view_name_split[:i])) + "/doc/" + assert_not_contains(resp, f'') + assert_contains(resp, f'') + + # check previous erroneous URL now redirects to the fixed one + url = reverse("1-some-doc-route-doc") + resp = check_html_get_response(client, url, status_code=302) + assert resp["location"] == doc_url def test_apidoc_route_fn(api_client):