feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint
This commit is contained in:
@@ -1,67 +1,103 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Error handling utilities for Reynolds & Reynolds (RR) API calls.
|
||||
// This mirrors Fortellis/CDK error helpers so the call pipeline stays uniform.
|
||||
//
|
||||
// TODO:RR — Replace the heuristics in assertRrOk with the *actual* envelope and
|
||||
// status semantics from the Rome RR specs. Examples in the PDFs may show:
|
||||
// - <Status code="0" severity="INFO">Success</Status>
|
||||
// - <Status code="123" severity="ERROR">Some message</Status>
|
||||
// - or a SuccessFlag/ReturnCode element in the JSON/XML response.
|
||||
// -----------------------------------------------------------------------------
|
||||
/**
|
||||
* @file rr-error.js
|
||||
* @description Centralized error class and assertion logic for Reynolds & Reynolds API calls.
|
||||
* Provides consistent handling across all RR modules (customer, repair order, lookups, etc.)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Custom Error type for RR API responses
|
||||
*/
|
||||
class RrApiError extends Error {
|
||||
/**
|
||||
* @param {string} message - Human-readable message
|
||||
* @param {object} opts
|
||||
* @param {string} [opts.reqId] - Internal request identifier
|
||||
* @param {string} [opts.url] - Target URL of the API call
|
||||
* @param {string} [opts.apiName] - Which API was invoked (for context)
|
||||
* @param {object} [opts.errorData] - Raw error payload from RR
|
||||
* @param {number} [opts.status] - HTTP status code
|
||||
* @param {string} [opts.statusText] - HTTP status text
|
||||
*/
|
||||
constructor(message, { reqId, url, apiName, errorData, status, statusText } = {}) {
|
||||
super(message);
|
||||
this.name = "RrApiError";
|
||||
this.reqId = reqId;
|
||||
this.url = url;
|
||||
this.apiName = apiName;
|
||||
this.errorData = errorData;
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.reqId = reqId || null;
|
||||
this.url = url || null;
|
||||
this.apiName = apiName || null;
|
||||
this.errorData = errorData || null;
|
||||
this.status = status || null;
|
||||
this.statusText = statusText || null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an RR API response is considered "OK".
|
||||
* Throws RrApiError otherwise.
|
||||
* Assert that a Reynolds & Reynolds response is successful.
|
||||
*
|
||||
* @param {*} data - Parsed response object from MakeRRCall
|
||||
* @param {object} opts
|
||||
* @param {string} opts.apiName - Which API we're checking (for error messages)
|
||||
* @param {boolean} [opts.allowEmpty=false] - If true, allow null/empty results
|
||||
* @returns {*} - The same data if valid
|
||||
* Expected success structure (based on Rome RR specs):
|
||||
* {
|
||||
* "SuccessFlag": true,
|
||||
* "ErrorCode": "0",
|
||||
* "ErrorMessage": "",
|
||||
* "Data": { ... }
|
||||
* }
|
||||
*
|
||||
* Or if SOAP/XML-based:
|
||||
* {
|
||||
* "Envelope": {
|
||||
* "Body": {
|
||||
* "Response": {
|
||||
* "SuccessFlag": true,
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* This helper unwraps and normalizes the response to detect any error cases.
|
||||
*/
|
||||
function assertRrOk(data, { apiName, allowEmpty = false } = {}) {
|
||||
// TODO:RR — Update logic to exactly match RR's success envelope.
|
||||
// Possible patterns to confirm from PDFs:
|
||||
// - data.Status?.code === "0"
|
||||
// - data.Return?.successFlag === true
|
||||
// - data.Errors is missing or empty
|
||||
//
|
||||
// For now, we use a simple heuristic fallback.
|
||||
|
||||
const hasErrors =
|
||||
data == null ||
|
||||
data.error ||
|
||||
(Array.isArray(data.errors) && data.errors.length > 0) ||
|
||||
(data.Status && data.Status.severity === "ERROR");
|
||||
|
||||
if (!allowEmpty && hasErrors) {
|
||||
throw new RrApiError(`${apiName} returned an error`, { errorData: data, apiName });
|
||||
function assertRrOk(data, { apiName = "RR API Call", allowEmpty = false } = {}) {
|
||||
if (!data && !allowEmpty) {
|
||||
throw new RrApiError(`${apiName} returned no data`, { apiName });
|
||||
}
|
||||
|
||||
return data;
|
||||
// Normalize envelope
|
||||
const response =
|
||||
data?.Envelope?.Body?.Response ||
|
||||
data?.Envelope?.Body?.[Object.keys(data.Envelope?.Body || {})[0]] ||
|
||||
data?.Response ||
|
||||
data;
|
||||
|
||||
// Handle array of errors or error objects
|
||||
const errorBlock = response?.Errors || response?.Error || response?.Fault || null;
|
||||
|
||||
// Basic success conditions per RR documentation
|
||||
const success =
|
||||
response?.SuccessFlag === true ||
|
||||
response?.ErrorCode === "0" ||
|
||||
response?.ResultCode === "0" ||
|
||||
(Array.isArray(errorBlock) && errorBlock.length === 0);
|
||||
|
||||
// If success, return normalized response
|
||||
if (success || allowEmpty) {
|
||||
return response?.Data || response;
|
||||
}
|
||||
|
||||
// Construct contextual error info
|
||||
const errorMessage = response?.ErrorMessage || response?.FaultString || response?.Message || "Unknown RR API error";
|
||||
|
||||
throw new RrApiError(`${apiName} failed: ${errorMessage}`, {
|
||||
apiName,
|
||||
errorData: response,
|
||||
status: response?.ErrorCode || response?.ResultCode
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { RrApiError, assertRrOk };
|
||||
/**
|
||||
* Safely unwrap nested RR API responses for consistency across handlers.
|
||||
*/
|
||||
function extractRrResponseData(data) {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
data?.Envelope?.Body?.Response?.Data ||
|
||||
data?.Envelope?.Body?.[Object.keys(data.Envelope?.Body || {})[0]]?.Data ||
|
||||
data?.Data ||
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RrApiError,
|
||||
assertRrOk,
|
||||
extractRrResponseData
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user