granc_core 0.6.0

Cranc gRPC CLI core library
Documentation

Granc Core

Crates.io Documentation License

granc-core is the foundational library powering the Granc CLI. It provides a dynamic gRPC client capability that allows you to interact with any gRPC server without needing compile-time Protobuf code generation.

Instead of strictly typed Rust structs, this library bridges standard serde_json::Value payloads directly to Protobuf binary wire format at runtime.

🚀 High-Level Usage

The primary entry point is the [GrancClient]. It uses a Typestate Pattern to ensure safety and correctness regarding how the Protobuf schema is resolved. There are three distinct states:

  1. [Online]: Connected to a server, uses Server Reflection (Async introspection).
  2. [OnlineWithoutReflection]: Connected to a server, uses a local FileDescriptorSet (Sync introspection).
  3. [Offline]: Disconnected, uses a local FileDescriptorSet (Sync introspection).

1. Online (Server Reflection)

This is the default state when you connect. The client queries the server's reflection endpoint to dynamically discover services and message formats.

use granc_core::client::{GrancClient, DynamicRequest, DynamicResponse};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Connect (Starts in 'Online' state)
    let mut client = GrancClient::connect("http://localhost:50051").await?;

    // 2. Introspection (Async via Reflection)
    let services = client.list_services().await?;
    println!("Server services: {:?}", services);

    // 3. Dynamic Call
    let request = DynamicRequest {
        service: "helloworld.Greeter".to_string(),
        method: "SayHello".to_string(),
        body: json!({ "name": "Ferris" }),
        headers: vec![],
    };

    // Schema is fetched automatically from the server
    let response = client.dynamic(request).await?;
    println!("{:?}", response);

    Ok(())
}

2. OnlineWithoutReflection (Local Schema)

Use this state if you are connecting to a server that does not support reflection, or if you want to enforce a specific schema version from a local file.

use granc_core::client::GrancClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = GrancClient::connect("http://localhost:50051").await?;
    let descriptor_bytes = std::fs::read("descriptor.bin")?;

    // Transition state: Online -> OnlineWithoutReflection
    let mut client = client.with_file_descriptor(descriptor_bytes)?;

    // Introspection is now SYNCHRONOUS (in-memory)
    let services = client.list_services(); 
    println!("Local services: {:?}", services);

    // Dynamic calls use the local schema to encode/decode
    // client.dynamic(req).await?;

    Ok(())
}

3. Offline (Introspection Only)

This state is useful for building tools that need to inspect .bin descriptor files without establishing a network connection.

use granc_core::client::GrancClient;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let descriptor_bytes = std::fs::read("descriptor.bin")?;

    // Create directly in 'Offline' state
    let client = GrancClient::offline(descriptor_bytes)?;

    // Sync introspection methods
    let services = client.list_services();
    
    if let Some(descriptor) = client.get_descriptor_by_symbol("helloworld.Greeter") {
        println!("Found service: {:?}", descriptor);
    }

    // Note: client.dynamic() is NOT available in this state.
    Ok(())
}

🛠️ Internal Components

We expose the internal building blocks of granc for developers who need more granular control or want to build their own tools on top of our dynamic transport layer.

1. GrpcClient (Generic Transport)

Standard tonic clients are strongly typed. GrpcClient is a generic wrapper around tonic::client::Grpc that works strictly with serde_json::Value and prost_reflect::MethodDescriptor. It handles the raw HTTP/2 path construction and metadata mapping.

2. JsonCodec

The magic behind the dynamic serialization. This implementation of tonic::codec::Codec validates and transcodes JSON to Protobuf bytes (and vice versa) on the fly.

3. ReflectionClient

A robust client for grpc.reflection.v1. It automatically handles transitive dependency resolution, recursively fetching all imported files to build a complete, self-contained FileDescriptorSet.

⚖️ License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.