Skip to content
/ granc Public

A dynamic gRPC CLI tool written in Rust (gRPC + Cranc, Crab in Catalan)

License

Notifications You must be signed in to change notification settings

JasterV/granc

Repository files navigation

Granc 🦀

granc on crates.io License

⚠️ Status: Experimental

This project is a working prototype intended for testing and development purposes. APIs, command-line arguments, and internal logic are subject to breaking changes. Please use with caution.

Granc (gRPC + Cranc, Crab in Catalan) is a lightweight, dynamic gRPC CLI tool written in Rust.

It allows you to make gRPC calls to any server using simple JSON payloads, without needing to compile the specific Protobuf files into the client. By loading a FileDescriptorSet at runtime, granc acts as a bridge between human-readable JSON and binary Protobuf wire format.

It is heavily inspired by tools like grpcurl but built to leverage the safety and performance of the Rust ecosystem (Tonic + Prost).

🚀 Features

  • Dynamic Encoding/Decoding: Transcodes JSON to Protobuf (and vice versa) on the fly using prost-reflect.
  • Metadata Support: Easily attach custom headers (authorization, tracing) to your requests.
  • Fast Fail Validation: Validates your JSON before hitting the network.
  • Smart Dispatch: Automatically detects if a call is Unary, Server Streaming, Client Streaming, or Bidirectional based on the descriptor.
  • Server Reflection: Can fetch schemas directly from the server, removing the need to pass a local file descriptor set file (.bin or .pb).
  • Introspection Tools: Commands to list services and describe services, messages, and enums.
    • Local Introspection: In addition to making network requests, Granc can also be used as a local introspection tool for file descriptor binary files. You can load a local .bin file to inspect services, messages, and enums without needing to fetch the schema from a server.
  • Zero Compilation Dependencies: Does not require generating Rust code for your protos. Just point to a descriptor file.
  • Tonic 0.14: Built on the latest stable Rust gRPC stack.

📦 Installation

cargo install --locked granc

🛠️ Prerequisites

Granc needs to know the schema of the service you are calling. It can obtain this in two ways:

  1. Automatic Server Reflection: If the server has Server Reflection enabled, Granc can download the schema automatically.
  2. Local Descriptor File: You can provide a binary FileDescriptorSet (.bin) generated by protoc.

Generating Descriptors (Optional)

If your server does not support reflection, you must generate a descriptor file:

# Generate descriptor.bin including all imports
protoc \
    --include_imports \
    --descriptor_set_out=descriptor.bin \
    --proto_path=. \
    my_service.proto

Note: The --include_imports flag is crucial. It ensures that types defined in imported files (like google/protobuf/timestamp.proto) are available for reflection.

📖 Usage

Syntax:

granc <COMMAND> [ARGS]

Commands

1. call (Make Requests)

Performs a gRPC call using a JSON body.

granc call <ENDPOINT> --uri <URI> --body <JSON> [OPTIONS]
Argument/Flag Short Description Required
<ENDPOINT> Fully qualified method name (e.g., my.package.Service/Method). Yes
--uri -u Server address (e.g., http://[::1]:50051). Yes
--body -b The request body in JSON format. Object {} for unary, Array [] for streaming. Yes
--header -H Custom header key:value. Can be used multiple times. No
--file-descriptor-set -f Path to a local .bin descriptor file to use instead of reflection. No

Example using Server Reflection:

granc call helloworld.Greeter/SayHello --uri http://localhost:50051 --body '{"name": "Ferris"}'
{
  "message": "Hello Ferris"
}

Example using a Local Descriptor File:

granc call helloworld.Greeter/SayHello \
  --uri http://localhost:50051 \
  --file-descriptor-set ./descriptors.bin \
  --body '{"name": "Ferris"}'

2. list (Service Discovery)

Lists all services exposed by the server (via reflection) or contained in the provided descriptor file. You must provide either a URI or a file descriptor set.

granc list [OPTIONS]
Flag Short Description
--uri -u Use Server Reflection to list available services.
--file-descriptor-set -f Use a local file to list contained services (offline).

Listing services via Reflection:

granc list --uri http://localhost:50051
Available Services:
  - grpc.reflection.v1.ServerReflection
  - helloworld.Greeter

Listing services from a file (Offline):

granc list --file-descriptor-set ./descriptors.bin

3. describe (Introspection)

Inspects a specific symbol (Service, Message, or Enum) and prints its Protobuf definition in a colored, human-readable format. You must provide either a URI or a file descriptor set.

granc describe <SYMBOL> [OPTIONS]
Argument/Flag Short Description
<SYMBOL> Fully qualified name of the Service, Message, or Enum.
--uri -u Use Server Reflection to resolve the symbol.
--file-descriptor-set -f Use a local file to resolve the symbol (offline).

Describing a Service via Reflection:

granc describe helloworld.Greeter --uri http://localhost:50051
service Greeter {
  rpc SayHello(helloworld.HelloRequest) returns (helloworld.HelloReply);
  rpc StreamHello(stream helloworld.HelloRequest) returns (stream helloworld.HelloReply);
}

Describing a Message using a Local File:

granc describe helloworld.HelloRequest --file-descriptor-set ./descriptors.bin
message HelloRequest {
  string name = 1;
  int32 age = 2;
  repeated string tags = 3;
}

Describing an Enum:

granc describe my.package.Status --uri http://localhost:50051
enum Status {
  UNKNOWN = 0;
  ACTIVE = 1;
  INACTIVE = 2;
}

🔮 Roadmap

  • Interactive Mode: A REPL for streaming requests interactively.
  • Pretty Printing JSON: Enhanced colored output for JSON responses.
  • TLS Support: Configurable root certificates and client identity.

🧩 Using as a Library

The core logic of Granc is decoupled into a separate library crate, granc-core.

If you want to build your own tools using the dynamic gRPC engine (e.g., for custom integration testing, proxies, or automation tools), you can depend on granc-core directly.

⚠️ Common Errors

**1. Service 'x' not found**

  • Cause: The service name in the command does not match the package defined in your proto file.
  • Fix: Check your .proto file. If it has package my.app; and service API {}, the full name is my.app.API.

**2. Method 'y' not found in service 'x'**

  • Cause: Typo in the method name or the method doesn't exist.
  • Fix: Ensure case sensitivity matches (e.g., GetUser vs getUser).

**3. h2 protocol error**

  • Cause: This often occurs when the JSON payload fails to encode after the connection has already been established, or the server rejected the stream structure.
  • Fix: Double-check your JSON payload against the Protobuf schema.

🤝 Contributing

Contributions are welcome! Please run the Makefile checks before submitting a PR:

cargo make ci # Checks formatting, lints, and runs tests

📄 License

Licensed under either of:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

A dynamic gRPC CLI tool written in Rust (gRPC + Cranc, Crab in Catalan)

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •  

Languages