feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint

This commit is contained in:
Dave
2025-10-08 13:57:34 -04:00
parent 2ffc4b81f4
commit de02b34a63
28 changed files with 2550 additions and 2443 deletions

View File

@@ -1,103 +1,48 @@
/**
* @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.)
* @description Custom error types for the Reynolds & Reynolds (Rome) integration.
*/
/**
* Custom Error type for RR API responses
* Base RR API Error class — always structured with a message and a code.
*/
class RrApiError extends Error {
constructor(message, { reqId, url, apiName, errorData, status, statusText } = {}) {
/**
* @param {string} message - Human-readable message
* @param {string} [code="RR_ERROR"] - Short machine-readable error code
* @param {Object} [details] - Optional structured metadata
*/
constructor(message, code = "RR_ERROR", details = {}) {
super(message);
this.name = "RrApiError";
this.reqId = reqId || null;
this.url = url || null;
this.apiName = apiName || null;
this.errorData = errorData || null;
this.status = status || null;
this.statusText = statusText || null;
this.code = code;
this.details = details;
}
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
details: this.details
};
}
}
/**
* Assert that a Reynolds & Reynolds response is successful.
*
* 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.
* Helper to normalize thrown errors into a consistent RrApiError instance.
*/
function assertRrOk(data, { apiName = "RR API Call", allowEmpty = false } = {}) {
if (!data && !allowEmpty) {
throw new RrApiError(`${apiName} returned no data`, { apiName });
}
// 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
});
}
/**
* 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
);
function toRrError(err, defaultCode = "RR_ERROR") {
if (!err) return new RrApiError("Unknown RR error", defaultCode);
if (err instanceof RrApiError) return err;
if (typeof err === "string") return new RrApiError(err, defaultCode);
const msg = err.message || "Unspecified RR error";
const code = err.code || defaultCode;
const details = err.details || {};
return new RrApiError(msg, code, details);
}
module.exports = {
RrApiError,
assertRrOk,
extractRrResponseData
toRrError
};