Skip to content

Rich error details for plugin methods

Thunder maps the Core::hresult returned by a method to a JSON-RPC error code and message. Use the standard Core::ERROR_* codes in most cases. They are simpler for both plugins and clients, and an error-details parameter is not required just to report a failure.

Rich errors are for exceptional cases where the standard error alone cannot convey information a client needs to act on: for example, a device-specific diagnostic, an actionable explanation, or a deliberately chosen JSON-RPC error code. They can also preserve backward compatibility when existing clients depend on custom error data; keep the expected data shape and values when migrating those methods. Rich errors are supported for methods only, not properties. The returned Core::hresult remains the framework result; rich details supplement it rather than replacing it.

Core::ErrorDetails is modeled after the JSON-RPC error fields, but it is a native interface type, not a JSON-RPC-only feature. A COM-RPC client can also receive the optional error-details output parameter and use its code, message, or typed data to understand a failure. The implementation supplies the same meaningful details regardless of whether a client uses COM-RPC or JSON-RPC; only the transport's presentation differs.

Dynamic error messages (the PluginHost::JSONRPCErrorAssessor approach) are deprecated in favor of rich error details for new interfaces. Existing integrations may continue to use them, but new methods that need more than a standard error should declare an ErrorDetails output parameter instead. Prefer the standard Core::ERROR_* codes when they are sufficient.

Declare an interface method

Include the core error definition for interface generators with @insert, declare an optional trailing @out parameter, and choose a type for the data field if structured diagnostics are needed:

#include "Module.h"

// @insert <core/Errors.h>

namespace Thunder {
namespace Example {

    // @json 1.0.0
    struct EXTERNAL ISimpleRichError : virtual public Core::IUnknown {

        enum { ID = ID_SIMPLERICHERROR };

        struct Diagnostic {
            uint8_t water_level;
            Core::OptionalType<string> info;
        };

        using Error = Core::ErrorDetails<Diagnostic>;

        // @brief Makes a coffee
        virtual Core::hresult MakeCoffee(uint16_t& duration /* @out */,
            Core::OptionalType<Error>& errorDetails /* @out */) = 0;

        // @brief Restarts the machine; this error has no data field
        virtual Core::hresult Restart(
            Core::OptionalType<Core::ErrorDetails<void>>& errorDetails /* @out */) = 0;
    };

} // namespace Example
} // namespace Thunder

Core::ErrorDetails<DATA> has independently optional code, message, and data members. Core::ErrorDetails<void> has only code and message. An alias such as Error can keep interface signatures short. The enclosing Core::OptionalType lets a method leave all details unset.

Populate details in the implementation

Return a non-Core::ERROR_NONE framework result on failure. Set only those fields for which the standard mapping is insufficient:

Core::hresult SimpleRichError::MakeCoffee(uint16_t& duration,
    Core::OptionalType<Example::ISimpleRichError::Error>& errorDetails)
{
    Core::hresult result = Core::ERROR_NONE;

    if (_waterLevel == 0) {
        Example::ISimpleRichError::Diagnostic diagnostic;
        diagnostic.water_level = 0;
        diagnostic.info = _T("Refill the water tank");

        errorDetails = Example::ISimpleRichError::Error();
        errorDetails.Value().data = diagnostic;
        result = Core::ERROR_UNAVAILABLE;
    }
    else {
        duration = 25;
    }

    return (result);
}

Here only data is supplied. Thunder still provides the JSON-RPC code and message corresponding to Core::ERROR_UNAVAILABLE. When an override is genuinely necessary, assign errorDetails.Value().code or .message as well. To supply an error without data, use ErrorDetails<void>; for example, a Restart failure can set only .message and return Core::ERROR_ILLEGAL_STATE, retaining Thunder's mapped code.

For example, if the MakeCoffee method above fails because the water level is zero, a request might receive a JSON-RPC response like this:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -31002,
    "message": "The service is not active.",
    "data": {
      "waterLevel": 0,
      "info": "Refill the water tank"
    }
  }
}
Method result and details JSON-RPC behavior
Core::ERROR_NONE Normal result is returned; populated error details are ignored.
Failure, details unset Standard Thunder error code and message are used.
Failure, only data set Typed data is returned; code and message keep Thunder's defaults.
Failure, only code or message set Only that field overrides the Thunder default.
Failure, all fields set Explicit code, message, and typed data are returned.

A failed method's ordinary output parameter is not returned as a successful JSON-RPC result.

Reading details over COM-RPC

For COM-RPC, errorDetails is an ordinary optional output parameter in the interface signature. A client checks the returned Core::hresult first, then checks whether the output parameter and its individual members are set. Unlike JSON-RPC, COM-RPC does not synthesize default errorDetails.code or errorDetails.message values: unset members remain unset, and the framework result still conveys the standard error. Define any explicit code value so it has a useful meaning to COM-RPC clients as well, rather than relying solely on JSON-RPC-specific conventions.