On this page

gRPC Protocol Reference

Interface declaration:

orders = service("orders",
    interface("grpc", "grpc", 50051),
    image = "myapp-orders:latest",
    healthcheck = ready(timeout = "30s"),
)

Methods

call(method=, body={}, descriptors=)

Invoke a unary gRPC method using a local protobuf descriptor set. Reflection is not required. Faultbox encodes the request as the method’s protobuf input and decodes the response as its protobuf output.

resp = orders.grpc.call(
    method="/orders.OrderService/CreateOrder",
    descriptors="./orders.pb",
    body={"item": "widget", "qty": 1},
    metadata={"authorization": "Bearer test-token"},
    timeout="3s",
)
assert_true(resp.ok, resp.error)
# resp.data contains the decoded response fields directly.

Generate descriptors with protoc --include_imports --descriptor_set_out=orders.pb orders.proto. Descriptor paths are resolved relative to the Starlark module that makes the call. Standard google.protobuf types resolve automatically; include other imported schemas in the descriptor set.

ParameterTypeDefaultDescription
methodstringrequiredFull unary gRPC path (/package.Service/Method)
descriptorsstringomittedFileDescriptorSet path; enables typed requests and responses
bodydict or JSON string{}Typed request; unknown fields and invalid values fail before sending
metadatadict{}Request metadata; values are strings or lists of strings
timeoutduration stringcaller deadlinePositive RPC timeout; also respects earlier caller cancellation
body_base64stringomittedExplicit raw protobuf bytes; cannot combine with body or descriptors

Typed response: .data is the decoded message using protobuf field names (e.g. order_id). Integers of protobuf type int64/uint64 are JSON strings, bytes are base64, enums are names, and standard well-known types use protobuf JSON rules. Default scalar fields are included. .headers contains response metadata. .ok is true for gRPC status OK, .status contains the numeric gRPC status (including failures), and .duration_ms records elapsed time.

Streaming methods are rejected. Schema, JSON, and argument errors are reported as spec errors; server errors and RPC deadlines produce a failed response with the real gRPC status.

Legacy raw calls: without descriptors, a string body remains opaque bytes; it is not converted from JSON. An omitted body, "", or "{}" means an empty request, preserving existing health/ping calls. Dict bodies require descriptors. For arbitrary binary requests use body_base64. Raw responses retain .data["method"] and .data["raw"], and add .data["raw_base64"] for lossless access to binary responses (the legacy raw string may replace invalid UTF-8).

gRPC Status Codes

CodeNameDescription
0OKSuccess
1CANCELLEDOperation cancelled
2UNKNOWNUnknown error
3INVALID_ARGUMENTClient sent invalid argument
4DEADLINE_EXCEEDEDTimeout
5NOT_FOUNDResource not found
13INTERNALInternal server error
14UNAVAILABLEService unavailable
16UNAUTHENTICATEDAuthentication required

Fault Rules

error(method=, status=, message=)

Return a gRPC error for matching methods.

unavailable = fault_assumption("orders_unavailable",
    target = orders.grpc,
    rules = [error(method="/orders.OrderService/*", status=14,
                   message="service unavailable")],
)

not_found = fault_assumption("order_not_found",
    target = orders.grpc,
    rules = [error(method="/orders.OrderService/GetOrder", status=5,
                   message="order not found")],
)

deadline = fault_assumption("deadline_exceeded",
    target = orders.grpc,
    rules = [error(method="*", status=4, message="deadline exceeded")],
)
ParameterTypeDescription
methodstringgRPC method glob ("/orders.OrderService/*")
statusintgRPC status code (see table above)
messagestringError message

delay(method=, delay=)

slow_orders = fault_assumption("slow_orders",
    target = orders.grpc,
    rules = [delay(method="/orders.OrderService/CreateOrder", delay="5s")],
)

A matched delay emits its proxy hit (action="delay", phase="started") before waiting. The RPC deadline, client cancellation, or proxy shutdown ends the wait promptly. A separate proxy_delay_completed or proxy_delay_cancelled event records its outcome; rpc_id correlates these events within the service/interface proxy. Outcome events do not count as additional fault hits.

Leaving a fault scope clears rules for new RPCs. Already-matched RPCs retain their delay, subject to cancellation. Clearing rules keeps the proxy listener open; explicit test teardown emits proxy_stopping, cancels active RPCs, and closes it.

drop(method=)

Returns UNAVAILABLE with “connection dropped” message.

drop_creates = fault_assumption("drop_creates",
    target = orders.grpc,
    rules = [drop(method="/orders.OrderService/CreateOrder")],
)

Seed / Reset Patterns

gRPC services are typically backed by a database — seed the database directly rather than the gRPC service:

orders = service("orders",
    interface("grpc", "grpc", 50051),
    image = "myapp-orders:latest",
    depends_on = [db],
    reuse = True,
    # No seed on the gRPC service — seed the DB instead
)

db = service("postgres", ...,
    reuse = True,
    seed = lambda: db.main.exec(sql=open("./seed.sql").read()),
    reset = lambda: db.main.exec(sql="TRUNCATE orders RESTART IDENTITY CASCADE"),
)