mirror of
https://codeberg.org/JasterV/granc.git
synced 2026-04-26 18:40:05 +00:00
124 lines
4.7 KiB
Markdown
124 lines
4.7 KiB
Markdown
# Granc Core
|
|
|
|
[](https://crates.io/crates/granc_core)
|
|
[](https://docs.rs/granc_core)
|
|
[](https://github.com/JasterV/granc/blob/main/LICENSE)
|
|
|
|
**`granc-core`** is the foundational library powering the [Granc CLI](https://crates.io/crates/granc). 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.
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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.
|