diff --git a/api/schema.py b/api/schema.py index 339913e1cc..3055047bdb 100644 --- a/api/schema.py +++ b/api/schema.py @@ -4,6 +4,7 @@ from drf_spectacular.extensions import OpenApiAuthenticationExtension from drf_spectacular.openapi import AutoSchema +from drf_spectacular.plumbing import alpha_operation_sorter from rest_framework.response import Response @@ -37,3 +38,52 @@ def preprocess_ignore_deprecated_paths(endpoints: list[ENDPOINT]) -> list[ENDPOI for path, path_regex, method, callback in endpoints if path not in IGNORED_PATHS ] + + +# Organize partial paths by tags +# The partial path is the part after /api/v1/, like /api/v1/{partial_path}/ +# The tag is set with the @extend_schema decorator +_TAG_TO_PARTIAL_PATH = { + "email": {"domainaddresses", "first-forwarded-email", "relayaddresses"}, + "privaterelay": { + "flags", + "profiles", + "report_webcompat_issue", + "runtime_data", + "terms-accepted-user", + "users", + }, + "phones": {"inboundcontact", "realphone", "relaynumber", "vCard"}, + "phones: Twilio": {"inbound_call", "inbound_sms", "sms_status", "voice_status"}, + "phones: Outbound": {"call", "message", "messages"}, + "phones: Inteliquent": {"inbound_sms_iq"}, +} + +# Reverse the dictionary to be partial paths to their tag +_PARTIAL_PATH_TO_TAG: dict[str, str] = {} +for _tag, _paths in _TAG_TO_PARTIAL_PATH.items(): + for _path in _paths: + _PARTIAL_PATH_TO_TAG[_path] = _tag + +# The order to display tag groups in the browsable API +_TAG_ORDER = { + "UNKNOWN": 0, + "privaterelay": 10, + "email": 20, + "phones": 30, + "phones: Twilio": 31, + "phones: Inteliquent": 32, + "phones: Outbound": 33, +} + + +def sort_by_tag(endpoint: ENDPOINT) -> tuple[int, str, int]: + """ + Sort paths by their tag, then name, then method. + + The browseable APIs will sort by tag, but in the order returned by this sort key. + """ + drf_order = alpha_operation_sorter(endpoint) + partial_path = endpoint[0].split("/")[3] + tag = _PARTIAL_PATH_TO_TAG.get(partial_path, "UNKNOWN") + return (_TAG_ORDER[tag], drf_order[0], drf_order[1]) diff --git a/privaterelay/settings.py b/privaterelay/settings.py index 0bb0b12940..e453445cbc 100644 --- a/privaterelay/settings.py +++ b/privaterelay/settings.py @@ -721,6 +721,7 @@ def set_index_cache_control_headers( "VERSION": "1.0", "SERVE_INCLUDE_SCHEMA": False, "PREPROCESSING_HOOKS": ["api.schema.preprocess_ignore_deprecated_paths"], + "SORT_OPERATIONS": "api.schema.sort_by_tag", } if IN_PYTEST or RELAY_CHANNEL in ["local", "dev"]: