faucet-source-grpc
Config-driven gRPC source for the faucet-stream ecosystem. It calls any gRPC service dynamically via protobuf reflection (prost-reflect) — no generated client code, no per-service Rust — using a compiled FileDescriptorSet to encode the request and decode each response into a typed serde_json::Value.
Reach for it when you need to pull data out of an internal gRPC API — a list/get RPC for a one-shot snapshot, or a server-streaming RPC for a long-lived event/change/log feed — and land it in any faucet-stream sink with one declarative config.
Feature highlights
- Dynamic protobuf, zero codegen — point the source at a
FileDescriptorSet(.bin) produced byprotoc; it resolves the service/method, maps your JSONrequestonto the protobuf message, and decodes responses back to JSON. No.protocompilation into your binary. - Two RPC kinds —
unary(one request → one response) andserver_streaming(one request → a stream of responses). Server-streaming is consumed message-by-message and emitted as records arrive. - Native streaming for server-streaming RPCs —
stream_pagesflushes aStreamPageeach timebatch_sizemessages accumulate, bounding both source-side and sink-side memory for unbounded feeds. - Resilient reconnect — server-streaming reconnects on transient transport errors with exponential backoff (
reconnect_initial_backoff→reconnect_max_backoff), an optional attempt cap, and replay-prefix skipping so each message is delivered downstream once. - JSONPath record extraction —
records_path(e.g.$.users[*]) pulls a repeated field out of each response message; unset returns the whole response as a single record. - Three auth modes — none, bearer token (
authorizationmetadata), or arbitrary ordered metadata key/value pairs (duplicate keys allowed). Bearer/metadata auth also resolves from the CLI's sharedauth:catalog viaauth: { ref: <name> }. - TLS auto-detection — inferred from an
https://endpoint, or forced on/off withtls. - Tunable message-size limits — raise
max_decoding_message_size/max_encoding_message_sizeabove tonic's 4 MiB default for large messages. - Connection reuse — the tonic channel is built once and reused for the run.
Installation
# As a library:
# In the CLI (opt-in connector feature):
Via the umbrella crate:
Prerequisites
This source needs a compiled FileDescriptorSet — the binary schema of your protobuf services. Generate it from your .proto files with protoc:
--include_imports is required so transitively-imported message types resolve. The descriptor file drives both request encoding and response decoding at runtime.
Quick start
# pipeline.yaml — faucet run pipeline.yaml
version: 1
pipeline:
source:
type: grpc
config:
endpoint: http://localhost:50051
service_name: users.UserService
method_name: ListUsers
descriptor_set_path: proto/descriptor.bin
request:
page_size: 100
records_path: $.users[*]
sink:
type: jsonl
config:
path: ./users.jsonl
Configuration reference
Core
| Field | Type | Default | Description |
|---|---|---|---|
endpoint |
string | — (required) | gRPC endpoint URL (e.g. http://localhost:50051, https://grpc.example.com:443). |
service_name |
string | — (required) | Fully qualified service name (e.g. users.UserService). Must exist in the descriptor set. |
method_name |
string | — (required) | Method (RPC) name on that service (e.g. ListUsers). |
descriptor_set_path |
path | — (required) | Path to the compiled FileDescriptorSet .bin file. |
request |
object | {} |
Request message as JSON; fields are mapped onto the protobuf request message via the descriptor. Unknown fields fail encoding. |
records_path |
string | (unset) | JSONPath extracting records from each response (e.g. $.users[*]). When unset, the whole response is returned as a single record. For server-streaming it is applied to each message individually. |
tls |
bool | (auto) | Force TLS on/off. When unset, auto-detected from an https:// endpoint. |
Auth
| Field | Type | Default | Description |
|---|---|---|---|
auth |
GrpcAuth / { ref } |
none |
Inline { type, config } (see Authentication) or { ref: <name> } pointing at a shared provider in the CLI's top-level auth: catalog. |
RPC kind & server-streaming
| Field | Type | Default | Description |
|---|---|---|---|
rpc_kind |
enum | unary |
unary (one request → one response) or server_streaming (one request → stream of responses). |
max_messages |
int | (unset) | Server-streaming only. Cap on streamed messages to consume before terminating. Unset = consume until the server closes the stream. |
terminate_on_error |
bool | false |
Server-streaming only. true propagates a transient stream error on first failure; false reconnects with backoff. |
reconnect_initial_backoff |
int (seconds) | 1 |
Server-streaming only. Initial reconnect backoff; doubles each failure up to reconnect_max_backoff. Must be > 0. |
reconnect_max_backoff |
int (seconds) | 30 |
Server-streaming only. Upper bound on reconnect backoff. |
reconnect_max_attempts |
int | (unset) | Server-streaming only. Max reconnect attempts before surfacing the error. Unset = unlimited. |
reconnect_replay_from_start |
bool | true |
Server-streaming only. true skips the already-emitted prefix when a stateless server replays from message 0 (effectively-once downstream); false emits every received message (at-least-once). See Reconnect. |
Batching & limits
| Field | Type | Default | Description |
|---|---|---|---|
batch_size |
int | 1000 |
Records per emitted StreamPage. 0 = no batching (whole result set in one page). For unary RPCs any value behaves identically (full response is buffered first); for server-streaming it bounds memory. Max 1_000_000. |
max_decoding_message_size |
int (bytes) | (tonic 4 MiB) | Max size of a single inbound (decoded) message. Raise for large responses; a too-low limit surfaces as a decode error. |
max_encoding_message_size |
int (bytes) | (tonic default) | Max size of a single outbound (encoded) request message. Rarely needs tuning for a data source. |
Authentication
auth uses the project-wide { type, config } shape (GrpcAuth):
type |
config |
Description |
|---|---|---|
none |
(none) | No authentication (default). |
bearer |
{ token: <string> } |
Token sent as authorization request metadata. |
metadata |
{ entries: [{ key, value }, …] } |
Arbitrary metadata pairs attached to every request. Order is preserved and duplicate keys are allowed (gRPC permits both). |
# Bearer token (via env indirection)
auth:
type: bearer
config:
token: ${env:GRPC_TOKEN}
# Custom metadata (e.g. API key + tenant)
auth:
type: metadata
config:
entries:
-
-
# Shared provider from the top-level auth: catalog
auth:
ref: my_idp
Examples
Authenticated unary list with TLS and record extraction
source:
type: grpc
config:
endpoint: https://grpc.production.example.com:443
service_name: analytics.EventService
method_name: QueryEvents
descriptor_set_path: proto/descriptor.bin
request:
start_time: "2026-01-01T00:00:00Z"
end_time: "2026-02-01T00:00:00Z"
limit: 1000
auth:
type: bearer
config:
tls: true
records_path: $.events[*]
gRPC → HTTP (matches cli/examples/grpc_to_http.yaml)
version: 1
name: grpc_to_http
pipeline:
source:
type: grpc
config:
endpoint: https://grpc.example.com:443
service_name: metrics.MetricsService
method_name: ListMetrics
descriptor_set_path: proto/metrics.bin
request:
auth:
type: bearer
config:
tls: true
records_path: $.metrics[*]
sink:
type: http
config:
url: https://ingest.example.com/v1/events?tenant=acme
method: POST
auth:
type: bearer
config:
batch_mode:
max_retries: 3
concurrency: 8
Server-streaming event feed with reconnect
source:
type: grpc
config:
endpoint: https://grpc.example.com:443
service_name: events.EventService
method_name: Tail
descriptor_set_path: proto/descriptor.bin
request:
auth:
type: bearer
config:
tls: true
rpc_kind: server_streaming
max_messages: 100000
batch_size: 500
reconnect_initial_backoff: 1
reconnect_max_backoff: 30
reconnect_replay_from_start: true
max_decoding_message_size: 16777216 # 16 MiB
Custom-metadata auth, large messages
source:
type: grpc
config:
endpoint: http://localhost:50051
service_name: inventory.InventoryService
method_name: ListProducts
descriptor_set_path: proto/descriptor.bin
request:
auth:
type: metadata
config:
entries:
-
records_path: $.products[*]
max_decoding_message_size: 33554432 # 32 MiB
Streaming & batching
Unary RPCs
A unary RPC returns one response containing all records. stream_pages falls back to the default trait impl, which buffers the full response and chunks it in memory into batch_size pages. This bounds sink-side memory only — source-side memory is O(full response). batch_size = 0 and any positive value are observably identical here, since there is no native wire paging primitive to honour. Treat batch_size as a sink-side chunk size for unary.
Server-streaming RPCs
When rpc_kind: server_streaming, the source calls tonic::client::Grpc::server_streaming and consumes the response stream message-by-message. Each streamed DynamicMessage is decoded via prost-reflect, converted to JSON, and (if records_path is set) flattened per message. stream_pages flushes a page every batch_size records, bounding both source-side and sink-side memory — the right mode for unbounded feeds. batch_size = 0 drains the whole stream into a single page (good for short streams). Pages carry bookmark: None.
Reconnect on transient errors
By default, transient stream errors (server disconnects, transport failures) trigger a reconnect with exponential backoff from reconnect_initial_backoff, doubling up to reconnect_max_backoff; after reconnect_max_attempts (when set) the error is surfaced. Set terminate_on_error: true to propagate on first failure instead.
Reconnect re-sends the same request (resolved once per run), so a stateless server re-streams from message 0. With reconnect_replay_from_start: true (default) the source tracks how many messages it already emitted and skips that replayed prefix, delivering each message once. Set it false only for servers that resume mid-stream on an identical request (rare — most resumable feeds need a resume token in the request, e.g. an after_event_id field you maintain): there, every received message is emitted (at-least-once), so duplicates are possible on replay.
Resume/state: this source has no faucet-managed bookmark or
state:resume. For a resumable feed, drive the cursor through therequest(e.g. anafter_event_idyour config advances), not via a faucet state store.
Config loading & schema introspection
Load config from YAML/JSON, environment, or a .env file:
use ;
use GrpcStreamConfig;
let config: GrpcStreamConfig = load_json?;
let config: GrpcStreamConfig = load_env_file?;
GRPC_ENDPOINT=http://localhost:50051
GRPC_SERVICE_NAME=users.UserService
GRPC_METHOD_NAME=ListUsers
GRPC_DESCRIPTOR_SET_PATH=proto/descriptor.bin
Inspect the full JSON Schema with:
Library usage
use ;
use ;
use json;
# async
For a long-lived server-streaming feed, drive the pipeline so pages flush to the sink as they arrive instead of buffering everything:
use Pipeline;
use ;
use json;
# async
How it works
new()loads and parses theFileDescriptorSet, resolves the service/method, and builds the tonic channel once (TLS auto-detected from the scheme unlesstlsoverrides it).- The JSON
requestis mapped onto the protobuf request message via reflection; any configuredmax_encoding_message_sizeis applied. - Unary: a single response is decoded to JSON;
records_path(if set) extracts records, otherwise the whole response is one record. - Server-streaming: the response stream is consumed message-by-message; each
DynamicMessageis decoded, JSON-converted, andrecords_path-flattened, with reconnect/backoff and replay-prefix skipping wrapping the consume loop. - Records are framed into
batch_sizepages and streamed to the pipeline;max_decoding_message_sizebounds each inbound message.
Lineage dataset URI
<endpoint>/<service_name>/<method_name> with credentials stripped — e.g. http://grpc.example.com:50051/example.Service/ListItems.
Feature flags
This crate has no optional features of its own. Enable it in the CLI/umbrella via the source-grpc feature.
Troubleshooting / FAQ
| Symptom | Likely cause & fix |
|---|---|
| Service or method not found | service_name / method_name don't match the descriptor. Use the fully qualified service name (package.Service) exactly as in the .proto, and confirm the method exists. |
| Descriptor fails to load | Regenerate with --include_imports so transitively-imported types resolve; pass the correct descriptor_set_path. |
| Request field rejected during encoding | A request key isn't a field of the protobuf request message. Match field names (proto field names, not JSON aliases) against the descriptor. |
| TLS handshake fails / plaintext on a TLS endpoint | Set tls explicitly. An http:// endpoint defaults to plaintext, https:// to TLS; override when the scheme and the server disagree. |
Unauthenticated / 401-equivalent |
Wrong or missing credentials. For bearer, the token is sent as authorization metadata; for metadata, confirm the server expects those exact keys. |
| Decode error on a large response | The message exceeds tonic's 4 MiB inbound limit. Raise max_decoding_message_size. |
records_path returns nothing |
The JSONPath doesn't match the decoded response shape. Drop records_path to inspect the raw response, then target the actual array field (e.g. $.users[*]). |
| Server-streaming run never ends | Expected for an open-ended feed. Bound it with max_messages, or cancel the run (the page loop stops at the next boundary). |
| Reconnect busy-spins / errors immediately | reconnect_initial_backoff must be > 0. For non-transient failures set terminate_on_error: true to fail fast. |
| Duplicate or missing messages after a reconnect | Match reconnect_replay_from_start to the server: true for a stateless server that replays from 0, false for one that resumes mid-stream on the same request. |
See also
- Choosing a connector
- Connector capability matrix
- Authentication cookbook
faucet-sink-http·faucet-source-rest·faucet-source-graphql
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.