Handle errors
A failed call ends with a gRPC status. For M-Files errors the status is UNKNOWN, and the
M-Files error code (an HRESULT) and the error stack travel in trailers. Decode them, act on the
codes you handle, and report the rest.
How errors arrive
| What | Where |
|---|---|
| gRPC status | UNKNOWN for M-Files errors; UNAUTHENTICATED for a missing or expired session (no trailers); UNAVAILABLE and others from the transport |
| Message | The status message: the message of the top-level error |
| Error stack | Trailer mfiles-error-serialized-bin: a serialized RPCErrorInfoArray, ordered from the original cause (first) to the top-level error (last) |
| Diagnostic text | Trailer mfiles-error-text-bin: the stack as UTF-8 text, for logs |
error_code is a signed 32-bit integer; mask it with 0xFFFFFFFF to compare it with codes
written in hex. Details and the table of common codes: Errors.
What the helpers give you
The samples' helpers turn a failed call into an error object with the same parts in every language:
| Part | Python (MFilesError) |
C# (MFilesException) |
Go (*mfilesclient.Error) |
|---|---|---|---|
| Error code | hresult, hresult_hex, hresult_signed |
ErrorCode, ErrorCodeHex |
HResult, SignedHResult() |
| All codes, cause first | codes |
ErrorCodes |
Codes |
| Code anywhere in the stack | has(code) |
Has(code) |
Has(code) |
| Message | message |
Message |
Message |
| gRPC status | status |
StatusCode |
Status |
The error code is the code of the last entry of the stack, the top-level error that the
status message describes. If that entry has the generic code 0x80040001, which only passes an
error on, the code is that of the last entry that has another code. Without an error stack (for
example UNAUTHENTICATED) there is no error code.
Test with "has". The code you handle can be anywhere in the stack, so test with has
rather than comparing the error code. Decoding, in Python:
self.long_message: str = trailers.get(ERROR_TEXT_KEY, b"").decode("utf-8", "replace")
self.stack = list(structs_pb2.RPCErrorInfoArray.FromString(
trailers.get(ERROR_STACK_KEY, b"")).value)
self.codes = [entry.error_code & 0xFFFFFFFF for entry in self.stack] # cause first
# The top-level error, skipping entries that only pass the error on.
self.hresult: Optional[int] = next(
(code for code in reversed(self.codes) if code != E_PASS_THROUGH),
self.codes[-1] if self.codes else None)
Source: python/mfiles_client.py
Code
The recipe asks for an object that does not exist, then calls a method that needs vault administrator rights.
Python
# Not found: a document ID that does not exist.
objects = client.stub(IRPCObjectOperations_pb2_grpc.IRPCObjectOperationsStub)
try:
objects.GetObjectInfoAndProperties(
IRPCObjectOperations_pb2.GetObjectInfoAndPropertiesRequest(
obj_id=obj_id(0, 999_999_999), latest_version=True))
raise RuntimeError("Expected the call to fail.")
except MFilesError as error:
if not error.has(E_NOT_FOUND):
raise
print_error("Not found", error)
# Access denied: an administrative call by a user who is not a vault administrator.
admin = client.stub(IRPCPropertyDefsAdmin_pb2_grpc.IRPCPropertyDefsAdminStub)
try:
admin.GetPropertyDefsAdmin(IRPCPropertyDefsAdmin_pb2.GetPropertyDefsAdminRequest())
print("Access denied: not provoked, the user is a vault administrator")
except MFilesError as error:
if not error.has(E_ACCESS_DENIED):
raise
print_error("Access denied", error)
Source: python/recipes/errors.py
C#
// Not found: a document ID that does not exist.
var objects = new IRPCObjectOperations.IRPCObjectOperationsClient(connection.CallInvoker);
try
{
await objects.GetObjectInfoAndPropertiesAsync(new GetObjectInfoAndPropertiesRequest
{
ObjId = ObjId(0, 999_999_999),
LatestVersion = true,
});
throw new InvalidOperationException("Expected the call to fail.");
}
catch (MFilesException e) when (e.Has(NotFound))
{
Print("Not found", e);
}
// Access denied: an administrative call by a user who is not a vault administrator.
var admin = new IRPCPropertyDefsAdmin.IRPCPropertyDefsAdminClient(connection.CallInvoker);
try
{
await admin.GetPropertyDefsAdminAsync(new GetPropertyDefsAdminRequest());
Console.WriteLine("Access denied: not provoked, the user is a vault administrator");
}
catch (MFilesException e) when (e.Has(AccessDenied))
{
Print("Access denied", e);
}
Source: csharp/Recipes/Errors.cs
Go
// Not found: a document ID that does not exist.
objects := mfiles.NewIRPCObjectOperationsClient(client.Conn)
_, err = objects.GetObjectInfoAndProperties(ctx, &mfiles.GetObjectInfoAndPropertiesRequest{
ObjId: mfilesclient.ObjID(0, 999_999_999),
LatestVersion: true,
})
if mfilesErr, ok := mfilesclient.AsError(err); ok && mfilesErr.Has(mfilesclient.CodeNotFound) {
printError("Not found", mfilesErr)
} else {
return fmt.Errorf("expected a not-found error, got %v", err)
}
// Access denied: an administrative call by a user who is not a vault administrator.
admin := mfiles.NewIRPCPropertyDefsAdminClient(client.Conn)
_, err = admin.GetPropertyDefsAdmin(ctx, &mfiles.GetPropertyDefsAdminRequest{})
switch mfilesErr, ok := mfilesclient.AsError(err); {
case err == nil:
fmt.Println("Access denied: not provoked, the user is a vault administrator")
case ok && mfilesErr.Has(mfilesclient.CodeAccessDenied):
printError("Access denied", mfilesErr)
default:
return err
}
Source: go/recipes/errors/main.go
Output for a normal user (the stack lists each code once):
Not found: gRPC status UNKNOWN, M-Files error 0x8004000B (-2147221493)
Message: Not found.
Error stack, original error first: 0x8004000B
Access denied: gRPC status UNKNOWN, M-Files error 0x800407E4 (-2147219484)
Message: Access denied. You do not have the access rights required by this operation.
Error stack, original error first: 0x800400A9, 0x800407E4
The full access-denied stack is 0x800400A9 followed by six entries of 0x800407E4; the not-found
stack is eight entries of 0x8004000B.
Pitfalls
- "Not found" also means "not visible". An object the user has no right to see fails with
0x8004000B, exactly like one that does not exist. - The first and last entries can differ. For access denied, the error code is
0x800407E4and the first entry says what was missing:0x800400A9(a vault-level right) or0x800400AA(rights to the object). Test withhas, not only with the error code. UNAUTHENTICATEDhas no M-Files code and no error stack. Log in again and retry; see Authentication and sessions.- Messages contain
\r\nand can change between versions: match codes, not text. - The diagnostic text and the
fileandlineof each entry name server source files; log them, do not show them to end users.
See also
- Errors: the full error model and the common error codes.
- Permissions: what access denied looks like for objects.