• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / 3e6f828a-50b0-45bf-b098-2d142215ca72

10 Sep 2024 04:52PM CUT coverage: 85.531% (+0.1%) from 85.425%
3e6f828a-50b0-45bf-b098-2d142215ca72

push

circleci

web-flow
Merge pull request #5001 from mozilla/refactor-relay-sms-exception-mpp3722

MPP-3722, MPP-3513, MPP-3890, MPP-3373: Handle more errors when relaying SMS messages

4113 of 5264 branches covered (78.13%)

Branch coverage included in aggregate %.

264 of 272 new or added lines in 11 files covered. (97.06%)

1 existing line in 1 file now uncovered.

16145 of 18421 relevant lines covered (87.64%)

10.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.0
/api/exceptions.py
1
from __future__ import annotations
1✔
2

3
from typing import TYPE_CHECKING, Any, TypedDict
1✔
4

5
from rest_framework import status
1✔
6
from rest_framework.exceptions import APIException
1✔
7

8
from privaterelay.ftl_bundles import main as ftl_bundle
1✔
9

10
if TYPE_CHECKING:
11
    # Provided by djangorestframework-stubs
12
    # https://github.com/typeddjango/djangorestframework-stubs/blob/master/rest_framework-stubs/exceptions.pyi
13
    from rest_framework.exceptions import _APIExceptionInput
14

15

16
class ConflictError(APIException):
1✔
17
    status_code = status.HTTP_409_CONFLICT
1✔
18
    default_detail = "Request conflicts with current state of the target resource."
1✔
19
    default_code = "conflict_error"
1✔
20

21

22
ErrorContextType = dict[str, int | str]
1✔
23

24

25
class OptionalErrorData(TypedDict, total=False):
1✔
26
    error_context: ErrorContextType
1✔
27

28

29
class ErrorData(OptionalErrorData):
1✔
30
    detail: str
1✔
31
    error_code: str | list[Any] | dict[str, Any] | None
1✔
32

33

34
class RelayAPIException(APIException):
1✔
35
    """
36
    Base class for exceptions that may be returned through the API.
37

38
    Derived classes should set `default_code` to a unique string identifying the
39
    exception. The `ftl_id` should be a matching Fluent string with an `api-error-`
40
    prefix. For example, the `ftl_id` "api-error-free-tier-limit" matches
41
    the exception class with the `default_code` "free-tier-limit". These Fluent
42
    strings are in misc.ftl. While the `ftl_id` _could_ be constructed from the
43
    `default_code`, it is included as a cross-referencing aid.
44

45
    Derived classes can set `default_detail` to a human-readable string, or they
46
    can set `default_detail_template` to dynamically create the string from extra
47
    context. When using default_detail_template, the Fluent string should use the same
48
    variable names.
49

50
    Derived classes can set `status_code` to the HTTP status code, or accept the
51
    APIException default of 500 for a Server Error.
52
    """
53

54
    default_code: str
1✔
55
    default_detail: str
1✔
56
    status_code: int
1✔
57
    ftl_id: str
1✔
58
    ftl_id_prefix = "api-error-"
1✔
59

60
    def __init__(
1✔
61
        self, detail: _APIExceptionInput = None, code: str | None = None
62
    ) -> None:
63
        """Check that derived classes have set the required data."""
64
        if not isinstance(self.default_code, str):
1!
65
            raise TypeError("default_code must be type str")
×
66
        if not isinstance(self.status_code, int):
1!
67
            raise TypeError("self.status_code must be type int")
×
68
        if hasattr(self, "default_detail_template"):
1✔
69
            context = self.error_context()
1✔
70
            if not context:
1!
71
                raise ValueError("error_context is required")
×
72
            self.default_detail = self.default_detail_template.format(**context)
1✔
73
        if not isinstance(self.default_detail, str):
1!
74
            raise TypeError("self.default_detail must be type str")
×
75
        if not isinstance(self.ftl_id, str):
1!
NEW
76
            raise TypeError("self.ftl_id must be type str")
×
77
        super().__init__(detail, code)
1✔
78

79
        # Validated the Fluent error ID ftl_id
80
        error_code = self.get_codes()
1✔
81
        if not isinstance(error_code, str):
1!
NEW
82
            raise TypeError("error_code must be type str")
×
83
        ftl_id_error = error_code.replace("_", "-")
1✔
84
        expected_ftl_id = self.ftl_id_prefix + ftl_id_error
1✔
85
        if expected_ftl_id != self.ftl_id:
1!
NEW
86
            raise ValueError(f'ftl_id is "{self.ftl_id}", expected "{expected_ftl_id}"')
×
87

88
    def error_context(self) -> ErrorContextType:
1✔
89
        """Return context variables for client-side translation."""
90
        return {}
1✔
91

92
    def error_data(self) -> ErrorData:
1✔
93
        """Return extra data for API error responses."""
94

95
        # Replace the default message with the translated Fluent string
96
        error_context = self.error_context()
1✔
97
        translated_detail = ftl_bundle.format(self.ftl_id, error_context)
1✔
98

99
        error_data = ErrorData(detail=translated_detail, error_code=self.get_codes())
1✔
100
        if error_context:
1✔
101
            error_data["error_context"] = error_context
1✔
102
        return error_data
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc