hrpc-c
v0.1.0
Published
C code generation for HRPC
Readme
hrpc-c
C code generation for HRPC. Generates sans-io C that encodes, decodes, and dispatches RPC commands over the bare-rpc wire protocol using librpc and libcompact, reusing struct codecs from hyperschema-c.
Sans-io means the generated code is pure functions: it never owns a socket, an event loop, or stream state. You call the typed encoders to produce frames, hand inbound frames to the dispatcher, and do the actual I/O and stream lifecycle yourself.
Install
npm install hrpc-cUsage
CHRPC extends the JS hrpc builder. Define a schema with hyperschema-c, register handlers, then write the C to disk (or get it as strings).
const CHyperschema = require('hyperschema-c')
const CHRPC = require('hrpc-c')
const schema = CHyperschema.from('./spec')
const greeter = schema.namespace('greeter')
greeter.register({ name: 'hello-request', fields: [{ name: 'id', type: 'uint', required: true }] })
greeter.register({
name: 'hello-response',
fields: [{ name: 'greeting', type: 'uint', required: true }]
})
const hrpc = new CHRPC(schema, './spec')
hrpc.namespace('greeter').register({
name: 'hello',
request: { name: '@greeter/hello-request', stream: false },
response: { name: '@greeter/hello-response', stream: false }
})
CHyperschema.toDisk(schema)
CHRPC.toDisk(hrpc) // writes to hrpc.hrpcDir, or pass an explicit dirCHRPC.toDisk(hrpc, dir, opts) writes five files into dir: hrpc.json (the schema), hrpc.h (the shared, schema-independent runtime types and enums), <target>.h and <target>.c (the generated per-schema header and source), and CMakeLists.txt. <target> is the namespaces joined with _ plus _hrpc (e.g. greeter_hrpc, or greeter_admin_hrpc for several namespaces). opts.schemaTarget overrides the CMake target name the generated CMakeLists.txt expects for the hyperschema-c structs (default <namespaces>_schema). CHRPC.toCode(opts) returns { shared, header, source } as strings instead of writing.
The generated CMakeLists.txt expects the consumer to provide compact, rpc, and the schema target as CMake targets.
Generated surface
Identifiers are namespaced <ns>_... and command ids are the global, append-only ids from the schema. Each handler is one of five kinds, by its request/response shape:
- unary (
request+response, neither streaming):<ns>_encode_<name>(client request),<ns>_encode_<name>_error,<ns>_decode_<name>_response, and a handlerint (*<ns>_on_<name>)(void *ctx, const <req>_t *req, <res>_t *res, hrpc_error_t *error). - event (
request.send: true, noresponse):<ns>_encode_<name>(fire-and-forget, id 0) and avoid (*<ns>_on_<name>)(void *ctx, const <req>_t *req). - response-stream (
response.stream: true): the client request encoder plus server frame encoders_open/_chunk/_end/_error, a client_stream_openecho, a_decode_<name>_chunk, andint (*on)(ctx, req, uint64_t stream_id). - request-stream (
request.stream: true): client_open/_chunk/_end, server_stream_openecho +_decode_<name>_chunk, a single unary-style reply (_response/_errorencoders +_decode_<name>_response), andint (*on)(ctx, uint64_t stream_id). - duplex (both streaming): the union of the two stream directions, fully
request_/response_qualified (_request_open,_request_chunk,_request_end,_response_open,_response_chunk,_response_end,_response_error, the two_stream_openechoes, and both_decode_*_chunk), withint (*on)(ctx, uint64_t stream_id).
Every *_encode_* heap-allocates *out (the caller frees). Decoded views (hrpc_error_t strings, chunk payloads) point into the inbound frame buffer, so copy out anything kept past the read loop.
Dispatch
<target>_dispatch(const <target>_handlers_t *handlers, const rpc_message_t *msg, uint8_t **reply_out, size_t *reply_len) routes one decoded inbound rpc_request by command id to the matching handler, and returns one of:
hrpc_dispatch_reply- a unary reply was written to*reply_out/*reply_len; send it, then free it.hrpc_dispatch_no_reply- an event was handled; nothing to send.hrpc_dispatch_stream- a stream was opened; the handler drives the frames overmsg->idvia its own transport. No reply buffer is written.< 0- an error code (hrpc_err_unknown,hrpc_err_decode,hrpc_err_alloc).
Dispatch only sees the inbound request that opens a conversation. Subsequent stream frames carry only a stream id (not a command), so routing them by id - and the open handshake, flow control, and lifecycle - is the caller's responsibility (a job for a stateful client runtime such as librpc's rpc_client_t).
See SPEC.md for the full design.
License
Apache-2.0
