Coverage for functions \ flipdare \ message \ user_error_code.py: 100%
0 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
13# pragma: no cover
14from __future__ import annotations
15from enum import StrEnum
16import re
17from typing import Any
19from flipdare.error.app_error import AppError
20from flipdare.job.cron_validator import CronValidator
21from flipdare.job.trigger_data import TriggerData
23__all__ = [
24 "UserErrorCode",
25]
27_SCHEMA_PATTERN = r"(_?schema)+"
30_APP_ERROR_REPLACEMENTS = {
31 "RESTRICTION": "R",
32 "UNEXPECTED": "U",
33 "WRONG_AUTH_REQUEST": "AUTH",
34 "INVALID": "INV",
35 "HTML": "H",
36 "DARE": "H",
37 "GENERATION": "GEN",
38 "CURRENCY": "CUR",
39 "PROCESSING": "PROC",
40}
43class UserStripeErrorCode(StrEnum):
44 BALANCE_NOT_PRESENT = "BAL_NOT_PRESENT"
45 ACCOUNT_NOT_SUPPORTED = "ACC_NOT_SUPP"
46 ACCOUNT_HAS_NO_TYPE = "ACC_NO_TYPE"
47 ACCOUNT_NOT_FOUND = "ACC_NOT_FOUND"
50class UserErrorCode:
51 @staticmethod
52 def fallback_code(error: Exception) -> str:
53 if isinstance(error, AppError):
54 code = error.error_code.value.upper()
56 for target, replacement in _APP_ERROR_REPLACEMENTS.items():
57 code = code.replace(target, replacement)
58 return code
59 else:
60 # just return str(error) but cleaned up to be a valid code
61 code = str(error).upper()
62 code = re.sub(r"\W+", "_", code)
63 code = re.sub(r"_+", "_", code)
64 code = code.strip("_")
65 if not code:
66 code = "UNKNOWN_ERROR"
67 return f"ERR_{code}"
69 @staticmethod
70 def from_trigger_data(validator: TriggerData[Any, Any]) -> str:
71 errors = validator.errors
72 ct = len(errors) + 1 if errors is not None else 1
74 return UserErrorCode.validation(
75 class_type=validator.__class__,
76 error_ct=ct,
77 )
79 @staticmethod
80 def from_cron_validator(cron: CronValidator) -> str:
81 errors = cron.errors
82 ct = len(errors) + 1 if errors is not None else 1
84 return UserErrorCode.validation(
85 class_type=cron.__class__,
86 error_ct=ct,
87 )
89 @staticmethod
90 def validation(class_type: type[Any], error_ct: int, parse_failed: bool = False) -> str:
91 class_name = class_type.__name__
93 snake_name = "".join(
94 ["_" + c.lower() if c.isupper() else c for c in class_name],
95 ).lstrip("_")
97 class_name = re.sub(_SCHEMA_PATTERN, "", snake_name, flags=re.IGNORECASE)
98 actual_class_name = "code" if not class_name else class_name.lower()
100 # this shortens the name for the user without losing the meaning, for example:
101 # miss_stripe_create_account_request_data_2
102 # miss_stripe_create_account_data_2
103 actual_class_name = actual_class_name.replace("request", "")
105 # final cleanup
106 actual_class_name = actual_class_name.replace("__", "_").strip("_")
108 # class_abbrev = "".join(word[0] for word in actual_class_name.split("_") if word)
109 # if class_abbrev:
110 # actual_class_name = class_abbrev
112 parse_code = "D" if parse_failed else "V"
113 code = f"mi_{actual_class_name}_{parse_code}{error_ct}".upper()
115 # now we shorten for the user
117 code = code.replace("STRIPE", "STR")
118 code = code.replace("ACCOUNT", "ACC")
119 code = code.replace("PAYMENT", "PAY")
121 return code.replace("REQUEST", "REQ")