├── .gitignore ├── README.md ├── UNLICENSE ├── advanced.png ├── build.sh ├── commonErrors.png ├── customErrors.png ├── examples └── customErrors.dwl ├── exchange-docs └── home.md ├── generatedError.png ├── handlerFlow.png ├── mule-artifact.json ├── pom.xml └── src ├── main └── resources │ ├── custom-errors-schema.json │ ├── module-error-handler-plugin-catalog.xml │ ├── module-error-handler-plugin.xml │ ├── module_error_handler_plugin │ ├── common.dwl │ └── defaultErrors.dwl │ ├── output-attribute-schema.json │ └── output-response-schema.json └── test └── resources ├── examples ├── MultiValidationModuleError.json ├── ParallelForeachError.json ├── ScatterGather.json ├── UntilSuccessfulError.json └── ValidationModuleError.json └── log4j2-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ # 2 | # Java defaults (https://github.com/github/gitignore/blob/master/Java.gitignore) # 3 | # ------------------------------------------------------------------------------ # 4 | *.class 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # ------------------------------------------------------------------------------------------- # 12 | # Eclipse-specific (https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore) # 13 | # ------------------------------------------------------------------------------------------- # 14 | *.pydevproject 15 | .metadata 16 | bin/** 17 | tmp/** 18 | tmp/**/* 19 | *.tmp 20 | *.bak 21 | *.swp 22 | *~.nib 23 | local.properties 24 | .externalToolBuilders/ 25 | .settings/ 26 | .loadpath 27 | .project 28 | .classpath 29 | .springBeans 30 | 31 | # External tool builders 32 | .externalToolBuilders/ 33 | 34 | # Locally stored "Eclipse launch configurations" 35 | *.launch 36 | 37 | # CDT-specific 38 | .cproject 39 | 40 | # PDT-specific 41 | .buildpath 42 | 43 | # --------------- # 44 | # Studio-specific # 45 | # --------------- # 46 | target/ 47 | reports/ 48 | .mule/** 49 | .mule/**/* 50 | velocity.log 51 | 52 | # Log files # 53 | /*.log 54 | /*.log.* 55 | 56 | # Output folders # 57 | /test-output/ 58 | 59 | # IntelliJ Idea files # 60 | .idea 61 | .idea/ 62 | *.iml 63 | out/ 64 | .idea_modules/ 65 | 66 | # OS files # 67 | .DS_Store 68 | .svn 69 | ._* 70 | /bin/ 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API Error Handler 2 | 3 | The error handler module processes any errors thrown in a flow and transforms to the correct JSON response body and HTTP status code for an API. 4 | 5 | *This requires the [MuleSoft Enterprise Maven Repository][mule-ee] to compile and use.* 6 | 7 | All APIKit and HTTP exceptions are handled by the module and can be customized in the *Common Errors* tab. Additional error definitions can be added via dataweave in the *Custom Errors* tab. 8 | 9 | The error is converted by this module into the items below. 10 | 11 | - **HTTP status code**: *Status code* from the [HTTP RFC 7231 Response Status Codes][http-rfc-7231-6] for [Client Error 4xx][http-rfc-7231-6.5] and [Server Error 5xx][http-rfc-7231-6.6]. 12 | - **JSON response body** 13 | - **Error status**: *Status code*; see above. 14 | - **Error reason**: *Reason phrase* from the [HTTP RFC 7231 Response Status Codes][http-rfc-7231-6] for [Client Error 4xx][http-rfc-7231-6.5] and [Server Error 5xx][http-rfc-7231-6.6]. 15 | - **Error message**: This can be any text description, array, or object. It is usually the Mule `error.description` or developer-defined dataweave or text. 16 | 17 | A JSON body response example is shown below. Note that the top-level field name, `error`, is customizable. 18 | 19 | **StatusCode: 400** 20 | 21 | ``` 22 | { 23 | "error": { 24 | "code": 400, 25 | "reason": "Bad Request", 26 | "message": "Error validating response" 27 | } 28 | } 29 | ``` 30 | 31 | ## Features 32 | This module provides all the features below. It provides the main features of previous versions, while providing the developer the ability to completely customize their error messages. 33 | 34 | - Converts all errors into proper API JSON request body and HTTP status code. 35 | - Customize error messages for HTTP & APIKIT errors. 36 | - Customize error message for the default error when no errors matched: 500 - Internal Server Error. 37 | - Provide custom error mappings with dataweave. 38 | - Automatically propagate errors from downstream APIs automatically, if desired. 39 | - Automatically use the generated error description for the error response message, if desired. 40 | - Un-clutter exceptions both in UI and XML. 41 | - Compatible with `on-error-propagate` and `on-error-continue` error handlers. 42 | - All error types are parsed by this module. 43 | - Supports strings, arrays, or objects in the `message` response field, which applies to previous error messages also. 44 | - Log error message, separate from API response payload, that stringifies and aggregates all error messages. This is available in the error handler flow for printing in the error logger. This feature is useful since you only want to return a single error to the caller but would like to log all errors for troubleshooting. This aggregates current error message, previous error message, and the error object's description field. 45 | 46 | ### Error Messages Used by Module 47 | The error messages that are used by this module are described below. The module allows for customizing which one you use for your response to the caller via dataweave. The module aggregates them all together into a string for error logging. 48 | 49 | - **Error Message**: the error message generated from the matching error in Common Errors or Custom Errors. This defaults to the **Error Object Description**. If no previous error is found, this is returned as the error message. 50 | - **Previous Error Message**: the error message pulled from the error object's response payload (e.g. called API's error response). If found, this is returned as the error message. This can be completely customized but defaults to the error response payload produced by this module. 51 | - **Error Object Description**: the error message in the error object's description. This is used for the Error Message in Common Errors or Custom Errors by default. However, those can be customized to use something else instead. 52 | 53 | ## Compatibility 54 | ### General 55 | - **`Java 17`: requires minimum Mule version `4.6.0`** 56 | - **`Java 9/11`: requires minimum Mule version `4.4.0`** 57 | 58 | ### Version `6.0.0` 59 | - **The XML component namespace changed due to internal requirements so previous versions of this should be deleted from an app before using this version.** 60 | - **The API response format has breaking changes.** While the response format has changed from versions previous to `6.0.0`, which breaks the API contract, you can create the same response format by transforming this module's response to the old format, if that is desired. 61 | 62 | ## Versions 63 | The released versions are listed below. The latest version is in master branch, and we recommend using that version. However, if you want to use a previous version, you can fork the repository and branch from the version tag listed below. Previous versions are not maintained, and no issues or PRs to previous versions will be accepted. 64 | 65 | This module may run on a runtime previous to the minimum listed, but it has not been tested on that runtime. 66 | 67 | | Version Tag | Minimum Runtime | 68 | | ----------- | --------------- | 69 | | 4.0.0 | 4.2.0 | 70 | | 5.0.0 | 4.3.0 | 71 | | 6.0.0 - 6.2.0 | 4.4.0 | 72 | | 6.3.0 | 4.6.0 | 73 | 74 | ### Changes from versions previous to 6.0.0 75 | 76 | - The XML component namespace changed to `module-error-handler-plugin`. 77 | - The error response was simplified to only contain the error information. The API metadata was removed and `errorDetails` was changed to `error`. The previous error response was highly opinionated; the new response is simple and flexible to different needs. The metadata can be added outside of this component if needed. The API metadata is recommended to be returned via headers and not in the response body. 78 | - There is no longer a global configuration. 79 | - The error response fields below were removed. 80 | - correlationId 81 | - timestamp 82 | - apiName 83 | - version 84 | - success 85 | - The Use Previous Error feature now replaces the error message instead of added to an array of error messages that includes the current error. 86 | - The error, previously errorDetails, is now a single object and not an array. The message is a string but can be customized to an array or object. 87 | 88 | # Operations 89 | 90 | ## Process Error 91 | This operation processes any exception to a proper API error response. It provides the outputs below. 92 | 93 | - `payload`: the HTTP response body with the error details. 94 | - `attributes.httpStatus`: the HTTP response status code. 95 | - `attributes.errorLog`: the string of all aggregated errors: error message, previous error message, and error object's description. The module converts all types to strings and removes duplicates and empties. 96 | 97 | # Installation 98 | 99 | This is a custom Mule module that is installed like any other connector, it is installed by adding the dependency to the pom or from "Search Exchange" button in Studio. Once installed, it will show up in the Anypoint Studio palette. 100 | 101 | # Configuration 102 | This section describes how to configure and use the error handler in an app. This module does not have a separate global configuration for ease of use. 103 | 104 | ## Maven Dependency 105 | You can find and install from Exchange in Studio. 106 | 107 | Alternatively, you can manually add the dependency below to an app's pom.xml to include the API Error Handler in the app's maven build. 108 | The groupId value must be the appropriate Anypoint Org Id where the module is deployed. 109 | 110 | ``` 111 | 112 | ${groupId} 113 | api-error-handler 114 | ${error-handler.module.version} 115 | mule-plugin 116 | 117 | ``` 118 | 119 | ## App Preparation 120 | This replaces the generated error handling so a few steps have to be done to prepare the app to use the error handler. 121 | 122 | - Delete the APIKit's auto-generated error blocks (`on-error-propagate`/`on-error-continue`) before using this module. 123 | - Set the outbound HTTP Status variable, `vars.httpStatus` when using APIKit, from the HTTP status attribute set by the module: `attributes.httpStatus`. This is how the status code is sent to the caller. 124 | - Update HTTP Listener's response values to properly use the generated body and HTTP status from the module. See below. 125 | 126 | ### Update HTTP Listener Response 127 | 128 | Add `vars.httpStatus` to the listener's `http:response` and `http:error-response` elements. Also make sure that the `http:error-response` element has the `payload` as its body. These are child elements of ``. 129 | 130 | ``` 131 | 133 | 134 | ``` 135 | 136 | ``` 137 | 139 | 140 | 141 | ``` 142 | 143 | ## Error Handler Flow 144 | 145 | 1. Drag the *Process Error* operation from Studio's palette into the error handler to transform errors into API response. Place the module inside an error block: `on-error-continue`. 146 | 2. Set vars.httpStatus = attributes.httpStatus 147 | 3. Log error message: attributes.errorLog 148 | 4. Reference this error handler in the APIkit's main flow to be the top-level error handler for the API. 149 | 150 | 151 | **Module XML** 152 | 153 | ``` 154 | 157 | ``` 158 | 159 | **Module XML with Custom Errors** 160 | 161 | ``` 162 | 165 | 166 | 167 | 168 | 169 | ``` 170 | 171 | ### Example 172 | An example of the full error handler flow is shown below. This example uses the built-in logger for logging the error. 173 | 174 | ![Error Handler Flow](handlerFlow.png "Error Handler Flow") 175 | 176 | ``` 177 | 178 | 182 | 185 | 189 | 194 | 195 | 196 | ``` 197 | 198 | ## Common Errors Tab 199 | **Customize APIKit & HTTP Error Messages** 200 | 201 | Modify the error message for the APIKit and HTTP errors on the *Common Errors* tab. This field supports dataweave for dynamically generated messages if needed. 202 | The response status code and error reason (phrase) *cannot* be changed for common errors on this tab. 203 | 204 | - Additional errors not covered here can be mapped to the same status codes with the *Custom Errors* feature. 205 | - If you want to change the status code or reason, use the *Custom Errors* feature to override the desired APIKit or HTTP exceptions. 206 | 207 | ![Common Errors Tab](commonErrors.png "Common Errors Tab") 208 | 209 | ### Use Generated Error Message 210 | You can set the error message to the generated error description from the error object, `error.description`, based on the *Use Generated Error Description Instead* selection. If it evaluates to true, the generated error will be used as the error message. If it evaluates to false, the user-provided message will be used. This selection only applies to common errors. It does not apply to custom errors. If you want to add dynamic error messages via dataweave, then set this to `false` and add the dataweave into the message fields. 211 | 212 | ![Use Generated Error](generatedError.png "Use Generated Error") 213 | 214 | **Note:** The only exception to using generated errors is the *dataweave Expression Error*, which does not use the generated error description, regardless of the setting since this can be a security risk. If you want to add the generated error to this error, you will have to explicitly do that in its message field. 215 | 216 | ## Custom Errors Tab 217 | **Customize Full Error Definitions** 218 | 219 | You can add any number of custom error definitions for the module to include in the mapping. This is done by defining these custom error mappings inline or in a [dataweave file][dataweave-file]. The screenshot shows using a file. 220 | 221 | ![Custom Errors Tab](customErrors.png "Custom Errors Tab") 222 | 223 | ### Using a File 224 | 225 | A file is recommended. This file should be in or below `src/main/resources` folder in the Mule app. Recommended practice is to put it in an *errors* folder: `src/main/resources/errors`. 226 | 227 | When adding the file name to the *Custom Errors* field in the module, make sure to include the full relative path from the resources folder. 228 | Example: if the custom errors file is `src/main/resources/errors/customErrors.dwl` then this field should be `errors/customErrors.dwl`. The full syntax for importing a dataweave file and processing it is listed below. 229 | 230 | ``` 231 | ${file::errors/customErrors.dwl} 232 | ``` 233 | 234 | ### Error Format 235 | The custom errors must be an *object of objects* with the fields below. 236 | 237 | - Key: [Mule error type][mule-error-types] used to match. Example: `HTTP:BAD_REQUEST` 238 | - Value: (object) 239 | - `code`: HTTP status code to send in response. This is a number. 240 | - `reason`: Error reason phrase to send in JSON body response. This is a string. 241 | - `message`: Error details to send in JSON body response. A string is preferred for this field, but any type is allowed. 242 | 243 | Dataweave script is allowed in each field value. To access the error object in this definition, you use `error` as normal. 244 | 245 | **Custom errors override common errors.** If you want to override a common error's status or reason, and not just the message, you would add an entry for that error in the custom errors definition, which will completely override the common error. 246 | 247 | A template custom error file, [examples/customErrors.dwl](./examples/customErrors.dwl), is shown below. This template shows common patterns. 248 | 249 | - Custom errors: `APP:UNAUTHORIZED` & `APP:SERVICE_UNAVAILABLE`. This maps custom exceptions raised in the app to specific error responses. 250 | - Common error override: `HTTP:INTERNAL_SERVER_ERROR`. This is recommended when not using previous error for all errors, since it uses previous error for all 500 responses. This can also be done in the Common Errors tab. 251 | - Unknown error handler: `MULE:UNKNOWN`. This is recommended when you want to propagate message and error code from non-standard errors, like `495`. 252 | 253 | ``` 254 | /** 255 | * This provides custom error handling for the API Error Handler. 256 | */ 257 | 258 | %dw 2.0 259 | output application/json 260 | import * from module_error_handler_plugin::common 261 | 262 | /** 263 | * Previous error nested in the Mule error object. 264 | * Provides the entire payload of the previous error as a String. 265 | * Handles the main Mule Error formats to get nested errors: 266 | * - Composite modules/scopes, like Scatter-Gather, Parallel-Foreach, Group Validation Module 267 | * - Until-Successful 268 | * - Standard Error, like Raise Error, Foreach, and most connectors and errors. 269 | */ 270 | var previousError = do { 271 | var nested = [ 272 | error.childErrors..errorMessage.payload, // Composite 273 | error.suppressedErrors..errorMessage.payload, // Until-Successful 274 | error.exception.errorMessage.typedValue // Standard Error: must go last because it has content if this is one of the other types of errors 275 | ] dw::core::Arrays::firstWith !isEmpty($) 276 | --- 277 | if (nested is Array) 278 | toString(nested map (toString($)) distinctBy $) 279 | else 280 | toString(nested) 281 | } 282 | 283 | --- 284 | { 285 | /* 286 | APP 401 Unauthorized 287 | This catches custom service unauthorized error from app and formats the response accordingly. 288 | */ 289 | "APP:UNAUTHORIZED": { 290 | code: 401, 291 | reason: "Unauthorized", 292 | message: error.description 293 | }, 294 | 295 | /* 296 | APP 503 Service Unavailable 297 | This catches custom service unavailable error from app and formats the response accordingly. 298 | */ 299 | "APP:SERVICE_UNAVAILABLE": { 300 | code: 503, 301 | reason: "Service Unavailable", 302 | message: error.description 303 | }, 304 | 305 | /* 306 | HTTP 500 Pass Through 307 | This catches HTTP 500 errors and propagates the detailed reason for failure. 308 | It uses the error.message field from the response of the HTTP call that failed for the message, which conforms to the API Error Handler responses. 309 | If not found, the error.description will be returned, which generally says an internal server error occurred. 310 | This useful for process or experience APIs to pass through system API errors. 311 | */ 312 | "HTTP:INTERNAL_SERVER_ERROR": { 313 | code: 500, 314 | reason: "Internal Server Error", 315 | message: if (!isEmpty(previousError)) previousError else error.description 316 | }, 317 | 318 | /* 319 | Unknown Errors 320 | This catches unknown errors, which includes any non-standard HTTP error status code and propagates the detailed reason for failure. 321 | It tries to use the called API's error response code and phrase if available in the error. If not, it uses the default 500 response. 322 | It uses the error.message field from the response of the HTTP call that failed for the message, which conforms to the API Error Handler responses. 323 | If not found, the error.description will be returned, which generally says an internal server error occurred. 324 | */ 325 | "MULE:UNKNOWN": { 326 | code: error.exception.errorMessage.attributes.statusCode default 500, 327 | reason: error.exception.errorMessage.attributes.reasonPhrase default "Internal Server Error", 328 | message: if (!isEmpty(previousError)) previousError else error.description 329 | } 330 | } 331 | ``` 332 | 333 | ### Common Functions 334 | There are some common functions provided by the module that you can use in your custom errors definition. They are imported by `import * from module_error_handler_plugin::common`. 335 | 336 | - `getErrorTypeAsString`: Gets the string for the current Mule error type. This corresponds to the *keys* in the custom error object. Example: `HTTP:INTERNAL_SERVER_ERROR`. 337 | - `toString`: Converts any type to a string. If not a string, it uses write() with Java format. If empty, then returns empty string or the value specified in the second parameter. 338 | 339 | ## Advanced Tab 340 | **General Configuration** 341 | 342 | General configuration is defined on the *Advanced* tab. This includes the *Error Object* definition and *Use Previous Error* feature. 343 | 344 | ![Advanced Tab](advanced.png "Advanced Tab") 345 | 346 | ### Error Object 347 | The error object definition takes the standard [Mule Error][mule-error] by default, which is the recommended setting. You can change this as long as the provided object has the same fields as the [Mule Error][mule-error]. 348 | 349 | ### Use Previous Error 350 | Connectors usually generate error responses their own error responses and wrap the actual error response from the external system in the error object. This causes the external system's response to be lost and not propagated back to the API's caller. The previous error feature allows the module to retrieve the external system's error response from the error object and use that as the error message. 351 | 352 | A common scenario is when a system API generates an error that needs to get propagated back to the caller of the experience or process API. Using normal error handling, like `error.description`, the SOAP fault or `500` response from the called system is not logged or propagated. These items are nested in the error object here: `error.exception.errorMessage.typedValue.payload` and `error.exception.errorMessage.typedValue.attributes`. Be aware that payload and attributes won't be accessable by selector if the content is `Binary`. If the type is `Binary`, then you must read the error payload, `error.exception.errorMessage.typedValue`, as the correct MIME type if you want to access a specific field using a selector. 353 | 354 | This feature will automatically replace the `message` field for ***all errors*** with the previous error defined by the provided dataweave if one exists. If the previous error does not exist or is empty, then it will leave the `message` field with its current value. This feature does not append the previous error to the current one. It simply replaces and is best used to propagate downstream errors up the API stack. 355 | 356 | The dataweave should resolve to a string but any type is allowed. You can override in any manner; the template custom error file gives a comprehensive example on how to convert nested errors to strings. 357 | 358 | **Set this field to an empty string if you do not want to propagate previous errors. This is the default value** 359 | 360 | ### Response Key Name for Payload 361 | This field allows you to customize the JSON key name where the error payload is set. This defaults to `error`, which is shown in the examples. This only supports changing the name of the top-level key; it does not change any other format. If you want the error payload to be the top-level element in the response, then set this field to empty string. 362 | 363 | **Default Response**: error 364 | ``` 365 | { 366 | "error": { 367 | "code": 400, 368 | "reason": "Bad Request", 369 | "message": "Error validating response" 370 | } 371 | } 372 | ``` 373 | 374 | **Custom Response**: errorDetails 375 | ``` 376 | { 377 | "errorDetails": { 378 | "code": 400, 379 | "reason": "Bad Request", 380 | "message": "Error validating response" 381 | } 382 | } 383 | ``` 384 | 385 | **Custom Response**: empty string 386 | ``` 387 | { 388 | "code": 400, 389 | "reason": "Bad Request", 390 | "message": "Error validating response" 391 | } 392 | ``` 393 | 394 | ## Error Handling Tips 395 | 396 | ### Override the Default Error 397 | If you want to override the default error when no errors matched (500 - Internal Server Error), use the error type `UNKNOWN` in the custom errors definition. This allows updating **unmapped** errors, which is the default error. If you only want to change the message, update it in the *Common Errors* tab, instead of in *Custom Errors*. 398 | 399 | ``` 400 | "UNKNOWN": { 401 | "code":528, 402 | "reason": "API Error", 403 | "message": error.description 404 | } 405 | ``` 406 | 407 | ### Override Non-Standard HTTP Status Code Errors 408 | If you want to override the error when no errors matched (500 - Internal Server Error) for **non-standard HTTP status code responses**, like `455`, use the error type `MULE:UNKNOWN` in the custom errors definition. This allows updating **unknown** errors, which correspond to non-standard HTTP status codes when coming from HTTP requester. If you don't want to distinguish between *unmapped* errors (`UNKNOWN`) and *unknown* errors (`MULE:UNKNOWN`), then use the `UNKNOWN` type. 409 | 410 | This propagates the HTTP status code, reasonPhrase, and message from the external system's error response. 411 | 412 | ``` 413 | "MULE:UNKNOWN": { 414 | code: error.exception.errorMessage.attributes.statusCode default 500, 415 | reason: error.exception.errorMessage.attributes.reasonPhrase default "Internal Server Error", 416 | message: if (!isEmpty(previousError)) previousError else error.description 417 | } 418 | ``` 419 | 420 | ### Downstream API Errors 421 | As described in the *Use Previous Error* feature in the *Advanced* tab, sometimes it is useful to get the downstream error when it is generated from a connector. That feature forces the downstream error propagation for all errors. 422 | 423 | Sometimes, you only want to propagate certain errors and not all. It is best practice to propagate `500` error message from Mule APIs up the API network. However, you may not want to propagate the error if it was a connection or authentication issue, which may provide info that you don't want going back to the caller. 424 | 425 | If you want to only propagate specific errors, then leave the *Use Previous Error* field empty and only put the previous error messages in specific common or custom errors. 426 | 427 | Accessing a previous error message from a downstream API that uses the API Handler Module or conforms to its response format can be done using the `getPreviousErrorMessage` common function. Since this requires an import statement, it's most useful in the custom errors definition. The common error message fields are best set using the specific nested error directly: `error.exception.errorMessage.payload.error.message default ""` 428 | 429 | The example dataweave snippet below shows how to access a called Mule API's error message for propagating, assuming that those message are in `error.message` in the response body. It falls back to the standard error description if the previous error is not available. 430 | 431 | ``` 432 | import * from module_error_handler_plugin::common 433 | var previousError = getPreviousErrorMessage(error) 434 | --- 435 | ``` 436 | 437 | ``` 438 | message: if (!isEmpty(previousError)) previousError else error.description 439 | ``` 440 | 441 | ### List of Errors 442 | The `message` field in the response body can be of any type. A string is recommended, but this can also be an array of errors if needed to show history of errors across APIs. To create an array of errors, follow the steps below. 443 | 444 | - Create the list of errors with dataweave in the `message` field contained in the *Custom Error* definition. This can be done in the message fields in the *Common Error* tab for the common errors. 445 | - Log the error in your flow while handling the array so it logs appropriately. 446 | 447 | You can also simply set the `message` to a single string containing the merged array of errors. 448 | 449 | ### Force All Errors to 500 450 | If you have a special, non-standard, use-case where you need to force all of the API's errors to 500 with the same error message then you can use a single custom error to override all errors. 451 | 452 | Do this by dynamically setting the error type key with the `getErrorTypeAsString` function. This will always match the current error. This is should be avoided unless you have a requirement to do this. 453 | 454 | ``` 455 | import * from module_error_handler_plugin::common 456 | var errorType = getErrorTypeAsString(error.errorType) 457 | --- 458 | { 459 | (errorType): { 460 | code: 500, 461 | reason: "Internal Service Error", 462 | message: error.description 463 | } 464 | } 465 | ``` 466 | 467 | # Building 468 | When building this module, the required dependencies are provided by the standard MuleSoft maven repositories. Standard maven build commands work without any additional parameters required. This is a [Mule XML SDK][xml-sdk] module. Use `build.sh` as described under **Deploying** below to build the module. 469 | 470 | # Deploying 471 | 472 | The `build.sh` script executes maven commands on the module, including deploying to Exchange. For deploying to Anypoint Exchange or other binary repositorities, server credentials must be in your maven `settings.xml` file, and repository and server properly linked in `pom.xml`. 473 | 474 | It takes the parameters below. 475 | 476 | 1. Build option: the type of build to execute: [package, install, deploy]. 477 | - package: builds the package without installing 478 | - install: builds locally (installs dependencies and module in local maven repository) 479 | - deploy: deploys module to Exchange 480 | 2. Maven Group Id: the Maven group id for the artifact. Can be Anypoint business organization to which to deploy the module. 481 | 3. Repo ID: the ID of the Maven repository. Defaults to Exchange2 if not provided. 482 | 4. Repo URL: the URL of the Maven repository. Defaults to Anypoint Exchange URL if not provided. 483 | 484 | **Syntax** 485 | ``` 486 | ./build.sh [build option] [Maven Group ID] [repo ID] [repo URL] 487 | ``` 488 | 489 | **Example** 490 | ``` 491 | ./build.sh deploy 43ae201-c97b-4665-9310-e3ac89ce1c28 492 | ``` 493 | 494 | [mule-ee]: https://docs.mulesoft.com/mule-runtime/latest/maven-reference#configure-mulesoft-enterprise-repository 495 | [mule-error]: https://docs.mulesoft.com/mule-runtime/4.4/mule-error-concept 496 | [mule-error-types]: https://docs.mulesoft.com/mule-runtime/4.4/mule-error-concept#error_types 497 | [http-rfc-7231-6]: https://tools.ietf.org/html/rfc7231#section-6 498 | [http-rfc-7231-6.5]: https://tools.ietf.org/html/rfc7231#section-6.5 499 | [http-rfc-7231-6.6]: https://tools.ietf.org/html/rfc7231#section-6.6 500 | [dataweave-file]: https://docs.mulesoft.com/dataweave/2.4/dataweave-language-introduction#dwl_file 501 | [xml-sdk]: https://docs.mulesoft.com/mule-sdk/1.1/xml-sdk 502 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mulesoft-catalyst/error-handler-plugin/de0fec691ff2293b70ee6f125eda42794c2c7a2f/advanced.png -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script builds/deploys the module locally and to a remote Maven repository. 4 | # 5 | # The default distributionManagement setup supports Anypoint Exchange as the remote repository. It requires the items below to work. 6 | # - Use the Anypoint Business Organization ID for the Maven Group ID where you want to deploy the module. 7 | # - Anypoint credentials must be in your Maven settings.xml file. The default repository ID is Exchange2 8 | # 9 | # Command syntax: ./build.sh [build option] [Maven Group ID] [repo ID] [repo URL] 10 | # 11 | # PARAMETERS 12 | # 1. Build option: the type of build to execute: [package, install, deploy]. 13 | # - package: builds the package without installing 14 | # - install: builds locally (installs dependencies and module in local maven repository) 15 | # - deploy: deploys module to Exchange 16 | # 2. Maven Group Id: the Maven group id for the artifact. Can be Anypoint business organization to which to deploy the module. 17 | # 3. Repo ID: the ID of the Maven repository. Defaults to Exchange2 if not provided. 18 | # 4. Repo URL: the URL of the Maven repository. Defaults to Anypoint Exchange URL if not provided. 19 | # 20 | 21 | # Assign command line arguments to variables or use default values 22 | GROUP_ID="${2:-com.example}" 23 | REPO_ID="$3" 24 | REPO_URL="$4" 25 | 26 | # Print command to console and execute it. 27 | # $1: bash command 28 | function execute { 29 | printf "%s\n" "$1" 30 | eval $1 31 | } 32 | 33 | # Run provided maven command on the specified project 34 | # $1: Maven phase [package, install, deploy] 35 | function buildProject { 36 | 37 | # Generate POM 38 | execute "sed 's/ORG_ID_TOKEN/${GROUP_ID}/g' pom.xml > pom-generated.xml" 39 | 40 | # Construct Maven command 41 | MAVEN_CMD="mvn -f pom-generated.xml clean $1" 42 | if [ -n "$REPO_ID" ]; then 43 | MAVEN_CMD+=" -Drepo.id=$REPO_ID" 44 | fi 45 | if [ -n "$REPO_URL" ]; then 46 | MAVEN_CMD+=" -Drepo.url=$REPO_URL" 47 | fi 48 | 49 | # Execute maven command 50 | execute "$MAVEN_CMD" 51 | 52 | if [ $? != 0 ] 53 | then 54 | printf "[ERROR] Failed %s %s\n" "$1" 55 | exit 1 56 | fi 57 | 58 | # Delete generated POM 59 | execute "rm -f pom-generated.xml" 60 | 61 | printf "\nCompleted %s %s\n\n" "$1" 62 | } 63 | 64 | # Main script 65 | if [ "$#" -lt 1 ] 66 | then 67 | printf "[ERROR] You need to provide maven build option [package, install, deploy]: ./build.sh [build option] [Maven Group ID] [repo ID] [repo URL]" 68 | exit 1 69 | fi 70 | 71 | # Check if the provided build type is valid 72 | case "$1" in 73 | package|install|deploy) 74 | printf "Starting %s build\n\n" "$1" 75 | buildProject $1 76 | ;; 77 | *) 78 | echo "[ERROR] You need to provide valid build type: [package, install, deploy]" 79 | exit 1 80 | ;; 81 | esac 82 | 83 | printf "\nCompleted Build\n" -------------------------------------------------------------------------------- /commonErrors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mulesoft-catalyst/error-handler-plugin/de0fec691ff2293b70ee6f125eda42794c2c7a2f/commonErrors.png -------------------------------------------------------------------------------- /customErrors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mulesoft-catalyst/error-handler-plugin/de0fec691ff2293b70ee6f125eda42794c2c7a2f/customErrors.png -------------------------------------------------------------------------------- /examples/customErrors.dwl: -------------------------------------------------------------------------------- 1 | /** 2 | * This provides custom error handling for the API Error Handler. 3 | */ 4 | 5 | %dw 2.0 6 | output application/json 7 | import * from module_error_handler_plugin::common 8 | 9 | /** 10 | * Previous error nested in the Mule error object. 11 | * Provides the entire payload of the previous error as a String. 12 | * Handles the main Mule Error formats to get nested errors: 13 | * - Composite modules/scopes, like Scatter-Gather, Parallel-Foreach, Group Validation Module 14 | * - Until-Successful 15 | * - Standard Error, like Raise Error, Foreach, and most connectors and errors. 16 | */ 17 | var previousError = do { 18 | var nested = [ 19 | error.childErrors..errorMessage.payload, // Composite 20 | error.suppressedErrors..errorMessage.payload, // Until-Successful 21 | error.exception.errorMessage.typedValue // Standard Error: must go last because it has content if this is one of the other types of errors 22 | ] dw::core::Arrays::firstWith !isEmpty($) 23 | --- 24 | if (nested is Array) 25 | toString(nested map (toString($)) distinctBy $) 26 | else 27 | toString(nested) 28 | } 29 | 30 | --- 31 | { 32 | /* 33 | APP 401 Unauthorized 34 | This catches custom service unauthorized error from app and formats the response accordingly. 35 | */ 36 | "APP:UNAUTHORIZED": { 37 | code: 401, 38 | reason: "Unauthorized", 39 | message: error.description 40 | }, 41 | 42 | /* 43 | APP 503 Service Unavailable 44 | This catches custom service unavailable error from app and formats the response accordingly. 45 | */ 46 | "APP:SERVICE_UNAVAILABLE": { 47 | code: 503, 48 | reason: "Service Unavailable", 49 | message: error.description 50 | }, 51 | 52 | /* 53 | HTTP 500 Pass Through 54 | This catches HTTP 500 errors and propagates the detailed reason for failure. 55 | It uses the error.message field from the response of the HTTP call that failed for the message, which conforms to the API Error Handler responses. 56 | If not found, the error.description will be returned, which generally says an internal server error occurred. 57 | This useful for process or experience APIs to pass through system API errors. 58 | */ 59 | "HTTP:INTERNAL_SERVER_ERROR": { 60 | code: 500, 61 | reason: "Internal Server Error", 62 | message: if (!isEmpty(previousError)) previousError else error.description 63 | }, 64 | 65 | /* 66 | Unknown Errors 67 | This catches unknown errors, which includes any non-standard HTTP error status code and propagates the detailed reason for failure. 68 | It tries to use the called API's error response code and phrase if available in the error. If not, it uses the default 500 response. 69 | It uses the error.message field from the response of the HTTP call that failed for the message, which conforms to the API Error Handler responses. 70 | If not found, the error.description will be returned, which generally says an internal server error occurred. 71 | */ 72 | "MULE:UNKNOWN": { 73 | code: error.exception.errorMessage.attributes.statusCode default 500, 74 | reason: error.exception.errorMessage.attributes.reasonPhrase default "Internal Server Error", 75 | message: if (!isEmpty(previousError)) previousError else error.description 76 | } 77 | } -------------------------------------------------------------------------------- /exchange-docs/home.md: -------------------------------------------------------------------------------- 1 | # API Error Handler Overview 2 | 3 | This error handler module processes any errors thrown in a flow and transforms to the correct JSON response body and HTTP status code for an API. 4 | 5 | All APIKit and HTTP exceptions are handled by the module and can be customized in the *Common Errors* tab. Additional error definitions can be added via dataweave in the *Custom Errors* tab. 6 | 7 | The error is converted by this module into the items below. 8 | 9 | - **HTTP status code**: *Status code* from the [HTTP RFC 7231 Response Status Codes][http-rfc-7231-6] for [Client Error 4xx][http-rfc-7231-6.5] and [Server Error 5xx][http-rfc-7231-6.6]. 10 | - **JSON response body** 11 | - **Error status**: *Status code*; see above. 12 | - **Error reason**: *Reason phrase* from the [HTTP RFC 7231 Response Status Codes][http-rfc-7231-6] for [Client Error 4xx][http-rfc-7231-6.5] and [Server Error 5xx][http-rfc-7231-6.6]. 13 | - **Error message**: This can be any text description, array, or object. It is usually the Mule `error.description` or developer-defined dataweave or text. 14 | 15 | A JSON body response example is shown below. 16 | 17 | **StatusCode: 400** 18 | 19 | ``` 20 | { 21 | "error": { 22 | "code": 400, 23 | "reason": "Bad Request", 24 | "message": "Error validating response" 25 | } 26 | } 27 | ``` 28 | 29 | ## Features 30 | This module provides all the features below. It provides the main features of previous versions, while providing the developer the ability to completely customize their error messages. 31 | 32 | - Converts all errors into proper API JSON request body and HTTP status code. 33 | - Customize error messages for HTTP & APIKIT errors. 34 | - Customize error message for the default error when no errors matched: 500 - Internal Server Error. 35 | - Provide custom error mappings with dataweave. 36 | - Automatically propagate errors from downstream APIs automatically, if desired. 37 | - Automatically use the generated error description for the error response message, if desired. 38 | - Un-clutter exceptions both in UI and XML. 39 | - Compatible with `on-error-propagate` and `on-error-continue` error handlers. 40 | - All error types are parsed by this module. 41 | - Supports strings, arrays, or objects in the `message` response field, which applies to previous error messages also. 42 | - Log error message, separate from API response payload, that stringifies and aggregates all error messages. This is available in the error handler flow for printing in the error logger. This feature is useful since you only want to return a single error to the caller but would like to log all errors for troubleshooting. This aggregates current error message, previous error message, and the error object's description field. 43 | 44 | ### Error Messages Used by Module 45 | The error messages that are used by this module are described below. The module allows for customizing which one you use for your response to the caller via dataweave. The module aggregates them all together into a string for error logging. 46 | 47 | - **Error Message**: the error message generated from the matching error in Common Errors or Custom Errors. This defaults to the **Error Object Description**. If no previous error is found, this is returned as the error message. 48 | - **Previous Error Message**: the error message pulled from the error object's response payload (e.g. called API's error response). If found, this is returned as the error message. This can be completely customized but defaults to the error response payload produced by this module. 49 | - **Error Object Description**: the error message in the error object's description. This is used for the Error Message in Common Errors or Custom Errors by default. However, those can be customized to use something else instead. 50 | 51 | ## Compatibility 52 | 53 | - **Requires minimum Mule version: 4.3.0** 54 | - **The XML component namespace changed due to internal requirements so previous versions of this should be deleted from an app before using this version.** 55 | - **The API response format has breaking changes.** While the response format has changed from versions previous to `6.0.0`, which breaks the API contract, you can create the same response format by transforming this module's response to the old format, if that is desired. 56 | 57 | ## Versions 58 | The released versions are listed below. The latest version is in master branch, and we recommend using that version. However, if you want to use a previous version, you can fork the repository and branch from the version tag listed below. Previous versions are not maintained, and no issues or PRs to previous versions will be accepted. 59 | 60 | This module may run on a runtime previous to the minimum listed, but it has not been tested on that runtime. 61 | 62 | | Version Tag | Minimum Runtime | 63 | | ----------- | --------------- | 64 | | 4.0.0 | 4.2.0 | 65 | | 5.0.0 | 4.3.0 | 66 | | 6.0.0 | 4.4.0 | 67 | 68 | ### Changes from versions previous to 6.0.0 69 | 70 | - The XML component namespace changed to `module-error-handler-plugin`. 71 | - The error response was simplified to only contain the error information. The API metadata was removed and `errorDetails` was changed to `error`. The previous error response was highly opinionated; the new response is simple and flexible to different needs. The metadata can be added outside of this component if needed. The API metadata is recommended to be returned via headers and not in the response body. 72 | - There is no longer a global configuration. 73 | - The error response fields below were removed. 74 | - correlationId 75 | - timestamp 76 | - apiName 77 | - version 78 | - success 79 | - The Use Previous Error feature now replaces the error message instead of added to an array of error messages that includes the current error. 80 | - The error, previously errorDetails, is now a single object and not an array. The message is a string but can be customized to an array or object. 81 | 82 | # Operations 83 | 84 | ## Process Error 85 | This operation processes any exception to a proper API error response. It provides the outputs below. 86 | 87 | - `payload`: the HTTP response body with the error details. 88 | - `attributes.httpStatus`: the HTTP response status code. 89 | - `attributes.errorLog`: the string of all aggregated errors: error message, previous error message, and error object's description. The module converts all types to strings and removes duplicates and empties. 90 | 91 | # Installation 92 | 93 | This is a custom Mule module that is installed like any other connector, it is installed by adding the dependency to the pom or from "Search Exchange" button in Studio. Once installed, it will show up in the Anypoint Studio palette. 94 | 95 | # Configuration 96 | This section describes how to configure and use the error handler in an app. This module does not have a separate global configuration for ease of use. 97 | 98 | ## Maven Dependency 99 | You can find and install from Exchange in Studio. 100 | 101 | Alternatively, you can manually add the dependency below to an app's pom.xml to include the API Error Handler in the app's maven build. 102 | The groupId value must be the appropriate Anypoint Org Id where the module is deployed. 103 | 104 | ``` 105 | 106 | ${groupId} 107 | api-error-handler 108 | ${error-handler.module.version} 109 | 110 | ``` 111 | 112 | ## App Preparation 113 | This replaces the generated error handling so a few steps have to be done to prepare the app to use the error handler. 114 | 115 | - Delete the APIKit's auto-generated error blocks (`on-error-propagate`/`on-error-continue`) before using this module. 116 | - Set the outbound HTTP Status variable, `vars.httpStatus` when using APIKit, from the HTTP status attribute set by the module: `attributes.httpStatus`. This is how the status code is sent to the caller. 117 | - Update HTTP Listener's response values to properly use the generated body and HTTP status from the module. See below. 118 | 119 | ### Update HTTP Listener Response 120 | 121 | Add `vars.httpStatus` to the listener's `http:response` and `http:error-response` elements. Also make sure that the `http:error-response` element has the `payload` as its body. These are child elements of ``. 122 | 123 | ``` 124 | 126 | 127 | ``` 128 | 129 | ``` 130 | 132 | 133 | 134 | ``` 135 | 136 | ## Error Handler Flow 137 | 138 | Drag the *Process Error* operation from Studio's palette into the error handler to transform errors into API response. Place the module inside an error block: `on-error-propagate`. You usually would not use `on-error-continue` since the error handler module generates the API's error response to the caller. 139 | 140 | Reference this error handler in the APIkit's main flow to be the top-level error handler for the API. 141 | 142 | **Module XML** 143 | 144 | ``` 145 | 148 | ``` 149 | 150 | **Module XML with Custom Errors** 151 | 152 | ``` 153 | 156 | 157 | 158 | 159 | 160 | ``` 161 | 162 | ### Example 163 | An example of the full error handler flow is shown below. This example uses the built-in logger for logging the error. 164 | 165 | ![Error Handler Flow](handlerFlow.png "Error Handler Flow") 166 | 167 | ``` 168 | 169 | 173 | 177 | 181 | 186 | 187 | 188 | ``` 189 | 190 | ## Common Errors Tab 191 | **Customize APIKit & HTTP Error Messages** 192 | 193 | Modify the error message for the APIKit and HTTP errors on the *Common Errors* tab. This field supports dataweave for dynamically generated messages if needed. 194 | The response status code and error reason (phrase) *cannot* be changed for common errors on this tab. 195 | 196 | - Additional errors not covered here can be mapped to the same status codes with the *Custom Errors* feature. 197 | - If you want to change the status code or reason, use the *Custom Errors* feature to override the desired APIKit or HTTP exceptions. 198 | 199 | ![Common Errors Tab](commonErrors.png "Common Errors Tab") 200 | 201 | ### Use Generated Error Message 202 | You can set the error message to the generated error description from the error object, `error.description`, based on the *Use Generated Error Description Instead* selection. If it evaluates to true, the generated error will be used as the error message. If it evaluates to false, the user-provided message will be used. This selection only applies to common errors. It does not apply to custom errors. If you want to add dynamic error messages via dataweave, then set this to `false` and add the dataweave into the message fields. 203 | 204 | ![Use Generated Error](generatedError.png "Use Generated Error") 205 | 206 | **Note:** The only exception to using generated errors is the *dataweave Expression Error*, which does not use the generated error description, regardless of the setting since this can be a security risk. If you want to add the generated error to this error, you will have to explicitly do that in its message field. 207 | 208 | ## Custom Errors Tab 209 | **Customize Full Error Definitions** 210 | 211 | You can add any number of custom error definitions for the module to include in the mapping. This is done by defining these custom error mappings inline or in a [dataweave file][dataweave-file]. The screenshot shows using a file. 212 | 213 | ![Custom Errors Tab](customErrors.png "Custom Errors Tab") 214 | 215 | ### Using a File 216 | 217 | A file is recommended. This file should be in or below `src/main/resources` folder in the Mule app. Recommended practice is to put it in an *errors* folder: `src/main/resources/errors`. 218 | 219 | When adding the file name to the *Custom Errors* field in the module, make sure to include the full relative path from the resources folder. 220 | Example: if the custom errors file is `src/main/resources/errors/customErrors.dwl` then this field should be `errors/customErrors.dwl`. The full syntax for importing a dataweave file and processing it is listed below. 221 | 222 | ``` 223 | ${file::errors/customErrors.dwl} 224 | ``` 225 | 226 | ### Error Format 227 | The custom errors must be an *object of objects* with the fields below. 228 | 229 | - Key: [Mule error type][mule-error-types] used to match. Example: `HTTP:BAD_REQUEST` 230 | - Value: (object) 231 | - `code`: HTTP status code to send in response. This is a number. 232 | - `reason`: Error reason phrase to send in JSON body response. This is a string. 233 | - `message`: Error details to send in JSON body response. A string is preferred for this field, but any type is allowed. 234 | 235 | dataweave script is allowed in each field value. To access the error object in this definition, you use `error` as normal. 236 | 237 | **Custom errors override common errors.** If you want to override a common error's status or reason, and not just the message, you would add an entry for that error in the custom errors definition, which will completely override the common error. 238 | 239 | A template custom error file, [examples/customErrors.dwl](./examples/customErrors.dwl), is shown below. This template shows common patterns. 240 | 241 | - Custom errors: `APP:UNAUTHORIZED` & `APP:SERVICE_UNAVAILABLE`. This maps custom exceptions raised in the app to specific error responses. 242 | - Common error override: `HTTP:INTERNAL_SERVER_ERROR`. This is recommended when not using previous error for all errors, since it uses previous error for all 500 responses. This can also be done in the Common Errors tab. 243 | - Unknown error handler: `MULE:UNKNOWN`. This is recommended when you want to propagate message and error code from non-standard errors, like `495`. 244 | 245 | ``` 246 | %dw 2.0 247 | output application/json 248 | 249 | import * from module_error_handler_plugin::common 250 | 251 | // Previous error nested in the Mule error object, which conforms to the API Error Handler responses. 252 | var previousError = getPreviousErrorMessage(error) 253 | --- 254 | { 255 | /* 256 | APP 401 Unauthorized 257 | This catches custom service unauthorized error from app and formats the response accordingly. 258 | */ 259 | "APP:UNAUTHORIZED": { 260 | code: 401, 261 | reason: "Unauthorized", 262 | message: error.description 263 | }, 264 | 265 | /* 266 | APP 503 Service Unavailable 267 | This catches custom service unavailable error from app and formats the response accordingly. 268 | */ 269 | "APP:SERVICE_UNAVAILABLE": { 270 | code: 503, 271 | reason: "Service Unavailable", 272 | message: error.description 273 | }, 274 | 275 | /* 276 | HTTP 500 Pass Through 277 | This catches HTTP 500 errors and propagates the detailed reason for failure. 278 | It uses the error.message field from the response of the HTTP call that failed for the message, which conforms to the API Error Handler responses. 279 | If not found, the error.description will be returned, which generally says an internal server error occurred. 280 | This useful for process or experience APIs to pass through system API errors. 281 | */ 282 | "HTTP:INTERNAL_SERVER_ERROR": { 283 | code: 500, 284 | reason: "Internal Server Error", 285 | message: if (!isEmpty(previousError)) previousError else error.description 286 | }, 287 | 288 | /* 289 | Unknown Errors 290 | This catches unknown errors, which includes any non-standard HTTP error status code and propagates the detailed reason for failure. 291 | It tries to use the called API's error response code and phrase if available in the error. If not, it uses the default 500 response. 292 | It uses the error.message field from the response of the HTTP call that failed for the message, which conforms to the API Error Handler responses. 293 | If not found, the error.description will be returned, which generally says an internal server error occurred. 294 | */ 295 | "MULE:UNKNOWN": { 296 | code: error.exception.errorMessage.attributes.statusCode default 500, 297 | reason: error.exception.errorMessage.attributes.reasonPhrase default "Internal Server Error", 298 | message: if (!isEmpty(previousError)) previousError else error.description 299 | } 300 | } 301 | ``` 302 | 303 | ### Common Functions 304 | There are some common functions provided by the module that you can use in your custom errors definition. They are imported by `import * from module_error_handler_plugin::common`. 305 | 306 | - `getPreviousErrorMessage`: Gets the downstream API's error message text when an error is returned from that API. This should only used when the called API uses this same error module. Any other response structure must be manually handled by dataweave. It takes the error object as a parameter. This is a great way to propagate errors up the API stack without losing the original error. 307 | - `getErrorTypeAsString`: Gets the string for the current Mule error type. This corresponds to the *keys* in the custom error object. Example: `HTTP:INTERNAL_SERVER_ERROR`. 308 | - `toString`: Converts any type to a string. If not a string, it uses write() with Java format. If empty, then returns empty string or the value specified in the second parameter. 309 | 310 | ## Advanced Tab 311 | **General Configuration** 312 | 313 | General configuration is defined on the *Advanced* tab. This includes the *Error Object* definition and *Use Previous Error* feature. 314 | 315 | ![Advanced Tab](advanced.png "Advanced Tab") 316 | 317 | ### Error Object 318 | The error object definition takes the standard [Mule Error][mule-error] by default, which is the recommended setting. You can change this as long as the provided object has the same fields as the [Mule Error][mule-error]. 319 | 320 | ### Use Previous Error 321 | Connectors usually generate error responses their own error responses and wrap the actual error response from the external system in the error object. This causes the external system's response to be lost and not propagated back to the API's caller. The previous error feature allows the module to retrieve the external system's error response from the error object and use that as the error message. 322 | 323 | A common scenario is when a system API generates an error that needs to get propagated back to the caller of the experience or process API. Using normal error handling, like `error.description`, the SOAP fault or `500` response from the called system is not logged or propagated. These items are nested in the error object here: `error.exception.errorMessage.payload` and `error.exception.errorMessage.attributes`. 324 | 325 | This feature will automatically replace the `message` field for ***all errors*** with the previous error defined by the provided dataweave if one exists. If the previous error does not exist or is empty, then it will leave the `message` field with its current value. This feature does not append the previous error to the current one. It simply replaces and is best used to propagate downstream errors up the API stack. 326 | 327 | The dataweave should resolve to a string but any type is allowed. The default value below resolves to a called Mule API's error message, if the message is in `error.message` in the response body, which conforms to the API Error Handler responses. 328 | 329 | ``` 330 | error.exception.errorMessage.payload.error.message default '' 331 | ``` 332 | 333 | **Note: Leave this field empty if you do not want to propagate previous errors.** 334 | 335 | ## Error Handling Tips 336 | 337 | ### Override the Default Error 338 | If you want to override the default error when no errors matched (500 - Internal Server Error), use the error type `UNKNOWN` in the custom errors definition. This allows updating **unmapped** errors, which is the default error. If you only want to change the message, update it in the *Common Errors* tab, instead of in *Custom Errors*. 339 | 340 | ``` 341 | "UNKNOWN": { 342 | "code":528, 343 | "reason": "API Error", 344 | "message": error.description 345 | } 346 | ``` 347 | 348 | ### Override Non-Standard HTTP Status Code Errors 349 | If you want to override the error when no errors matched (500 - Internal Server Error) for **non-standard HTTP status code responses**, like `455`, use the error type `MULE:UNKNOWN` in the custom errors definition. This allows updating **unknown** errors, which correspond to non-standard HTTP status codes when coming from HTTP requester. If you don't want to distinguish between *unmapped* errors (`UNKNOWN`) and *unknown* errors (`MULE:UNKNOWN`), then use the `UNKNOWN` type. 350 | 351 | This propagates the HTTP status code, reasonPhrase, and message from the external system's error response. 352 | 353 | ``` 354 | "MULE:UNKNOWN": { 355 | code: error.exception.errorMessage.attributes.statusCode default 500, 356 | reason: error.exception.errorMessage.attributes.reasonPhrase default "Internal Server Error", 357 | message: if (!isEmpty(previousError)) previousError else error.description 358 | } 359 | ``` 360 | 361 | ### Downstream API Errors 362 | As described in the *Use Previous Error* feature in the *Advanced* tab, sometimes it is useful to get the downstream error when it is generated from a connector. That feature forces the downstream error propagation for all errors. 363 | 364 | Sometimes, you only want to propagate certain errors and not all. It is best practice to propagate `500` error message from Mule APIs up the API network. However, you may not want to propagate the error if it was a connection or authentication issue, which may provide info that you don't want going back to the caller. 365 | 366 | If you want to only propagate specific errors, then leave the *Use Previous Error* field empty and only put the previous error messages in specific common or custom errors. 367 | 368 | Accessing a previous error message from a downstream API that uses the API Handler Module or conforms to its response format can be done using the `getPreviousErrorMessage` common function. Since this requires an import statement, it's most useful in the custom errors definition. The common error message fields are best set using the specific nested error directly: `error.exception.errorMessage.payload.error.message default ""` 369 | 370 | The example dataweave snippet below shows how to access a called Mule API's error message for propagating, assuming that those message are in `error.message` in the response body. It falls back to the standard error description if the previous error is not available. 371 | 372 | ``` 373 | import * from module_error_handler_plugin::common 374 | var previousError = getPreviousErrorMessage(error) 375 | --- 376 | ``` 377 | 378 | ``` 379 | message: if (!isEmpty(previousError)) previousError else error.description 380 | ``` 381 | 382 | ### List of Errors 383 | The `message` field in the response body can be of any type. A string is recommended, but this can also be an array of errors if needed to show history of errors across APIs. To create an array of errors, follow the steps below. 384 | 385 | - Create the list of errors with dataweave in the `message` field contained in the *Custom Error* definition. This can be done in the message fields in the *Common Error* tab for the common errors. 386 | - Log the error in your flow while handling the array so it logs appropriately. 387 | 388 | You can also simply set the `message` to a single string containing the merged array of errors. 389 | 390 | ### Force All Errors to 500 391 | If you have a special, non-standard, use-case where you need to force all of the API's errors to 500 with the same error message then you can use a single custom error to override all errors. 392 | 393 | Do this by dynamically setting the error type key with the `getErrorTypeAsString` function. This will always match the current error. This is should be avoided unless you have a requirement to do this. 394 | 395 | ``` 396 | import * from module_error_handler_plugin::common 397 | var errorType = getErrorTypeAsString(error.errorType) 398 | --- 399 | { 400 | (errorType): { 401 | code: 500, 402 | reason: "Internal Service Error", 403 | message: error.description 404 | } 405 | } 406 | ``` 407 | 408 | # Building 409 | When building this module, the required dependencies are provided by the standard MuleSoft maven repositories. Standard maven build commands work without any additional parameters required. This is a [Mule XML SDK][xml-sdk] module. Use `build.sh` as described under **Deploying** below to build the module. 410 | 411 | # Deploying 412 | 413 | The `build.sh` script executes maven commands on the module, including deploying to Exchange. For deploying to Anypoint Exchange or other binary repositorities, server credentials must be in your maven `settings.xml` file, and repository and server properly linked in `pom.xml`. 414 | 415 | It takes the parameters below. 416 | 417 | 1. Anypoint Org Id: the Anypoint business organization where to deploy the module. 418 | 2. Build option: the type of build to execute: `local`, `deploy`. 419 | - `local`: builds locally (installs dependencies and module in local maven repository) 420 | - `deploy`: deploys module to Exchange 421 | 422 | **Syntax** 423 | ``` 424 | ./build.sh {Anypoint Org ID} {build option} 425 | ``` 426 | 427 | **Example** 428 | ``` 429 | ./build.sh 43ae201-c97b-4665-9310-e3ac89ce1c28 deploy 430 | ``` 431 | 432 | [mule-error]: https://docs.mulesoft.com/mule-runtime/4.4/mule-error-concept 433 | [mule-error-types]: https://docs.mulesoft.com/mule-runtime/4.4/mule-error-concept#error_types 434 | [http-rfc-7231-6]: https://tools.ietf.org/html/rfc7231#section-6 435 | [http-rfc-7231-6.5]: https://tools.ietf.org/html/rfc7231#section-6.5 436 | [http-rfc-7231-6.6]: https://tools.ietf.org/html/rfc7231#section-6.6 437 | [dataweave-file]: https://docs.mulesoft.com/dataweave/2.4/dataweave-language-introduction#dwl_file 438 | [xml-sdk]: https://docs.mulesoft.com/mule-sdk/1.1/xml-sdk 439 | -------------------------------------------------------------------------------- /generatedError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mulesoft-catalyst/error-handler-plugin/de0fec691ff2293b70ee6f125eda42794c2c7a2f/generatedError.png -------------------------------------------------------------------------------- /handlerFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mulesoft-catalyst/error-handler-plugin/de0fec691ff2293b70ee6f125eda42794c2c7a2f/handlerFlow.png -------------------------------------------------------------------------------- /mule-artifact.json: -------------------------------------------------------------------------------- 1 | { 2 | "minMuleVersion": "4.4.0" 3 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | ORG_ID_TOKEN 6 | api-error-handler 7 | 6.3.0 8 | mule-extension 9 | 10 | API Error Handler 11 | Provides standard REST API error responses 12 | 13 | 14 | UTF-8 15 | UTF-8 16 | 17 | 18 | Exchange2 19 | https://maven.anypoint.mulesoft.com/api/v1/organizations/${project.groupId}/maven 20 | 21 | 22 | 4.6.0 23 | 1.6.1 24 | 4.6.0 25 | 26 | 27 | 28 | 29 | ${repo.id} 30 | Deployment Repo 31 | ${repo.url} 32 | default 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.mule.runtime 40 | mule-module-extensions-xml-support 41 | ${mule.version} 42 | provided 43 | 44 | 45 | 46 | com.mulesoft.mule.runtime.modules 47 | mule-module-spring-config-ee 48 | ${mule.version} 49 | provided 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.mule.runtime.plugins 58 | mule-extensions-maven-plugin 59 | ${mule.extensions.maven.plugin.version} 60 | true 61 | 62 | 63 | 64 | 65 | com.mulesoft.munit 66 | munit-extensions-maven-plugin 67 | 68 | 69 | 70 | org.mule.runtime 71 | mule-core 72 | ${mule.version} 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | mulesoft-releases 82 | MuleSoft Releases Repository 83 | https://repository.mulesoft.org/releases/ 84 | default 85 | 86 | 87 | anypoint-exchange-v3 88 | Anypoint Exchange V3 89 | https://maven.anypoint.mulesoft.com/api/v3/maven 90 | default 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/resources/custom-errors-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "title": "Custom Errors", 4 | "patternProperties": { 5 | ".*": { 6 | "type": "object", 7 | "required": [ 8 | "code", 9 | "reason", 10 | "message" 11 | ], 12 | "properties": { 13 | "code": { 14 | "type": "integer", 15 | "title": "Status Code", 16 | "examples": [503] 17 | }, 18 | "reason": { 19 | "type": "string", 20 | "title": "Reason Phrase", 21 | "examples": ["Bad Connectivity"] 22 | }, 23 | "message": { 24 | "type": ["string","array","object"], 25 | "title": "Detailed Error Message", 26 | "examples": [ 27 | "You have issues accessing the system", 28 | ["You have issues accessing the system", "Unable to connect to https://someurl"] 29 | ] 30 | } 31 | } 32 | } 33 | }, 34 | "additionalProperties": false 35 | } -------------------------------------------------------------------------------- /src/main/resources/module-error-handler-plugin-catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/module-error-handler-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 23 | 24 | 33 | 34 | 42 | 43 | 51 | 52 | 60 | 61 | 69 | 70 | 78 | 79 | 87 | 88 | 96 | 97 | 105 | 106 | 114 | 115 | 123 | 124 | 132 | 133 | 141 | 142 | 150 | 151 | 152 | 160 | 161 | 162 | 170 | 171 | 180 | 181 | 190 | 191 | 192 | 193 | 194 | 201 | 202 | 207 | 208 | 209 | 213 | 214 | 218 | 222 | 223 | 224 | 225 | if (!isEmpty(vars.previousError)) vars.previousError else message 233 | } 234 | --- 235 | if (!isEmpty(responseKey) and (responseKey is String)) 236 | { 237 | (responseKey): responseError 238 | } 239 | else 240 | responseError 241 | ]]> 242 | 243 | 259 | 260 | 261 | 262 | 263 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /src/main/resources/module_error_handler_plugin/common.dwl: -------------------------------------------------------------------------------- 1 | %dw 2.0 2 | 3 | /* 4 | NOTICE: 5 | This file must be in the ./resources/ folder in order to be exported in the META-INF/mule-artifact/mule-artifact.json file. This allows the functions to be used in the module as they are executed in the app's context (without the mule message context). 6 | */ 7 | 8 | /* 9 | * Get the error type as a String 10 | */ 11 | fun getErrorTypeAsString(errorType) = 12 | if (!isBlank(errorType.namespace)) 13 | errorType.namespace ++ ":" ++ (errorType.identifier default "") 14 | else 15 | "UNKNOWN" 16 | 17 | /* 18 | * Get the proper error from the merged default and custom error lists. Provide a standard error if none found. 19 | */ 20 | fun getError(errorType, defaultErrors, customErrors = {}) = do { 21 | import mergeWith from dw::core::Objects 22 | var errorList = (defaultErrors mergeWith (customErrors default {})) 23 | var foundError = errorList[errorType] 24 | var error = if ( !isEmpty(foundError) ) foundError else errorList["UNKNOWN"] 25 | --- 26 | error 27 | } 28 | 29 | /** 30 | * Converts a value to a String representation. 31 | * Binary is converted as-is to Strings since it must be read to get content. 32 | * Primitives are directly converted to Strings. 33 | * Complex objects, like Objects and Arrays, are converted to the String presentation of their Java form. 34 | * 35 | * @p value to convert. 36 | * @p def is the default value if the provided value is empty. 37 | * @r String. If empty, then returns the default value provided 38 | */ 39 | fun toString(value, def="") = do { 40 | var safeValue = if (!isEmpty(value)) value else def default "" // No nulls allowed 41 | --- 42 | typeOf(safeValue) match { 43 | case "String" -> safeValue 44 | case "Number" -> safeValue as String 45 | case "Binary" -> read(safeValue, "text/plain") 46 | else -> write(safeValue, "application/java") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/module_error_handler_plugin/defaultErrors.dwl: -------------------------------------------------------------------------------- 1 | %dw 2.0 2 | output application/json 3 | 4 | // Conditionally set error message to the user-specified or the generated message. 5 | fun getMessage(message) = 6 | if (vars.useGeneratedError default true) 7 | vars.error.description default message 8 | else 9 | message 10 | --- 11 | { 12 | // Default error if no matching errors. 13 | "UNKNOWN": { 14 | code: 500, 15 | reason: "Internal Server Error", 16 | message: getMessage(vars.serverError) 17 | }, 18 | 19 | // List of all standard API-related errors. 20 | "APIKIT:BAD_REQUEST": { 21 | "code": 400, 22 | "reason": "Bad Request", 23 | "message": getMessage(vars.badRequestError) 24 | }, 25 | "HTTP:BAD_REQUEST": { 26 | "code": 400, 27 | "reason": "Bad Request", 28 | "message": getMessage(vars.badRequestError) 29 | }, 30 | "HTTP:PARSING": { 31 | "code": 400, 32 | "reason": "Bad Request", 33 | "message": getMessage(vars.badRequestError) 34 | }, 35 | "HTTP:CLIENT_SECURITY": { 36 | "code": 401, 37 | "reason": "Unauthorized", 38 | "message": getMessage(vars.unauthorizedError) 39 | }, 40 | "HTTP:SECURITY": { 41 | "code": 401, 42 | "reason": "Unauthorized", 43 | "message": getMessage(vars.unauthorizedError) 44 | }, 45 | "MULE:SECURITY": { 46 | "code": 401, 47 | "reason": "Unauthorized", 48 | "message": getMessage(vars.unauthorizedError) 49 | }, 50 | "HTTP:UNAUTHORIZED": { 51 | "code": 401, 52 | "reason": "Unauthorized", 53 | "message": getMessage(vars.unauthorizedError) 54 | }, 55 | "HTTP:FORBIDDEN": { 56 | "code": 403, 57 | "reason": "Forbidden", 58 | "message": getMessage(vars.forbiddenError) 59 | }, 60 | "HTTP:NOT_FOUND": { 61 | "code": 404, 62 | "reason": "Not Found", 63 | "message": getMessage(vars.notFoundError) 64 | }, 65 | "APIKIT:NOT_FOUND": { 66 | "code": 404, 67 | "reason": "Not Found", 68 | "message": getMessage(vars.notFoundError) 69 | }, 70 | "APIKIT:METHOD_NOT_ALLOWED": { 71 | "code": 405, 72 | "reason": "Method Not Allowed", 73 | "message": getMessage(vars.methodNotAllowedError) 74 | }, 75 | "HTTP:METHOD_NOT_ALLOWED": { 76 | "code": 405, 77 | "reason": "Method Not Allowed", 78 | "message": getMessage(vars.methodNotAllowedError) 79 | }, 80 | "APIKIT:NOT_ACCEPTABLE": { 81 | "code": 406, 82 | "reason": "Not Acceptable", 83 | "message": getMessage(vars.notAcceptableError) 84 | }, 85 | "HTTP:NOT_ACCEPTABLE": { 86 | "code": 406, 87 | "reason": "Not Acceptable", 88 | "message": getMessage(vars.notAcceptableError) 89 | }, 90 | "HTTP:TIMEOUT": { 91 | "code":408, 92 | "reason": "Request Timeout", 93 | "message": getMessage(vars.timeoutError) 94 | }, 95 | "APIKIT:UNSUPPORTED_MEDIA_TYPE": { 96 | "code": 415, 97 | "reason": "Unsupported Media Type", 98 | "message": getMessage(vars.unsupportedMediaTypeError) 99 | }, 100 | "HTTP:UNSUPPORTED_MEDIA_TYPE": { 101 | "code": 415, 102 | "reason": "Unsupported Media Type", 103 | "message": getMessage(vars.unsupportedMediaTypeError) 104 | }, 105 | "HTTP:TOO_MANY_REQUESTS": { 106 | "code":429, 107 | "reason": "Too Many Requests", 108 | "message": getMessage(vars.tooManyRequestsError) 109 | }, 110 | "MULE:EXPRESSION": { 111 | "code":500, 112 | "reason": "Internal Server Error", 113 | // Don't provide default error description for expression errors as that is a potential security issue. 114 | "message":vars.expressionError default "" 115 | }, 116 | "APIKIT:NOT_IMPLEMENTED": { 117 | "code": 501, 118 | "reason": "Not Implemented", 119 | "message": getMessage(vars.notImplementedError) 120 | }, 121 | "HTTP:NOT_IMPLEMENTED": { 122 | "code": 501, 123 | "reason": "Not Implemented", 124 | "message": getMessage(vars.notImplementedError) 125 | }, 126 | "HTTP:BAD_GATEWAY": { 127 | "code": 502, 128 | "reason": "Bad Gateway", 129 | "message": getMessage(vars.connectivityError) 130 | }, 131 | "HTTP:CONNECTIVITY": { 132 | "code": 503, 133 | "reason": "Service Unavailable", 134 | "message": getMessage(vars.connectivityError) 135 | }, 136 | "HTTP:RETRY_EXHAUSTED": { 137 | "code": 503, 138 | "reason": "Service Unavailable", 139 | "message": getMessage(vars.connectivityError) 140 | }, 141 | "HTTP:SERVICE_UNAVAILABLE": { 142 | "code": 503, 143 | "reason": "Service Unavailable", 144 | "message": getMessage(vars.connectivityError) 145 | } 146 | } -------------------------------------------------------------------------------- /src/main/resources/output-attribute-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "httpStatus": { 5 | "type": "string" 6 | }, 7 | "errorLog": { 8 | "type": "string" 9 | } 10 | }, 11 | "additionalProperties": false 12 | } -------------------------------------------------------------------------------- /src/main/resources/output-response-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "error":{ 5 | "type": "object", 6 | "properties": { 7 | "code": { 8 | "type":"integer" 9 | }, 10 | "reason": { 11 | "type":"string" 12 | }, 13 | "message":{ 14 | "type":"string" 15 | } 16 | } 17 | } 18 | }, 19 | "additionalProperties": false 20 | } -------------------------------------------------------------------------------- /src/test/resources/examples/MultiValidationModuleError.json: -------------------------------------------------------------------------------- 1 | { 2 | "dslSource": "", 3 | "suppressedErrors": [ 4 | 5 | ], 6 | "errorType": { 7 | "identifier": "MULTIPLE", 8 | "parentErrorType": { 9 | "identifier": "VALIDATION", 10 | "parentErrorType": { 11 | "identifier": "VALIDATION", 12 | "parentErrorType": { 13 | "identifier": "ANY", 14 | "parentErrorType": null, 15 | "namespace": "MULE" 16 | }, 17 | "namespace": "MULE" 18 | }, 19 | "namespace": "VALIDATION" 20 | }, 21 | "namespace": "VALIDATION" 22 | }, 23 | "childErrors": [ 24 | { 25 | "dslSource": "", 26 | "suppressedErrors": [ 27 | 28 | ], 29 | "errorType": { 30 | "identifier": "INVALID_BOOLEAN", 31 | "parentErrorType": { 32 | "identifier": "VALIDATION", 33 | "parentErrorType": { 34 | "identifier": "VALIDATION", 35 | "parentErrorType": { 36 | "identifier": "ANY", 37 | "parentErrorType": null, 38 | "namespace": "MULE" 39 | }, 40 | "namespace": "MULE" 41 | }, 42 | "namespace": "VALIDATION" 43 | }, 44 | "namespace": "VALIDATION" 45 | }, 46 | "childErrors": [ 47 | 48 | ], 49 | "errorMessage": { 50 | "inboundAttachmentNames": [ 51 | 52 | ], 53 | "exceptionPayload": null, 54 | "inboundPropertyNames": [ 55 | 56 | ], 57 | "outboundAttachmentNames": [ 58 | 59 | ], 60 | "payload": { 61 | "errorType": "INVALID_BOOLEAN", 62 | "message": "Muh Error 1!", 63 | "error": true 64 | }, 65 | "outboundPropertyNames": [ 66 | 67 | ], 68 | "attributes": null 69 | }, 70 | "cause": { 71 | "validationResult": { 72 | "errorType": "INVALID_BOOLEAN", 73 | "message": "Muh Error 1!", 74 | "error": true 75 | }, 76 | "localizedMessage": "Muh Error 1!", 77 | "errorMessage": { 78 | "inboundAttachmentNames": [ 79 | 80 | ], 81 | "exceptionPayload": null, 82 | "inboundPropertyNames": [ 83 | 84 | ], 85 | "outboundAttachmentNames": [ 86 | 87 | ], 88 | "payload": { 89 | "errorType": "INVALID_BOOLEAN", 90 | "message": "Muh Error 1!", 91 | "error": true 92 | }, 93 | "outboundPropertyNames": [ 94 | 95 | ], 96 | "attributes": null 97 | }, 98 | "cause": null, 99 | "type": "INVALID_BOOLEAN", 100 | "message": "Muh Error 1!", 101 | "suppressed": [ 102 | 103 | ], 104 | "stackTrace": [ 105 | 106 | ] 107 | }, 108 | "description": "Muh Error 1!", 109 | "detailedDescription": "Muh Error 1!", 110 | "failingComponent": "get:\\validation:application\\json:api-config/processors/1/processors/0 @ test-error-handler:api.xml:70 (Is true)" 111 | }, 112 | { 113 | "dslSource": "", 114 | "suppressedErrors": [ 115 | 116 | ], 117 | "errorType": { 118 | "identifier": "INVALID_BOOLEAN", 119 | "parentErrorType": { 120 | "identifier": "VALIDATION", 121 | "parentErrorType": { 122 | "identifier": "VALIDATION", 123 | "parentErrorType": { 124 | "identifier": "ANY", 125 | "parentErrorType": null, 126 | "namespace": "MULE" 127 | }, 128 | "namespace": "MULE" 129 | }, 130 | "namespace": "VALIDATION" 131 | }, 132 | "namespace": "VALIDATION" 133 | }, 134 | "childErrors": [ 135 | 136 | ], 137 | "errorMessage": { 138 | "inboundAttachmentNames": [ 139 | 140 | ], 141 | "exceptionPayload": null, 142 | "inboundPropertyNames": [ 143 | 144 | ], 145 | "outboundAttachmentNames": [ 146 | 147 | ], 148 | "payload": { 149 | "errorType": "INVALID_BOOLEAN", 150 | "message": "Muh Error 2!", 151 | "error": true 152 | }, 153 | "outboundPropertyNames": [ 154 | 155 | ], 156 | "attributes": null 157 | }, 158 | "cause": { 159 | "validationResult": { 160 | "errorType": "INVALID_BOOLEAN", 161 | "message": "Muh Error 2!", 162 | "error": true 163 | }, 164 | "localizedMessage": "Muh Error 2!", 165 | "errorMessage": { 166 | "inboundAttachmentNames": [ 167 | 168 | ], 169 | "exceptionPayload": null, 170 | "inboundPropertyNames": [ 171 | 172 | ], 173 | "outboundAttachmentNames": [ 174 | 175 | ], 176 | "payload": { 177 | "errorType": "INVALID_BOOLEAN", 178 | "message": "Muh Error 2!", 179 | "error": true 180 | }, 181 | "outboundPropertyNames": [ 182 | 183 | ], 184 | "attributes": null 185 | }, 186 | "cause": null, 187 | "type": "INVALID_BOOLEAN", 188 | "message": "Muh Error 2!", 189 | "suppressed": [ 190 | 191 | ], 192 | "stackTrace": [ 193 | 194 | ] 195 | }, 196 | "description": "Muh Error 2!", 197 | "detailedDescription": "Muh Error 2!", 198 | "failingComponent": "get:\\validation:application\\json:api-config/processors/1/processors/1 @ test-error-handler:api.xml:71 (Is true)" 199 | } 200 | ], 201 | "errorMessage": null, 202 | "cause": { 203 | "localizedMessage": "Muh Error 1!\nMuh Error 2!", 204 | "cause": null, 205 | "type": "MULTIPLE", 206 | "message": "Muh Error 1!\nMuh Error 2!", 207 | "suppressed": [ 208 | 209 | ], 210 | "stackTrace": [ 211 | 212 | ], 213 | "errors": [ 214 | { 215 | "dslSource": "", 216 | "suppressedErrors": [ 217 | 218 | ], 219 | "errorType": { 220 | "identifier": "INVALID_BOOLEAN", 221 | "parentErrorType": { 222 | "identifier": "VALIDATION", 223 | "parentErrorType": { 224 | "identifier": "VALIDATION", 225 | "parentErrorType": { 226 | "identifier": "ANY", 227 | "parentErrorType": null, 228 | "namespace": "MULE" 229 | }, 230 | "namespace": "MULE" 231 | }, 232 | "namespace": "VALIDATION" 233 | }, 234 | "namespace": "VALIDATION" 235 | }, 236 | "childErrors": [ 237 | 238 | ], 239 | "errorMessage": { 240 | "inboundAttachmentNames": [ 241 | 242 | ], 243 | "exceptionPayload": null, 244 | "inboundPropertyNames": [ 245 | 246 | ], 247 | "outboundAttachmentNames": [ 248 | 249 | ], 250 | "payload": { 251 | "errorType": "INVALID_BOOLEAN", 252 | "message": "Muh Error 1!", 253 | "error": true 254 | }, 255 | "outboundPropertyNames": [ 256 | 257 | ], 258 | "attributes": null 259 | }, 260 | "cause": { 261 | "validationResult": { 262 | "errorType": "INVALID_BOOLEAN", 263 | "message": "Muh Error 1!", 264 | "error": true 265 | }, 266 | "localizedMessage": "Muh Error 1!", 267 | "errorMessage": { 268 | "inboundAttachmentNames": [ 269 | 270 | ], 271 | "exceptionPayload": null, 272 | "inboundPropertyNames": [ 273 | 274 | ], 275 | "outboundAttachmentNames": [ 276 | 277 | ], 278 | "payload": { 279 | "errorType": "INVALID_BOOLEAN", 280 | "message": "Muh Error 1!", 281 | "error": true 282 | }, 283 | "outboundPropertyNames": [ 284 | 285 | ], 286 | "attributes": null 287 | }, 288 | "cause": null, 289 | "type": "INVALID_BOOLEAN", 290 | "message": "Muh Error 1!", 291 | "suppressed": [ 292 | 293 | ], 294 | "stackTrace": [ 295 | 296 | ] 297 | }, 298 | "description": "Muh Error 1!", 299 | "detailedDescription": "Muh Error 1!", 300 | "failingComponent": "get:\\validation:application\\json:api-config/processors/1/processors/0 @ test-error-handler:api.xml:70 (Is true)" 301 | }, 302 | { 303 | "dslSource": "", 304 | "suppressedErrors": [ 305 | 306 | ], 307 | "errorType": { 308 | "identifier": "INVALID_BOOLEAN", 309 | "parentErrorType": { 310 | "identifier": "VALIDATION", 311 | "parentErrorType": { 312 | "identifier": "VALIDATION", 313 | "parentErrorType": { 314 | "identifier": "ANY", 315 | "parentErrorType": null, 316 | "namespace": "MULE" 317 | }, 318 | "namespace": "MULE" 319 | }, 320 | "namespace": "VALIDATION" 321 | }, 322 | "namespace": "VALIDATION" 323 | }, 324 | "childErrors": [ 325 | 326 | ], 327 | "errorMessage": { 328 | "inboundAttachmentNames": [ 329 | 330 | ], 331 | "exceptionPayload": null, 332 | "inboundPropertyNames": [ 333 | 334 | ], 335 | "outboundAttachmentNames": [ 336 | 337 | ], 338 | "payload": { 339 | "errorType": "INVALID_BOOLEAN", 340 | "message": "Muh Error 2!", 341 | "error": true 342 | }, 343 | "outboundPropertyNames": [ 344 | 345 | ], 346 | "attributes": null 347 | }, 348 | "cause": { 349 | "validationResult": { 350 | "errorType": "INVALID_BOOLEAN", 351 | "message": "Muh Error 2!", 352 | "error": true 353 | }, 354 | "localizedMessage": "Muh Error 2!", 355 | "errorMessage": { 356 | "inboundAttachmentNames": [ 357 | 358 | ], 359 | "exceptionPayload": null, 360 | "inboundPropertyNames": [ 361 | 362 | ], 363 | "outboundAttachmentNames": [ 364 | 365 | ], 366 | "payload": { 367 | "errorType": "INVALID_BOOLEAN", 368 | "message": "Muh Error 2!", 369 | "error": true 370 | }, 371 | "outboundPropertyNames": [ 372 | 373 | ], 374 | "attributes": null 375 | }, 376 | "cause": null, 377 | "type": "INVALID_BOOLEAN", 378 | "message": "Muh Error 2!", 379 | "suppressed": [ 380 | 381 | ], 382 | "stackTrace": [ 383 | 384 | ] 385 | }, 386 | "description": "Muh Error 2!", 387 | "detailedDescription": "Muh Error 2!", 388 | "failingComponent": "get:\\validation:application\\json:api-config/processors/1/processors/1 @ test-error-handler:api.xml:71 (Is true)" 389 | } 390 | ] 391 | }, 392 | "description": "Muh Error 1!\nMuh Error 2!", 393 | "detailedDescription": "Muh Error 1!\nMuh Error 2!", 394 | "failingComponent": "api-main/processors/0 @ test-error-handler:api.xml:22" 395 | } -------------------------------------------------------------------------------- /src/test/resources/examples/ParallelForeachError.json: -------------------------------------------------------------------------------- 1 | { 2 | "dslSource": "", 3 | "suppressedErrors": [ 4 | 5 | ], 6 | "childErrors": [ 7 | { 8 | "dslSource": "\r\n\r\n", 9 | "suppressedErrors": [ 10 | 11 | ], 12 | "childErrors": [ 13 | 14 | ], 15 | "errorType": { 16 | "identifier": "METHOD_NOT_ALLOWED", 17 | "parentErrorType": { 18 | "identifier": "ANY", 19 | "parentErrorType": null, 20 | "namespace": "MULE" 21 | }, 22 | "namespace": "HTTP" 23 | }, 24 | "errorMessage": { 25 | "inboundAttachmentNames": [ 26 | 27 | ], 28 | "exceptionPayload": null, 29 | "inboundPropertyNames": [ 30 | 31 | ], 32 | "outboundAttachmentNames": [ 33 | 34 | ], 35 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 36 | "outboundPropertyNames": [ 37 | 38 | ], 39 | "attributes": { 40 | "headers": { 41 | "allow": "GET, HEAD", 42 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 43 | "content-type": "text/html; charset=UTF-8", 44 | "server": "gws", 45 | "content-length": "1589", 46 | "x-xss-protection": "0", 47 | "x-frame-options": "SAMEORIGIN", 48 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 49 | }, 50 | "reasonPhrase": "Method Not Allowed", 51 | "statusCode": 405 52 | } 53 | }, 54 | "cause": { 55 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 56 | "errorMessage": { 57 | "inboundAttachmentNames": [ 58 | 59 | ], 60 | "exceptionPayload": null, 61 | "inboundPropertyNames": [ 62 | 63 | ], 64 | "outboundAttachmentNames": [ 65 | 66 | ], 67 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 68 | "outboundPropertyNames": [ 69 | 70 | ], 71 | "attributes": { 72 | "headers": { 73 | "allow": "GET, HEAD", 74 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 75 | "content-type": "text/html; charset=UTF-8", 76 | "server": "gws", 77 | "content-length": "1589", 78 | "x-xss-protection": "0", 79 | "x-frame-options": "SAMEORIGIN", 80 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 81 | }, 82 | "reasonPhrase": "Method Not Allowed", 83 | "statusCode": 405 84 | } 85 | }, 86 | "cause": null, 87 | "type": "METHOD_NOT_ALLOWED", 88 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 89 | "suppressed": [ 90 | 91 | ], 92 | "stackTrace": [ 93 | 94 | ] 95 | }, 96 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 97 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 98 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 99 | } 100 | ], 101 | "errorType": { 102 | "identifier": "COMPOSITE_ROUTING", 103 | "parentErrorType": { 104 | "identifier": "ROUTING", 105 | "parentErrorType": { 106 | "identifier": "ANY", 107 | "parentErrorType": null, 108 | "namespace": "MULE" 109 | }, 110 | "namespace": "MULE" 111 | }, 112 | "namespace": "MULE" 113 | }, 114 | "errorMessage": { 115 | "inboundAttachmentNames": [ 116 | 117 | ], 118 | "exceptionPayload": null, 119 | "inboundPropertyNames": [ 120 | 121 | ], 122 | "outboundAttachmentNames": [ 123 | 124 | ], 125 | "payload": { 126 | "failures": { 127 | "0": { 128 | "dslSource": "\r\n\r\n", 129 | "suppressedErrors": [ 130 | 131 | ], 132 | "childErrors": [ 133 | 134 | ], 135 | "errorType": { 136 | "identifier": "METHOD_NOT_ALLOWED", 137 | "parentErrorType": { 138 | "identifier": "ANY", 139 | "parentErrorType": null, 140 | "namespace": "MULE" 141 | }, 142 | "namespace": "HTTP" 143 | }, 144 | "errorMessage": { 145 | "inboundAttachmentNames": [ 146 | 147 | ], 148 | "exceptionPayload": null, 149 | "inboundPropertyNames": [ 150 | 151 | ], 152 | "outboundAttachmentNames": [ 153 | 154 | ], 155 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 156 | "outboundPropertyNames": [ 157 | 158 | ], 159 | "attributes": { 160 | "headers": { 161 | "allow": "GET, HEAD", 162 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 163 | "content-type": "text/html; charset=UTF-8", 164 | "server": "gws", 165 | "content-length": "1589", 166 | "x-xss-protection": "0", 167 | "x-frame-options": "SAMEORIGIN", 168 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 169 | }, 170 | "reasonPhrase": "Method Not Allowed", 171 | "statusCode": 405 172 | } 173 | }, 174 | "cause": { 175 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 176 | "errorMessage": { 177 | "inboundAttachmentNames": [ 178 | 179 | ], 180 | "exceptionPayload": null, 181 | "inboundPropertyNames": [ 182 | 183 | ], 184 | "outboundAttachmentNames": [ 185 | 186 | ], 187 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 188 | "outboundPropertyNames": [ 189 | 190 | ], 191 | "attributes": { 192 | "headers": { 193 | "allow": "GET, HEAD", 194 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 195 | "content-type": "text/html; charset=UTF-8", 196 | "server": "gws", 197 | "content-length": "1589", 198 | "x-xss-protection": "0", 199 | "x-frame-options": "SAMEORIGIN", 200 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 201 | }, 202 | "reasonPhrase": "Method Not Allowed", 203 | "statusCode": 405 204 | } 205 | }, 206 | "cause": null, 207 | "type": "METHOD_NOT_ALLOWED", 208 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 209 | "suppressed": [ 210 | 211 | ], 212 | "stackTrace": [ 213 | 214 | ] 215 | }, 216 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 217 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 218 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 219 | } 220 | }, 221 | "failuresWithExceptionInfo": { 222 | 223 | }, 224 | "results": { 225 | 226 | } 227 | }, 228 | "outboundPropertyNames": [ 229 | 230 | ], 231 | "attributes": null 232 | }, 233 | "cause": { 234 | "verboseMessage": "\r\n********************************************************************************\r\nMessage : Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).\r\nElement : get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each)\r\nElement DSL : \r\n\r\n\r\nError type : MULE:COMPOSITE_ROUTING\r\nFlowStack : at get:\\parallelforeach:application\\json:api-config(get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each))\r\nat api-main(api-main/processors/0 @ test-error-handler:api.xml:22)\r\n--------------------------------------------------------------------------------\r\nRoot Exception stack trace:\r\norg.mule.runtime.core.privileged.routing.CompositeRoutingException: Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).\r\n\r\n********************************************************************************\r\n", 235 | "localizedMessage": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 236 | "summaryMessage": "\r\n********************************************************************************\r\nMessage : Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).\r\nElement : get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each)\r\nElement DSL : \r\n\r\n\r\nError type : MULE:COMPOSITE_ROUTING\r\nFlowStack : at get:\\parallelforeach:application\\json:api-config(get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each))\r\nat api-main(api-main/processors/0 @ test-error-handler:api.xml:22)\r\n\r\n (set debug level logging or '-Dmule.verbose.exceptions=true' for everything)\r\n********************************************************************************\r\n", 237 | "errorMessage": { 238 | "inboundAttachmentNames": [ 239 | 240 | ], 241 | "exceptionPayload": null, 242 | "inboundPropertyNames": [ 243 | 244 | ], 245 | "outboundAttachmentNames": [ 246 | 247 | ], 248 | "payload": { 249 | "failures": { 250 | "0": { 251 | "dslSource": "\r\n\r\n", 252 | "suppressedErrors": [ 253 | 254 | ], 255 | "childErrors": [ 256 | 257 | ], 258 | "errorType": { 259 | "identifier": "METHOD_NOT_ALLOWED", 260 | "parentErrorType": { 261 | "identifier": "ANY", 262 | "parentErrorType": null, 263 | "namespace": "MULE" 264 | }, 265 | "namespace": "HTTP" 266 | }, 267 | "errorMessage": { 268 | "inboundAttachmentNames": [ 269 | 270 | ], 271 | "exceptionPayload": null, 272 | "inboundPropertyNames": [ 273 | 274 | ], 275 | "outboundAttachmentNames": [ 276 | 277 | ], 278 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 279 | "outboundPropertyNames": [ 280 | 281 | ], 282 | "attributes": { 283 | "headers": { 284 | "allow": "GET, HEAD", 285 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 286 | "content-type": "text/html; charset=UTF-8", 287 | "server": "gws", 288 | "content-length": "1589", 289 | "x-xss-protection": "0", 290 | "x-frame-options": "SAMEORIGIN", 291 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 292 | }, 293 | "reasonPhrase": "Method Not Allowed", 294 | "statusCode": 405 295 | } 296 | }, 297 | "cause": { 298 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 299 | "errorMessage": { 300 | "inboundAttachmentNames": [ 301 | 302 | ], 303 | "exceptionPayload": null, 304 | "inboundPropertyNames": [ 305 | 306 | ], 307 | "outboundAttachmentNames": [ 308 | 309 | ], 310 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 311 | "outboundPropertyNames": [ 312 | 313 | ], 314 | "attributes": { 315 | "headers": { 316 | "allow": "GET, HEAD", 317 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 318 | "content-type": "text/html; charset=UTF-8", 319 | "server": "gws", 320 | "content-length": "1589", 321 | "x-xss-protection": "0", 322 | "x-frame-options": "SAMEORIGIN", 323 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 324 | }, 325 | "reasonPhrase": "Method Not Allowed", 326 | "statusCode": 405 327 | } 328 | }, 329 | "cause": null, 330 | "type": "METHOD_NOT_ALLOWED", 331 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 332 | "suppressed": [ 333 | 334 | ], 335 | "stackTrace": [ 336 | 337 | ] 338 | }, 339 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 340 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 341 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 342 | } 343 | }, 344 | "failuresWithExceptionInfo": { 345 | 346 | }, 347 | "results": { 348 | 349 | } 350 | }, 351 | "outboundPropertyNames": [ 352 | 353 | ], 354 | "attributes": null 355 | }, 356 | "cause": null, 357 | "message": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 358 | "i18nMessage": { 359 | "code": -1, 360 | "nextMessage": null, 361 | "message": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 362 | "args": [ 363 | 364 | ] 365 | }, 366 | "detailedMessage": "Exception(s) were found for route(s): \r\n\r\nRoute 0: Caught exception in Exception Strategy: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 367 | "additionalInfo": { 368 | 369 | }, 370 | "messageCode": -1, 371 | "suppressed": [ 372 | 373 | ], 374 | "stackTrace": [ 375 | 376 | ], 377 | "errors": [ 378 | { 379 | "dslSource": "\r\n\r\n", 380 | "suppressedErrors": [ 381 | 382 | ], 383 | "childErrors": [ 384 | 385 | ], 386 | "errorType": { 387 | "identifier": "METHOD_NOT_ALLOWED", 388 | "parentErrorType": { 389 | "identifier": "ANY", 390 | "parentErrorType": null, 391 | "namespace": "MULE" 392 | }, 393 | "namespace": "HTTP" 394 | }, 395 | "errorMessage": { 396 | "inboundAttachmentNames": [ 397 | 398 | ], 399 | "exceptionPayload": null, 400 | "inboundPropertyNames": [ 401 | 402 | ], 403 | "outboundAttachmentNames": [ 404 | 405 | ], 406 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 407 | "outboundPropertyNames": [ 408 | 409 | ], 410 | "attributes": { 411 | "headers": { 412 | "allow": "GET, HEAD", 413 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 414 | "content-type": "text/html; charset=UTF-8", 415 | "server": "gws", 416 | "content-length": "1589", 417 | "x-xss-protection": "0", 418 | "x-frame-options": "SAMEORIGIN", 419 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 420 | }, 421 | "reasonPhrase": "Method Not Allowed", 422 | "statusCode": 405 423 | } 424 | }, 425 | "cause": { 426 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 427 | "errorMessage": { 428 | "inboundAttachmentNames": [ 429 | 430 | ], 431 | "exceptionPayload": null, 432 | "inboundPropertyNames": [ 433 | 434 | ], 435 | "outboundAttachmentNames": [ 436 | 437 | ], 438 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 439 | "outboundPropertyNames": [ 440 | 441 | ], 442 | "attributes": { 443 | "headers": { 444 | "allow": "GET, HEAD", 445 | "date": "Fri, 19 Jan 2024 20:33:24 GMT", 446 | "content-type": "text/html; charset=UTF-8", 447 | "server": "gws", 448 | "content-length": "1589", 449 | "x-xss-protection": "0", 450 | "x-frame-options": "SAMEORIGIN", 451 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 452 | }, 453 | "reasonPhrase": "Method Not Allowed", 454 | "statusCode": 405 455 | } 456 | }, 457 | "cause": null, 458 | "type": "METHOD_NOT_ALLOWED", 459 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 460 | "suppressed": [ 461 | 462 | ], 463 | "stackTrace": [ 464 | 465 | ] 466 | }, 467 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 468 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 469 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 470 | } 471 | ], 472 | "exceptionInfo": { 473 | "dslSource": "\r\n\r\n", 474 | "alreadyLogged": true, 475 | "errorType": { 476 | "identifier": "COMPOSITE_ROUTING", 477 | "parentErrorType": { 478 | "identifier": "ROUTING", 479 | "parentErrorType": { 480 | "identifier": "ANY", 481 | "parentErrorType": null, 482 | "namespace": "MULE" 483 | }, 484 | "namespace": "MULE" 485 | }, 486 | "namespace": "MULE" 487 | }, 488 | "flowStack": { 489 | "elements": [ 490 | { 491 | "creationTime": 1705696404600, 492 | "flowName": "get:\\parallelforeach:application\\json:api-config", 493 | "elapsedTimeLong": 14600, 494 | "creationTimeLong": 1705696404600, 495 | "chainIdentifier": { 496 | "name": "flow", 497 | "namespace": "mule", 498 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 499 | }, 500 | "processorPath": "get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each)", 501 | "elapsedTime": 14600 502 | }, 503 | { 504 | "creationTime": 1705696404599, 505 | "flowName": "api-main", 506 | "elapsedTimeLong": 14601, 507 | "creationTimeLong": 1705696404599, 508 | "chainIdentifier": { 509 | "name": "flow", 510 | "namespace": "mule", 511 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 512 | }, 513 | "processorPath": "api-main/processors/0 @ test-error-handler:api.xml:22", 514 | "elapsedTime": 14601 515 | } 516 | ] 517 | }, 518 | "location": "get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each)", 519 | "suppressedCauses": [ 520 | 521 | ] 522 | }, 523 | "info": { 524 | "Error type": { 525 | "identifier": "COMPOSITE_ROUTING", 526 | "parentErrorType": { 527 | "identifier": "ROUTING", 528 | "parentErrorType": { 529 | "identifier": "ANY", 530 | "parentErrorType": null, 531 | "namespace": "MULE" 532 | }, 533 | "namespace": "MULE" 534 | }, 535 | "namespace": "MULE" 536 | }, 537 | "Element": "get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each)", 538 | "Element DSL": "\r\n\r\n", 539 | "FlowStack": { 540 | "elements": [ 541 | { 542 | "creationTime": 1705696404600, 543 | "flowName": "get:\\parallelforeach:application\\json:api-config", 544 | "elapsedTimeLong": 14600, 545 | "creationTimeLong": 1705696404600, 546 | "chainIdentifier": { 547 | "name": "flow", 548 | "namespace": "mule", 549 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 550 | }, 551 | "processorPath": "get:\\parallelforeach:application\\json:api-config/processors/1 @ test-error-handler:api.xml:42 (Parallel For Each)", 552 | "elapsedTime": 14600 553 | }, 554 | { 555 | "creationTime": 1705696404599, 556 | "flowName": "api-main", 557 | "elapsedTimeLong": 14601, 558 | "creationTimeLong": 1705696404599, 559 | "chainIdentifier": { 560 | "name": "flow", 561 | "namespace": "mule", 562 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 563 | }, 564 | "processorPath": "api-main/processors/0 @ test-error-handler:api.xml:22", 565 | "elapsedTime": 14601 566 | } 567 | ] 568 | } 569 | } 570 | }, 571 | "description": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 572 | "detailedDescription": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 573 | "failingComponent": "api-main/processors/0 @ test-error-handler:api.xml:22" 574 | } -------------------------------------------------------------------------------- /src/test/resources/examples/ScatterGather.json: -------------------------------------------------------------------------------- 1 | { 2 | "dslSource": "", 3 | "suppressedErrors": [ 4 | 5 | ], 6 | "childErrors": [ 7 | { 8 | "dslSource": "\r\n\r\n", 9 | "suppressedErrors": [ 10 | 11 | ], 12 | "childErrors": [ 13 | 14 | ], 15 | "errorType": { 16 | "identifier": "METHOD_NOT_ALLOWED", 17 | "parentErrorType": { 18 | "identifier": "ANY", 19 | "parentErrorType": null, 20 | "namespace": "MULE" 21 | }, 22 | "namespace": "HTTP" 23 | }, 24 | "errorMessage": { 25 | "inboundAttachmentNames": [ 26 | 27 | ], 28 | "exceptionPayload": null, 29 | "inboundPropertyNames": [ 30 | 31 | ], 32 | "outboundAttachmentNames": [ 33 | 34 | ], 35 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 36 | "outboundPropertyNames": [ 37 | 38 | ], 39 | "attributes": { 40 | "headers": { 41 | "allow": "GET, HEAD", 42 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 43 | "content-type": "text/html; charset=UTF-8", 44 | "server": "gws", 45 | "content-length": "1589", 46 | "x-xss-protection": "0", 47 | "x-frame-options": "SAMEORIGIN", 48 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 49 | }, 50 | "reasonPhrase": "Method Not Allowed", 51 | "statusCode": 405 52 | } 53 | }, 54 | "cause": { 55 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 56 | "errorMessage": { 57 | "inboundAttachmentNames": [ 58 | 59 | ], 60 | "exceptionPayload": null, 61 | "inboundPropertyNames": [ 62 | 63 | ], 64 | "outboundAttachmentNames": [ 65 | 66 | ], 67 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 68 | "outboundPropertyNames": [ 69 | 70 | ], 71 | "attributes": { 72 | "headers": { 73 | "allow": "GET, HEAD", 74 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 75 | "content-type": "text/html; charset=UTF-8", 76 | "server": "gws", 77 | "content-length": "1589", 78 | "x-xss-protection": "0", 79 | "x-frame-options": "SAMEORIGIN", 80 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 81 | }, 82 | "reasonPhrase": "Method Not Allowed", 83 | "statusCode": 405 84 | } 85 | }, 86 | "cause": null, 87 | "type": "METHOD_NOT_ALLOWED", 88 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 89 | "suppressed": [ 90 | 91 | ], 92 | "stackTrace": [ 93 | 94 | ] 95 | }, 96 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 97 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 98 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 99 | } 100 | ], 101 | "errorType": { 102 | "identifier": "COMPOSITE_ROUTING", 103 | "parentErrorType": { 104 | "identifier": "ROUTING", 105 | "parentErrorType": { 106 | "identifier": "ANY", 107 | "parentErrorType": null, 108 | "namespace": "MULE" 109 | }, 110 | "namespace": "MULE" 111 | }, 112 | "namespace": "MULE" 113 | }, 114 | "errorMessage": { 115 | "inboundAttachmentNames": [ 116 | 117 | ], 118 | "exceptionPayload": null, 119 | "inboundPropertyNames": [ 120 | 121 | ], 122 | "outboundAttachmentNames": [ 123 | 124 | ], 125 | "payload": { 126 | "failures": { 127 | "0": { 128 | "dslSource": "\r\n\r\n", 129 | "suppressedErrors": [ 130 | 131 | ], 132 | "childErrors": [ 133 | 134 | ], 135 | "errorType": { 136 | "identifier": "METHOD_NOT_ALLOWED", 137 | "parentErrorType": { 138 | "identifier": "ANY", 139 | "parentErrorType": null, 140 | "namespace": "MULE" 141 | }, 142 | "namespace": "HTTP" 143 | }, 144 | "errorMessage": { 145 | "inboundAttachmentNames": [ 146 | 147 | ], 148 | "exceptionPayload": null, 149 | "inboundPropertyNames": [ 150 | 151 | ], 152 | "outboundAttachmentNames": [ 153 | 154 | ], 155 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 156 | "outboundPropertyNames": [ 157 | 158 | ], 159 | "attributes": { 160 | "headers": { 161 | "allow": "GET, HEAD", 162 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 163 | "content-type": "text/html; charset=UTF-8", 164 | "server": "gws", 165 | "content-length": "1589", 166 | "x-xss-protection": "0", 167 | "x-frame-options": "SAMEORIGIN", 168 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 169 | }, 170 | "reasonPhrase": "Method Not Allowed", 171 | "statusCode": 405 172 | } 173 | }, 174 | "cause": { 175 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 176 | "errorMessage": { 177 | "inboundAttachmentNames": [ 178 | 179 | ], 180 | "exceptionPayload": null, 181 | "inboundPropertyNames": [ 182 | 183 | ], 184 | "outboundAttachmentNames": [ 185 | 186 | ], 187 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 188 | "outboundPropertyNames": [ 189 | 190 | ], 191 | "attributes": { 192 | "headers": { 193 | "allow": "GET, HEAD", 194 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 195 | "content-type": "text/html; charset=UTF-8", 196 | "server": "gws", 197 | "content-length": "1589", 198 | "x-xss-protection": "0", 199 | "x-frame-options": "SAMEORIGIN", 200 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 201 | }, 202 | "reasonPhrase": "Method Not Allowed", 203 | "statusCode": 405 204 | } 205 | }, 206 | "cause": null, 207 | "type": "METHOD_NOT_ALLOWED", 208 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 209 | "suppressed": [ 210 | 211 | ], 212 | "stackTrace": [ 213 | 214 | ] 215 | }, 216 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 217 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 218 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 219 | } 220 | }, 221 | "failuresWithExceptionInfo": { 222 | 223 | }, 224 | "results": { 225 | "1": { 226 | "inboundAttachmentNames": [ 227 | 228 | ], 229 | "exceptionPayload": null, 230 | "inboundPropertyNames": [ 231 | 232 | ], 233 | "outboundAttachmentNames": [ 234 | 235 | ], 236 | "payload": { 237 | 238 | }, 239 | "outboundPropertyNames": [ 240 | 241 | ], 242 | "attributes": { 243 | "headers": { 244 | "content-type": "application/json", 245 | "user-agent": "PostmanRuntime/7.36.1", 246 | "accept": "*/*", 247 | "cache-control": "no-cache", 248 | "postman-token": "34a0f905-c789-4fcd-af62-b0c1a7e4c662", 249 | "host": "localhost:8081", 250 | "accept-encoding": "gzip, deflate, br", 251 | "connection": "keep-alive", 252 | "content-length": "2" 253 | }, 254 | "clientCertificate": null, 255 | "method": "GET", 256 | "scheme": "http", 257 | "queryParams": { 258 | 259 | }, 260 | "requestUri": "/api/scattergather", 261 | "queryString": "", 262 | "version": "HTTP/1.1", 263 | "maskedRequestPath": "/scattergather", 264 | "listenerPath": "/api/*", 265 | "relativePath": "/api/scattergather", 266 | "localAddress": "/127.0.0.1:8081", 267 | "uriParams": { 268 | 269 | }, 270 | "rawRequestUri": "/api/scattergather", 271 | "rawRequestPath": "/api/scattergather", 272 | "remoteAddress": "/127.0.0.1:54902", 273 | "requestPath": "/api/scattergather" 274 | } 275 | } 276 | } 277 | }, 278 | "outboundPropertyNames": [ 279 | 280 | ], 281 | "attributes": null 282 | }, 283 | "cause": { 284 | "verboseMessage": "\r\n********************************************************************************\r\nMessage : Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).\r\nElement : get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather)\r\nElement DSL : \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nError type : MULE:COMPOSITE_ROUTING\r\nFlowStack : at get:\\scattergather:application\\json:api-config(get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather))\r\nat api-main(api-main/processors/0 @ test-error-handler:api.xml:22)\r\n--------------------------------------------------------------------------------\r\nRoot Exception stack trace:\r\norg.mule.runtime.core.privileged.routing.CompositeRoutingException: Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).\r\n\r\n********************************************************************************\r\n", 285 | "localizedMessage": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 286 | "summaryMessage": "\r\n********************************************************************************\r\nMessage : Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).\r\nElement : get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather)\r\nElement DSL : \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nError type : MULE:COMPOSITE_ROUTING\r\nFlowStack : at get:\\scattergather:application\\json:api-config(get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather))\r\nat api-main(api-main/processors/0 @ test-error-handler:api.xml:22)\r\n\r\n (set debug level logging or '-Dmule.verbose.exceptions=true' for everything)\r\n********************************************************************************\r\n", 287 | "errorMessage": { 288 | "inboundAttachmentNames": [ 289 | 290 | ], 291 | "exceptionPayload": null, 292 | "inboundPropertyNames": [ 293 | 294 | ], 295 | "outboundAttachmentNames": [ 296 | 297 | ], 298 | "payload": { 299 | "failures": { 300 | "0": { 301 | "dslSource": "\r\n\r\n", 302 | "suppressedErrors": [ 303 | 304 | ], 305 | "childErrors": [ 306 | 307 | ], 308 | "errorType": { 309 | "identifier": "METHOD_NOT_ALLOWED", 310 | "parentErrorType": { 311 | "identifier": "ANY", 312 | "parentErrorType": null, 313 | "namespace": "MULE" 314 | }, 315 | "namespace": "HTTP" 316 | }, 317 | "errorMessage": { 318 | "inboundAttachmentNames": [ 319 | 320 | ], 321 | "exceptionPayload": null, 322 | "inboundPropertyNames": [ 323 | 324 | ], 325 | "outboundAttachmentNames": [ 326 | 327 | ], 328 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 329 | "outboundPropertyNames": [ 330 | 331 | ], 332 | "attributes": { 333 | "headers": { 334 | "allow": "GET, HEAD", 335 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 336 | "content-type": "text/html; charset=UTF-8", 337 | "server": "gws", 338 | "content-length": "1589", 339 | "x-xss-protection": "0", 340 | "x-frame-options": "SAMEORIGIN", 341 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 342 | }, 343 | "reasonPhrase": "Method Not Allowed", 344 | "statusCode": 405 345 | } 346 | }, 347 | "cause": { 348 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 349 | "errorMessage": { 350 | "inboundAttachmentNames": [ 351 | 352 | ], 353 | "exceptionPayload": null, 354 | "inboundPropertyNames": [ 355 | 356 | ], 357 | "outboundAttachmentNames": [ 358 | 359 | ], 360 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 361 | "outboundPropertyNames": [ 362 | 363 | ], 364 | "attributes": { 365 | "headers": { 366 | "allow": "GET, HEAD", 367 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 368 | "content-type": "text/html; charset=UTF-8", 369 | "server": "gws", 370 | "content-length": "1589", 371 | "x-xss-protection": "0", 372 | "x-frame-options": "SAMEORIGIN", 373 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 374 | }, 375 | "reasonPhrase": "Method Not Allowed", 376 | "statusCode": 405 377 | } 378 | }, 379 | "cause": null, 380 | "type": "METHOD_NOT_ALLOWED", 381 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 382 | "suppressed": [ 383 | 384 | ], 385 | "stackTrace": [ 386 | 387 | ] 388 | }, 389 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 390 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 391 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 392 | } 393 | }, 394 | "failuresWithExceptionInfo": { 395 | 396 | }, 397 | "results": { 398 | "1": { 399 | "inboundAttachmentNames": [ 400 | 401 | ], 402 | "exceptionPayload": null, 403 | "inboundPropertyNames": [ 404 | 405 | ], 406 | "outboundAttachmentNames": [ 407 | 408 | ], 409 | "payload": { 410 | 411 | }, 412 | "outboundPropertyNames": [ 413 | 414 | ], 415 | "attributes": { 416 | "headers": { 417 | "content-type": "application/json", 418 | "user-agent": "PostmanRuntime/7.36.1", 419 | "accept": "*/*", 420 | "cache-control": "no-cache", 421 | "postman-token": "34a0f905-c789-4fcd-af62-b0c1a7e4c662", 422 | "host": "localhost:8081", 423 | "accept-encoding": "gzip, deflate, br", 424 | "connection": "keep-alive", 425 | "content-length": "2" 426 | }, 427 | "clientCertificate": null, 428 | "method": "GET", 429 | "scheme": "http", 430 | "queryParams": { 431 | 432 | }, 433 | "requestUri": "/api/scattergather", 434 | "queryString": "", 435 | "version": "HTTP/1.1", 436 | "maskedRequestPath": "/scattergather", 437 | "listenerPath": "/api/*", 438 | "relativePath": "/api/scattergather", 439 | "localAddress": "/127.0.0.1:8081", 440 | "uriParams": { 441 | 442 | }, 443 | "rawRequestUri": "/api/scattergather", 444 | "rawRequestPath": "/api/scattergather", 445 | "remoteAddress": "/127.0.0.1:54902", 446 | "requestPath": "/api/scattergather" 447 | } 448 | } 449 | } 450 | }, 451 | "outboundPropertyNames": [ 452 | 453 | ], 454 | "attributes": null 455 | }, 456 | "cause": null, 457 | "message": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 458 | "i18nMessage": { 459 | "code": -1, 460 | "nextMessage": null, 461 | "message": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 462 | "args": [ 463 | 464 | ] 465 | }, 466 | "detailedMessage": "Exception(s) were found for route(s): \r\n\r\nRoute 0: Caught exception in Exception Strategy: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 467 | "additionalInfo": { 468 | 469 | }, 470 | "messageCode": -1, 471 | "suppressed": [ 472 | 473 | ], 474 | "stackTrace": [ 475 | 476 | ], 477 | "errors": [ 478 | { 479 | "dslSource": "\r\n\r\n", 480 | "suppressedErrors": [ 481 | 482 | ], 483 | "childErrors": [ 484 | 485 | ], 486 | "errorType": { 487 | "identifier": "METHOD_NOT_ALLOWED", 488 | "parentErrorType": { 489 | "identifier": "ANY", 490 | "parentErrorType": null, 491 | "namespace": "MULE" 492 | }, 493 | "namespace": "HTTP" 494 | }, 495 | "errorMessage": { 496 | "inboundAttachmentNames": [ 497 | 498 | ], 499 | "exceptionPayload": null, 500 | "inboundPropertyNames": [ 501 | 502 | ], 503 | "outboundAttachmentNames": [ 504 | 505 | ], 506 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 507 | "outboundPropertyNames": [ 508 | 509 | ], 510 | "attributes": { 511 | "headers": { 512 | "allow": "GET, HEAD", 513 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 514 | "content-type": "text/html; charset=UTF-8", 515 | "server": "gws", 516 | "content-length": "1589", 517 | "x-xss-protection": "0", 518 | "x-frame-options": "SAMEORIGIN", 519 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 520 | }, 521 | "reasonPhrase": "Method Not Allowed", 522 | "statusCode": 405 523 | } 524 | }, 525 | "cause": { 526 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 527 | "errorMessage": { 528 | "inboundAttachmentNames": [ 529 | 530 | ], 531 | "exceptionPayload": null, 532 | "inboundPropertyNames": [ 533 | 534 | ], 535 | "outboundAttachmentNames": [ 536 | 537 | ], 538 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 539 | "outboundPropertyNames": [ 540 | 541 | ], 542 | "attributes": { 543 | "headers": { 544 | "allow": "GET, HEAD", 545 | "date": "Fri, 19 Jan 2024 20:50:17 GMT", 546 | "content-type": "text/html; charset=UTF-8", 547 | "server": "gws", 548 | "content-length": "1589", 549 | "x-xss-protection": "0", 550 | "x-frame-options": "SAMEORIGIN", 551 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 552 | }, 553 | "reasonPhrase": "Method Not Allowed", 554 | "statusCode": 405 555 | } 556 | }, 557 | "cause": null, 558 | "type": "METHOD_NOT_ALLOWED", 559 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 560 | "suppressed": [ 561 | 562 | ], 563 | "stackTrace": [ 564 | 565 | ] 566 | }, 567 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 568 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 569 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 570 | } 571 | ], 572 | "exceptionInfo": { 573 | "dslSource": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n", 574 | "alreadyLogged": true, 575 | "errorType": { 576 | "identifier": "COMPOSITE_ROUTING", 577 | "parentErrorType": { 578 | "identifier": "ROUTING", 579 | "parentErrorType": { 580 | "identifier": "ANY", 581 | "parentErrorType": null, 582 | "namespace": "MULE" 583 | }, 584 | "namespace": "MULE" 585 | }, 586 | "namespace": "MULE" 587 | }, 588 | "flowStack": { 589 | "elements": [ 590 | { 591 | "creationTime": 1705697417126, 592 | "flowName": "get:\\scattergather:application\\json:api-config", 593 | "elapsedTimeLong": 19793, 594 | "creationTimeLong": 1705697417126, 595 | "chainIdentifier": { 596 | "name": "flow", 597 | "namespace": "mule", 598 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 599 | }, 600 | "processorPath": "get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather)", 601 | "elapsedTime": 19793 602 | }, 603 | { 604 | "creationTime": 1705697417122, 605 | "flowName": "api-main", 606 | "elapsedTimeLong": 19797, 607 | "creationTimeLong": 1705697417122, 608 | "chainIdentifier": { 609 | "name": "flow", 610 | "namespace": "mule", 611 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 612 | }, 613 | "processorPath": "api-main/processors/0 @ test-error-handler:api.xml:22", 614 | "elapsedTime": 19797 615 | } 616 | ] 617 | }, 618 | "location": "get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather)", 619 | "suppressedCauses": [ 620 | 621 | ] 622 | }, 623 | "info": { 624 | "Error type": { 625 | "identifier": "COMPOSITE_ROUTING", 626 | "parentErrorType": { 627 | "identifier": "ROUTING", 628 | "parentErrorType": { 629 | "identifier": "ANY", 630 | "parentErrorType": null, 631 | "namespace": "MULE" 632 | }, 633 | "namespace": "MULE" 634 | }, 635 | "namespace": "MULE" 636 | }, 637 | "Element": "get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather)", 638 | "Element DSL": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n", 639 | "FlowStack": { 640 | "elements": [ 641 | { 642 | "creationTime": 1705697417126, 643 | "flowName": "get:\\scattergather:application\\json:api-config", 644 | "elapsedTimeLong": 19793, 645 | "creationTimeLong": 1705697417126, 646 | "chainIdentifier": { 647 | "name": "flow", 648 | "namespace": "mule", 649 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 650 | }, 651 | "processorPath": "get:\\scattergather:application\\json:api-config/processors/1 @ test-error-handler:api.xml:52 (Scatter-Gather)", 652 | "elapsedTime": 19793 653 | }, 654 | { 655 | "creationTime": 1705697417122, 656 | "flowName": "api-main", 657 | "elapsedTimeLong": 19797, 658 | "creationTimeLong": 1705697417122, 659 | "chainIdentifier": { 660 | "name": "flow", 661 | "namespace": "mule", 662 | "namespaceUri": "http://www.mulesoft.org/schema/mule/core" 663 | }, 664 | "processorPath": "api-main/processors/0 @ test-error-handler:api.xml:22", 665 | "elapsedTime": 19797 666 | } 667 | ] 668 | } 669 | } 670 | }, 671 | "description": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 672 | "detailedDescription": "Exception(s) were found for route(s): \r\n\tRoute 0: org.mule.extension.http.api.request.validator.ResponseValidatorTypedException: HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 673 | "failingComponent": "api-main/processors/0 @ test-error-handler:api.xml:22" 674 | } -------------------------------------------------------------------------------- /src/test/resources/examples/UntilSuccessfulError.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dslSource": "\r\n\r\n", 4 | "suppressedErrors": [ 5 | 6 | ], 7 | "childErrors": [ 8 | 9 | ], 10 | "errorType": { 11 | "identifier": "METHOD_NOT_ALLOWED", 12 | "parentErrorType": { 13 | "identifier": "ANY", 14 | "parentErrorType": null, 15 | "namespace": "MULE" 16 | }, 17 | "namespace": "HTTP" 18 | }, 19 | "errorMessage": { 20 | "inboundAttachmentNames": [ 21 | 22 | ], 23 | "exceptionPayload": null, 24 | "inboundPropertyNames": [ 25 | 26 | ], 27 | "outboundAttachmentNames": [ 28 | 29 | ], 30 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 31 | "outboundPropertyNames": [ 32 | 33 | ], 34 | "attributes": { 35 | "headers": { 36 | "allow": "GET, HEAD", 37 | "date": "Fri, 19 Jan 2024 20:30:29 GMT", 38 | "content-type": "text/html; charset=UTF-8", 39 | "server": "gws", 40 | "content-length": "1589", 41 | "x-xss-protection": "0", 42 | "x-frame-options": "SAMEORIGIN", 43 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 44 | }, 45 | "reasonPhrase": "Method Not Allowed", 46 | "statusCode": 405 47 | } 48 | }, 49 | "cause": { 50 | "localizedMessage": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 51 | "errorMessage": { 52 | "inboundAttachmentNames": [ 53 | 54 | ], 55 | "exceptionPayload": null, 56 | "inboundPropertyNames": [ 57 | 58 | ], 59 | "outboundAttachmentNames": [ 60 | 61 | ], 62 | "payload": "\n\n \n \n Error 405 (Method Not Allowed)!!1\n \n \n

405. That’s an error.\n

The request method POST is inappropriate for the URL /. That’s all we know.\n", 63 | "outboundPropertyNames": [ 64 | 65 | ], 66 | "attributes": { 67 | "headers": { 68 | "allow": "GET, HEAD", 69 | "date": "Fri, 19 Jan 2024 20:30:29 GMT", 70 | "content-type": "text/html; charset=UTF-8", 71 | "server": "gws", 72 | "content-length": "1589", 73 | "x-xss-protection": "0", 74 | "x-frame-options": "SAMEORIGIN", 75 | "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" 76 | }, 77 | "reasonPhrase": "Method Not Allowed", 78 | "statusCode": 405 79 | } 80 | }, 81 | "cause": null, 82 | "type": "METHOD_NOT_ALLOWED", 83 | "message": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 84 | "suppressed": [ 85 | 86 | ], 87 | "stackTrace": [ 88 | 89 | ] 90 | }, 91 | "description": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 92 | "detailedDescription": "HTTP POST on resource 'https://www.google.com' failed: method not allowed (405).", 93 | "failingComponent": "api_requestError/processors/0 @ test-error-handler:api.xml:26 (POST Google)" 94 | } 95 | ] -------------------------------------------------------------------------------- /src/test/resources/examples/ValidationModuleError.json: -------------------------------------------------------------------------------- 1 | { 2 | "dslSource": "", 3 | "suppressedErrors": [ 4 | 5 | ], 6 | "childErrors": [ 7 | 8 | ], 9 | "errorType": { 10 | "identifier": "INVALID_BOOLEAN", 11 | "parentErrorType": { 12 | "identifier": "VALIDATION", 13 | "parentErrorType": { 14 | "identifier": "VALIDATION", 15 | "parentErrorType": { 16 | "identifier": "ANY", 17 | "parentErrorType": null, 18 | "namespace": "MULE" 19 | }, 20 | "namespace": "MULE" 21 | }, 22 | "namespace": "VALIDATION" 23 | }, 24 | "namespace": "VALIDATION" 25 | }, 26 | "errorMessage": { 27 | "inboundAttachmentNames": [ 28 | 29 | ], 30 | "exceptionPayload": null, 31 | "inboundPropertyNames": [ 32 | 33 | ], 34 | "outboundAttachmentNames": [ 35 | 36 | ], 37 | "payload": { 38 | "errorType": "INVALID_BOOLEAN", 39 | "message": "Muh Error!", 40 | "error": true 41 | }, 42 | "outboundPropertyNames": [ 43 | 44 | ], 45 | "attributes": null 46 | }, 47 | "cause": { 48 | "validationResult": { 49 | "errorType": "INVALID_BOOLEAN", 50 | "message": "Muh Error!", 51 | "error": true 52 | }, 53 | "localizedMessage": "Muh Error!", 54 | "errorMessage": { 55 | "inboundAttachmentNames": [ 56 | 57 | ], 58 | "exceptionPayload": null, 59 | "inboundPropertyNames": [ 60 | 61 | ], 62 | "outboundAttachmentNames": [ 63 | 64 | ], 65 | "payload": { 66 | "errorType": "INVALID_BOOLEAN", 67 | "message": "Muh Error!", 68 | "error": true 69 | }, 70 | "outboundPropertyNames": [ 71 | 72 | ], 73 | "attributes": null 74 | }, 75 | "cause": null, 76 | "type": "INVALID_BOOLEAN", 77 | "message": "Muh Error!", 78 | "suppressed": [ 79 | 80 | ], 81 | "stackTrace": [ 82 | 83 | ] 84 | }, 85 | "description": "Muh Error!", 86 | "detailedDescription": "Muh Error!", 87 | "failingComponent": "get:\\validation:application\\json:api-config/processors/1 @ test-error-handler:api.xml:69 (Is true)" 88 | } -------------------------------------------------------------------------------- /src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------