Error handling
The Manager Service API error handling allows a specific service to report its own service-specific errors. Additionally, the internal MSA logic uses this error handling to communicate error situations.
A service can return an error to the client. The error status consists of an error number and an error text. The number is an element of a static list/enum and the text is variable. You can also send subsequent metadata together with the error status.
How sending works depends on the programming language.
- C#, TypeScript and CTRL support the classic
throwandtry-catchsyntax - In C++ a
statusobject is returned - Optionally, the CTRL
client can also return a
status object
Error Handling Examples
In the service implementation method, you can use the vrpcThrow
function to return an MSA-specific error. While it is also possible to use
throw, this approach provides less support on the client side.
public anytype someServiceMethodImplWithArgCheck(VrpcServerContext &serverContext, anytype request)
{
// Verify argument type
if (getType(request) != STRING_VAR)
vrpcThrow(VrpcStatusCode::InvalidArgument, "Request must be a string");
// Parse request
string name = request;
// Verify arguments
if (name.isEmpty())
{
serverContext.addTrailingMetadata("error-details", "Some details"); // Optional set additional info
vrpcThrow(VrpcStatusCode::InvalidArgument, "Name cannot be empty");
}
//...
return "MyResult";
}In the service implementation method, you can return a Status object that contains an error.
::vrpc::Status someServiceMethodImplWithArgCheck(std::shared_ptr<::vrpc::ServerContext> serverContext, const Variable* request, Variable*& response)
{
// Verify argument type
if (request == nullptr || request->isA(TEXT_VAR) != TEXT_VAR)
return ::vrpc::Status(::vrpc::StatusCode::InvalidArgument, "Request must be a string");
// Parse request
TextVar name; name = *request;
// Verify arguments
if (name.getString().isEmpty())
{
serverContext->addTrailingHeader("error-details", "Some details"); // Optional set additional info
return ::vrpc::Status(::vrpc::StatusCode::InvalidArgument, "Name cannot be empty");
}
//...
response = new TextVar("MyResult");
return vrpc::Status::OK;
} In the service implementation method, you can throw any exception. However, we
recommend using RpcException, as it provides the best support on the
client side.
public async Task<OaVariant> SomeServiceMethodImplWithArgCheck(OaVariant request, ServerCallContext serverContext)
{
// Verify argument type
if (request.VariantType != OaVariantType.TextVar)
throw new RpcException(new Status(StatusCode.InvalidArgument, "Request must be a string"));
// Parse request
string name = request.AsString();
// Verify arguments
if (name.Length == 0)
{
var trailers = new Metadata // Optional set additional info
{
{ "error-details", "Some details" }
};
throw new RpcException(new Status(StatusCode.InvalidArgument, "Name cannot be empty"), trailers);
}
//...
return new OaVariant("MyResult");
} In the service implementation method, you can throw any error. However, we recommend
using Vrpc.Error, as it provides the best possible support on the
client side.
private async someServiceMethodImplWithArgCheck(
serverContext: Vrpc.ServerContext,
request: Vrpc.Variant,
): Promise<Vrpc.Variant> {
// Verify argument type
if (!request.isString() || request.isNull())
throw new Vrpc.Error(
new Vrpc.Status(
Vrpc.StatusCode.InvalidArgument,
'Request must be a string',
),
);
// Parse request
const name: string = request.getString();
// Verify argument value
if (name.length == 0) {
const trailingMetadata = new Vrpc.Metadata();
trailingMetadata.add('error-details', 'Some details'); // Optional set additional info
throw new Vrpc.Error(
new Vrpc.Status(
Vrpc.StatusCode.InvalidArgument,
'Name cannot be empty',
),
trailingMetadata,
);
}
//...
return Vrpc.Variant.createString('MyResult');
} On the Client:
As previously mentioned, the CTRL client supports two modes of error handling. By
default, an exception is thrown when the client receives an error from the service. You
can catch this error using a try-catch block along with the
vrpcGetLastException function. Alternatively, you can change this
behavior by calling setThrowExceptionOnError(false) on the stub object.
This will return a status object in the response instead of throwing an exception.
public void callSomeFunctionWithErrorHandling1(shared_ptr<VrpcStub> stub, anytype request)
{
try
{
stub.callFunction("someServiceMethodImplWithArgCheck", request);
}
catch
{
VrpcException ex = vrpcGetLastException();
// Print error information
Metadata trailers = ex.getTrailers(); // Error may send trailing headers
string trailersStr = metadataToString(trailers);
DebugN("MSA-specific Exception thrown: " + ex.getStatus().getStatusCode() + ", " + ex.getStatus().getText() + ", " + trailersStr);
}
//...
}
public void callSomeFunctionWithErrorHandling2(shared_ptr<VrpcStub> stub, anytype request)
{
stub.setThrowExceptionOnError(false);
VrpcResponseData responseData = stub.callFunction("SomeFunction", request);
VrpcStatus status = responseData.getStatus();
if (!status.isOk())
{
// Print error information
Metadata trailers = responseData.getTrailingHeaders(); // Error may send trailing headers
string trailersStr = metadataToString(trailers);
DebugN("Received Error Status: " + status.getStatusCode() + ", " + status.getText() + ", " + trailersStr);
}
//...
}
private string metadataToString(const Metadata &metadata)
{
string str;
for (int i = 0; i < metadata.count(); i++)
{
MetadataEntry entry = metadata.getAt(i);
str = str + " " + entry.getKey() + " = " + (entry.isBinary() ? entry.getValueBinary() : entry.getValue());
}
return str;
} void callSomeFunctionWithErrorHandling(std::unique_ptr<::vrpc::Stub> stub, const Variable& request)
{
std::shared_ptr<DefaultWaitForResponse> wait = std::make_shared<DefaultWaitForResponse>();
stub->callFunctionWait("SomeFunction", request, wait);
while (!wait->isFinished()) { Manager::dispatch(0.05); }
const ::vrpc::ResponseData& responseData = wait->getResponseData();
::vrpc::Status status = responseData.getStatus();
if (!status.isOk())
{
// Print error information
const auto& trailingHeaders = responseData.getTrailingHeaders(); // Error may send trailing headers
std::ostringstream trailersStream;
for (const auto& item : trailingHeaders)
trailersStream << " " << item.getKey() << " = " << item.getValue();
ErrHdl::error(ErrClass::PRIO_SEVERE, ErrClass::ERR_IMPL, ErrClass::UNEXPECTEDSTATE, "Received Error Status: ", status.toString(), trailersStream.str().c_str());
}
//...
} public async Task CallSomeFunctionWithErrorHandling(VrpcStub stub, OaVariant request)
{
try
{
await stub.CallFunctionAsync("SomeFunction", request);
}
catch (RpcException ex)
{
// Print error information
// Error may send trailing headers
_logger.LogError($"MSA-specific Exception thrown: {ex.StatusCode}, {ex.Message}, {ex.TrailingHeaders}");
throw new InvalidOperationException("SomeFunction failed", ex);
}
catch (Exception ex)
{
_logger.LogError($"Some Exception thrown: {ex.Message}");
throw new InvalidOperationException("SomeFunction failed", ex);
}
//...
} public async callSomeFunctionWithErrorHandling(
stub: Vrpc.Stub,
request: Vrpc.Variant,
): Promise<void> {
try {
await stub.callFunction('SomeFunction', request);
} catch (error) {
// Print error information
if (error instanceof Vrpc.Error) {
var trailersString = '';
if (error.trailingHeaders)
trailersString = MetadataToString(
'Received error Metadata',
error.trailingHeaders,
);
console.error(
`MSA-specific Error thrown: ${error.status.statusCode}, ${error.status.text}, ${trailersString}`,
);
} else if (error instanceof Error)
console.error('Some Error thrown: ' + error.message);
else console.error('Something else thrown: ' + error);
}
//...
} 