mirror of
https://codeberg.org/JasterV/granc.git
synced 2026-04-26 18:40:05 +00:00
This pull request introduces major improvements to the `granc` gRPC CLI, focusing on enhanced introspection and discovery features, a more user-friendly command-line interface, and improved error and output formatting. The changes include new commands for listing and describing services, methods, and messages, a restructured CLI argument parser, and a new formatter for colored, readable output. Additionally, the core client is extended to support these new features, and error handling is refactored for clarity. **New CLI features and UX improvements:** * Added new `list` and `describe` commands to the CLI, allowing users to discover available services and inspect service/message definitions directly from the server using reflection. The CLI argument structure is now subcommand-based for better usability. [[1]](diffhunk://#diff-dfa67e7f5e147119fe8d665da6b31b3605f5e196734ec7407aab2bcc9e2f656cL8-R84) [[2]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L71-R139) * Updated the README with documentation for the new commands and improved usage instructions. [[1]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5R21) [[2]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L71-R139) **Core client and reflection enhancements:** * Implemented new methods in the core client for listing services and fetching symbol descriptors via reflection, including robust error types for each operation. [[1]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adL25-R27) [[2]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adL38-R78) [[3]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adR149-R211) [[4]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adL125-R229) [[5]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adL154-R252) [[6]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adL164-R262) [[7]](diffhunk://#diff-13deee04dd97de938cc46f0ef4faca083f3b471800e94cf45937122b83f01d57R19) [[8]](diffhunk://#diff-13deee04dd97de938cc46f0ef4faca083f3b471800e94cf45937122b83f01d57R124-R161) **Output formatting and error handling:** * Added a new `formatter` module for producing colored, human-friendly output for all major CLI operations, including pretty-printing of service lists, descriptors, and errors. * Improved error handling throughout the client and CLI, with more specific error types and user-facing messages. [[1]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adL38-R78) [[2]](diffhunk://#diff-0de6f761cf394791a15b0707e1a41f54559b5626f7aedb06ef339bc1a7ca6287R1-R248) **Dependency and project structure updates:** * Updated dependencies and added the `colored` crate for output styling.
141 lines
5.3 KiB
Markdown
141 lines
5.3 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 acts as an orchestrator that connects to a gRPC server and provides methods for both executing requests and inspecting the server's schema.
|
|
|
|
### 1. Making a Dynamic Call
|
|
|
|
The `dynamic` method handles the full request lifecycle:
|
|
|
|
1. Resolves the schema (either from a local file or via Server Reflection).
|
|
2. Determines the method type (Unary, Server Streaming, etc.).
|
|
3. Executes the request using JSON.
|
|
|
|
```rust
|
|
use granc_core::client::{GrancClient, DynamicRequest, DynamicResponse};
|
|
use serde_json::json;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Connect to the server
|
|
let mut client = GrancClient::connect("http://localhost:50051").await?;
|
|
|
|
// Prepare the request
|
|
// If you don't provide a file_descriptor_set, the client will attempt
|
|
// to fetch the schema from the server's reflection service automatically.
|
|
let request = DynamicRequest {
|
|
service: "helloworld.Greeter".to_string(),
|
|
method: "SayHello".to_string(),
|
|
body: json!({ "name": "World" }),
|
|
headers: vec![],
|
|
file_descriptor_set: None, // Uses Server Reflection
|
|
};
|
|
|
|
let response = client.dynamic(request).await?;
|
|
|
|
match response {
|
|
DynamicResponse::Unary(Ok(value)) => {
|
|
println!("Response: {}", value);
|
|
}
|
|
DynamicResponse::Unary(Err(status)) => {
|
|
eprintln!("gRPC Error: {:?}", status);
|
|
}
|
|
DynamicResponse::Streaming(Ok(stream)) => {
|
|
for msg in stream {
|
|
println!("Stream Msg: {:?}", msg);
|
|
}
|
|
}
|
|
_ => eprintln!("Unexpected response type"),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
```
|
|
|
|
### 2. Schema Introspection
|
|
|
|
`GrancClient` exposes several methods to inspect the server's available services and types using reflection.
|
|
|
|
```rust
|
|
// List all services exposed by the server
|
|
let services = client.list_services().await?;
|
|
println!("Available Services: {:?}", services);
|
|
|
|
// Get the descriptor for a specific type
|
|
let descriptor = client.get_descriptor_by_symbol("helloworld.Greeter").await?;
|
|
|
|
match descriptor {
|
|
Descriptor::MessageDescriptor(descriptor)) => println!("{}", descriptor.name())
|
|
Descriptor::ServiceDescriptor(descriptor)) => println!("{}", descriptor.name())
|
|
Descriptor::EnumDescriptor(descriptor)) => println!("{}", descriptor.name())
|
|
}
|
|
```
|
|
|
|
## 🛠️ 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 (e.g., `client.say_hello(HelloRequest)`).
|
|
`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, providing specific methods for all four gRPC access patterns:
|
|
|
|
* `unary`
|
|
* `server_streaming`
|
|
* `client_streaming`
|
|
* `bidirectional_streaming`
|
|
|
|
```rust
|
|
use granc_core::grpc::client::GrpcClient;
|
|
// You need a method_descriptor from prost_reflect::DescriptorPool
|
|
// let method_descriptor = ...;
|
|
|
|
let mut grpc = GrpcClient::new(channel);
|
|
let result = grpc.unary(method_descriptor, json_value, headers).await?;
|
|
|
|
```
|
|
|
|
### 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.
|
|
|
|
* **Encoder**: Validates `serde_json::Value` against the input `MessageDescriptor` and serializes it.
|
|
* **Decoder**: Deserializes bytes into a `DynamicMessage` and converts it back to `serde_json::Value`.
|
|
|
|
### 3. `ReflectionClient`
|
|
|
|
A client for `grpc.reflection.v1`. It enables runtime schema discovery.
|
|
|
|
The `ReflectionClient` is smart enough to handle dependencies. When you ask for a symbol (e.g., `my.package.Service`),
|
|
it recursively fetches the file defining that symbol and **all** its transitive imports, building a complete `prost_types::FileDescriptorSet` ready for use. It also supports listing available services.
|
|
|
|
```rust
|
|
use granc_core::reflection::client::ReflectionClient;
|
|
|
|
let mut reflection = ReflectionClient::new(channel);
|
|
|
|
// List services
|
|
let services = reflection.list_services().await?;
|
|
|
|
// Fetch full schema for a symbol
|
|
let fd_set = reflection.file_descriptor_set_by_symbol("my.package.Service").await?;
|
|
|
|
```
|
|
|
|
You can then build a `prost_reflect::DescriptorPool` with the returned `prost_types::FileDescriptorSet` to be able to inspect in detail the descriptor.
|
|
|
|
## ⚖️ License
|
|
|
|
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
|