Skip to content

foxygoat/jig

Repository files navigation

Jig

Jig lets you use jsonnet to implement gRPC methods, e.g.:

// Greeter.Hello
function(input) {
  response: {
    greeting: '💃 : Hello ' + input.request.firstName,
  },
}

The jsonnet method definitions can be updated in real time without restarting jig.

Jig uses protobuf FileDescriptorSets, generated by protoc into .pb files, as a description of valid gRPC method names, request and response types. Jig does not need any further pre-generated or pre-compiled code.

Usage

Generate a FileDescriptorSet for the services to stub with:

protoc --descriptor_set_out service.pb --include_imports service.proto

Put jsonnet method definitions together in a directory, each file named <pkg>.<service>.<method>.jsonnet. The jsonnet file is (re-)evaluated when the gRPC server receives a call to that method.

You can generate skeleton jsonnet method definitions using jig bones:

jig bones --proto-set=dir/service.pb --method-dir=dir

Request protobuf messages are marshaled to JSON and passed to the jsonnet method definition function as the input parameter. If the method is a unary, server-streaming or bidirectional streaming method, the request message is placed in the request field of input:

{
    request: { ...json-encoded gRPC request protobuf... }
}

If the method is a client-streaming method, the stream of request messages is placed in the stream field of input as an array:

{
    stream: [ {request1}, {request2}, ...]
}

For bidirectional streaming methods, the jsonnet method definition is evaluated once for each message on the request stream (with a single message in the request field). Once EOF has been received on the request stream, the jsonnet method definition is evaluated one more time with the request field set to null.

Response protobuf messages are unmarshaled from the jsonnet evaluation of the method definition. The result must evaluate as an object with fields describing the response to send back to the gRPC client.

If the method is a unary or client-streaming method, the result must have a response field that contains the response message encoded as JSON:

function(input) {
    response: { ...json-encoded gRPC response protobuf... }
}

If the method is a server- or bidirectional streaming method, the result must have a stream field that contains an array of response messages encoded as JSON:

function(input) {
    stream: [ {response1}, {response2}, ... ]
}

A gRPC status can be returned in the status field:

function(input) {
    status: {
        code: 3,
        message: 'Field "foo" failed validation: 0 < foo < 10',
        details: [
            {
                '@type': 'type.googleapis.com/google.protobuf.Duration',
                value: '15.2s',
            },
        ],
    },
}

If a result has a status field, it must not have a response or stream field.

The response can reference fields of the input using regular jsonnet references. See the testdata samples.

The request and response fields are encoded from/to protobuf messages according to the protojson encoding rules.

To serve these jsonnet methods, run:

jig serve <dir>

Playing

jig serve

Build and start jig on the test data:

. ./bin/activate-hermit
make install
jig serve --http serve/testdata/greet

in a second terminal call it with:

client world

To see streaming, call it with:

client --stream=server world
client --stream=client you me world
client --stream=bidi you me world

The --http flag passed to jig serve above allows you to make HTTP requests:

curl \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"firstName": "Kitty"}' \
    localhost:8080/api/greet/hello

Experiment with the jsonnet method files in the testdata directory.

Alternatively there is a traditional, generated gRPC server that the same client can interact with. Start it with:

server

jig bones

To get started on writing a jsonnet method definition, the jig bones subcommand generates a skeleton showing the input and output forms of a method.

To see a message with all the different types of message fields:

jig bones --proto-set pb/exemplar/exemplar.pb

To see the structure of the different method streaming types:

jig bones --proto-set pb/greet/greeter.pb

Development

. ./bin/activate-hermit
make ci

Run make help for help on other make targets.