|
17 | 17 | from integrated_channels.utils import generate_formatted_log, stringify_and_store_api_record |
18 | 18 |
|
19 | 19 | MOODLE_ERROR_STATUS_MAP = { |
| 20 | + # Duplicate/Conflict errors (409) |
20 | 21 | "shortnametaken": 409, |
21 | 22 | "courseidnumbertaken": 409, |
| 23 | + "morethanonerecordinfetch": 409, |
| 24 | + "duplicateemail": 409, |
| 25 | + "duplicateidnumber": 409, |
| 26 | + "duplicateusername": 409, |
| 27 | + |
| 28 | + # Not Found errors (404) |
22 | 29 | "cannotfindcourse": 404, |
23 | 30 | "cannotfinduser": 404, |
| 31 | + "invalidrecord": 404, |
| 32 | + "usernamenotfound": 404, |
| 33 | + "coursenotfound": 404, |
| 34 | + "usernotfound": 404, |
| 35 | + "invalidcourseid": 404, |
| 36 | + "invaliduserid": 404, |
| 37 | + "modulenotfound": 404, |
| 38 | + "activitynotfound": 404, |
| 39 | + |
| 40 | + # Bad Request errors (400) |
24 | 41 | "missingparam": 400, |
| 42 | + "codingerror": 400, |
| 43 | + "invalidparameter": 400, |
| 44 | + "invalidparametervalue": 400, |
| 45 | + "requiredparametermissing": 400, |
| 46 | + "invalidresponse": 400, |
| 47 | + "invaliddata": 400, |
| 48 | + |
| 49 | + # Authentication errors (401) |
| 50 | + "invalidlogin": 401, |
| 51 | + "invalidtoken": 401, |
| 52 | + "sessiontimeout": 401, |
| 53 | + "requiresauthentication": 401, |
| 54 | + |
| 55 | + # Authorization/Permission errors (403) |
| 56 | + "accessexception": 403, |
| 57 | + "errorcatcontextnotvalid": 403, |
| 58 | + "usernotfullysetup": 403, |
| 59 | + "nopermission": 403, |
| 60 | + "nopermissions": 403, |
| 61 | + "requirecapability": 403, |
| 62 | + "contextupdatefailed": 403, |
| 63 | + "notpermittedtoaccesscourse": 403, |
| 64 | + |
| 65 | + # Server/Database errors (503) |
| 66 | + "dbconnectionfailed": 503, |
| 67 | + "dmlreadexception": 503, |
| 68 | + "dmlwriteexception": 503, |
| 69 | + "dmlexception": 503, |
| 70 | + "databaseerror": 503, |
| 71 | + |
| 72 | + # Service/Method errors (400) |
| 73 | + "invalidservicename": 400, |
| 74 | + "invalidfunctionname": 400, |
| 75 | + "servicerequireslogin": 401, |
| 76 | + |
| 77 | + # Enrollment errors (400) |
| 78 | + "cannotenrol": 400, |
| 79 | + "enrolnotpermitted": 403, |
| 80 | + "alreadyenrolled": 409, |
| 81 | + |
| 82 | + # Grade/Completion errors (400) |
| 83 | + "invalidcompletion": 400, |
| 84 | + "invalidgrade": 400, |
| 85 | + "itemidnotfound": 404, |
25 | 86 | } |
26 | 87 |
|
27 | 88 | LOGGER = logging.getLogger(__name__) |
28 | 89 |
|
29 | 90 |
|
| 91 | +def _effective_error_status_code(status_code, error_code=None): |
| 92 | + """ |
| 93 | + Map Moodle error codes to correct HTTP status codes. |
| 94 | + Moodle returns 200 response status code on failed transmission jobs which is the |
| 95 | + historical and default behavior of Moodle web services. This function corrects |
| 96 | + the status code based on the error_code in the response body. |
| 97 | + Args: |
| 98 | + status_code: The HTTP status code from the response |
| 99 | + error_code: The error code from Moodle's response body |
| 100 | + Returns: |
| 101 | + int: The effective status code (mapped from MOODLE_ERROR_STATUS_MAP, |
| 102 | + or 555 for unknown semantic errors, or original status_code) |
| 103 | + """ |
| 104 | + # First check if we have a mapped status for this error code |
| 105 | + if error_code and error_code in MOODLE_ERROR_STATUS_MAP: |
| 106 | + return MOODLE_ERROR_STATUS_MAP[error_code] |
| 107 | + |
| 108 | + # If status code indicates error (>=400), keep it |
| 109 | + if status_code and status_code >= 400: |
| 110 | + return status_code |
| 111 | + |
| 112 | + # If we get here, Moodle returned a 2xx/3xx status with an unknown error code |
| 113 | + # This is a semantic error - the HTTP status says success but there's an error |
| 114 | + if error_code: |
| 115 | + return 555 # Semantic error for unknown Moodle failure |
| 116 | + |
| 117 | + # No error code and good status - this shouldn't happen in error path |
| 118 | + return status_code if status_code else 500 |
| 119 | + |
| 120 | + |
30 | 121 | class MoodleClientError(ClientError): |
31 | 122 | """ |
32 | 123 | Indicate a problem when interacting with Moodle. |
@@ -102,14 +193,16 @@ def inner(self, *args, **kwargs): |
102 | 193 | self.token = self._get_access_token() # pylint: disable=protected-access |
103 | 194 | response = method(self, *args, **kwargs) |
104 | 195 | elif error_code: |
| 196 | + status_code = _effective_error_status_code(response.status_code, error_code) |
| 197 | + |
105 | 198 | raise MoodleClientError( |
106 | 199 | 'Moodle API Client Task "{method}" failed with error code ' |
107 | 200 | '"{code}" and message: "{msg}" '.format( |
108 | 201 | method=method.__name__, |
109 | 202 | code=error_code, |
110 | 203 | msg=body.get('message'), |
111 | 204 | ), |
112 | | - response.status_code, |
| 205 | + status_code, |
113 | 206 | moodle_error={ |
114 | 207 | 'mapped_status': mapped_status, |
115 | 208 | 'error_code': error_code, |
|
0 commit comments