TypeDB.GRPC.Error (TypeDB.GRPC v0.2.0)

Copy Markdown View Source

Turning gRPC failures into the %TypeDB.Error{} the sibling driver returns.

This module exists so that the two transports are interchangeable at the call site. An application that routes failures by kind, by code, or through TypeDB.Error.retryable?/1 keeps every branch it wrote when it switches, and that is the whole reason typedb_grpc depends on typedb rather than defining error types of its own.

Where a TypeDB error code lives on this transport

Nowhere obvious, which is why this is a module and not a function. A gRPC failure arrives as a GRPC.RPCError whose message is a category ("Unauthenticated", "Request generated error") rather than anything about the request. The useful parts are in details, as two google.rpc messages that TypeDB attaches by hand:

  • ErrorInfo.reason — the TypeDB code, "AUT1", "DBD1", "STC2". The same codes the HTTP API puts in its JSON body.
  • DebugInfo.stack_entries — the human messages, most specific first.

Measured against 3.12.1: deleting a database that does not exist arrives as gRPC status 3 with reason: "DBD1" and stack entries ["[DBD1] Cannot delete database since it does not exist.", "[SRV13] Unable to delete database."].

Why :status carries an HTTP status on a transport that has none

%TypeDB.Error{} documents :status as the HTTP status, and there is no HTTP here. It is filled anyway, with the standard gRPC-to-HTTP equivalence that grpc-gateway and every other bridge uses, because TypeDB.Error.retryable?/1 judges :server errors by status — and a nil there would quietly make the same failure retryable over one transport and terminal over the other. That divergence is exactly what sharing the struct is meant to prevent.

The gRPC status is not lost: it is in :reason, as {:grpc_status, integer}.

The one status whose kind depends on more than the status

INTERNAL (13) is :server when it carries details and :transport when it carries none. TypeDB attaches ErrorInfo and DebugInfo to every error it generates, so an INTERNAL with neither did not come from TypeDB — it is the gRPC stack reporting a failure of its own, and the one this rule was written for is a connection that never came up. A plaintext client against a TLS port is the reproducible case: it arrives as INTERNAL with :connection_error: {:protocol_error, :"Invalid connection preface received"} and no details, and calling that :server says the server answered when nothing answered at all.

It also made one misconfiguration report two different kinds depending on which side of a race won — :timeout when the TLS handshake simply hung, :server when the client's HTTP/2 layer got far enough to reject the bytes. Both are now :transport, which is what from_reason/2 has always called an unestablished connection.

Summary

Functions

Converts anything else a gRPC call can fail with.

Converts a GRPC.RPCError into a %TypeDB.Error{}.

The HTTP status this driver reports for a gRPC status code.

Functions

from_reason(reason, context)

@spec from_reason(term(), String.t()) :: TypeDB.Error.t()

Converts anything else a gRPC call can fail with.

The adapter reports a connection that could not be established, or a stream that died, as plain terms rather than as GRPC.RPCError. They are transport failures in the sense %TypeDB.Error{} means: the request produced no answer, and trying again could produce one.

from_rpc_error(error, context)

@spec from_rpc_error(GRPC.RPCError.t(), String.t()) :: TypeDB.Error.t()

Converts a GRPC.RPCError into a %TypeDB.Error{}.

context is prepended to the message when the server gave nothing better — it names the operation, so that a bare "Request generated error" still tells the reader what was being attempted.

http_status(grpc_status)

@spec http_status(integer()) :: pos_integer()

The HTTP status this driver reports for a gRPC status code.

iex> TypeDB.GRPC.Error.http_status(16)
401

iex> TypeDB.GRPC.Error.http_status(14)
503

Unknown codes become 500: a gRPC status this driver has not seen is the server behaving in a way nobody predicted, which is what 500 means.