mirror of
https://codeberg.org/JasterV/granc.git
synced 2026-04-26 18:40:05 +00:00
This pull request introduces a significant internal refactor of the `granc` project, decoupling core dynamic gRPC client logic into a new reusable library crate (`granc-core`). It also improves project organization, updates documentation, and enhances workspace configuration. The main CLI functionality is now built atop this new core, making future maintenance and extensibility easier. **Project structure and workspace improvements:** - Created a new crate, `granc-core`, to encapsulate all core dynamic gRPC client logic, including schema resolution, dynamic request dispatch, and reflection support. This enables potential reuse outside the CLI and clarifies project boundaries. (`granc-core/Cargo.toml`, `granc-core/src/client.rs`, [[1]](diffhunk://#diff-dd6f7ed591a1bd2577444d0079c1f56851ef74e3b9df75a86ef4af76681435f6R1-R126) [[2]](diffhunk://#diff-ddab7585cf4c860c9922ed56471bccf5804da60f0ccb174158fd31b9b82457abR1-R46) [[3]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adR1-R186) - Updated workspace configuration in `Cargo.toml` to include `granc-core`, centralize dependency versions, and set workspace-wide package metadata for consistency. (`Cargo.toml`, [Cargo.tomlL2-R26](diffhunk://#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542L2-R26)) - Adjusted `echo-service` and other crates to use workspace-wide settings for edition and authors. (`echo-service/Cargo.toml`, [echo-service/Cargo.tomlL3-R3](diffhunk://#diff-e74eb8a3bebf341a9bee1cdcd5cd3a50e15998db5a9df9eaf9e7aec341287b1eL3-R3)) **Documentation :** - Added a detailed `README.md` for both the main project and the new `granc-core` library, providing clear installation, usage, and architecture guidance for users and contributors. (`README.md`, `granc-core/README.md`, [[1]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5R1-R172) [[2]](diffhunk://#diff-dd6f7ed591a1bd2577444d0079c1f56851ef74e3b9df75a86ef4af76681435f6R1-R126) **Build and tooling updates:** - Updated `Makefile.toml` to use workspace-wide test runs and renamed tasks/binaries for consistency with the new crate layout. (`Makefile.toml`, [[1]](diffhunk://#diff-9375fd04332c86472d7be397ef09428cb86babd8826880a5835bd1d1c1bdbc08L18-R18) [[2]](diffhunk://#diff-9375fd04332c86472d7be397ef09428cb86babd8826880a5835bd1d1c1bdbc08L45-R50) --- **Key changes:** **1. Core library extraction and refactor** - Moved dynamic gRPC client logic (including `GrancClient`, request/response types, and reflection handling) into a new `granc-core` crate, decoupling it from the CLI and preparing for independent publishing. [[1]](diffhunk://#diff-ddab7585cf4c860c9922ed56471bccf5804da60f0ccb174158fd31b9b82457abR1-R46) [[2]](diffhunk://#diff-46d757daaa6737f1a6247142e8abff1cb5079109e641c447e8a9793ea1f063adR1-R186) **2. Workspace and dependency management** - Updated the root `Cargo.toml` to add `granc-core` as a workspace member, centralize dependency versions, and set workspace-wide metadata fields (authors, edition, license, etc.). - Adjusted `echo-service` and new crates to inherit workspace settings for consistency. **3. Documentation** - Updated and added comprehensive `README.md` files for both the main project and the new core library, with installation, usage, and architecture sections. [[1]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5R1-R172) [[2]](diffhunk://#diff-dd6f7ed591a1bd2577444d0079c1f56851ef74e3b9df75a86ef4af76681435f6R1-R126) - Introduced a `CHANGELOG.md` to document project history and recent changes. **4. Build and CI tooling** - Updated test and generation commands in `Makefile.toml` to reflect the new workspace structure and binary names. [[1]](diffhunk://#diff-9375fd04332c86472d7be397ef09428cb86babd8826880a5835bd1d1c1bdbc08L18-R18) [[2]](diffhunk://#diff-9375fd04332c86472d7be397ef09428cb86babd8826880a5835bd1d1c1bdbc08L45-R50) **5. Housekeeping** - Removed outdated or redundant files as part of the refactor. [[1]](diffhunk://#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4edL1) [[2]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L1) This refactor lays the groundwork for improved maintainability, easier future development, and potential wider adoption of the dynamic gRPC client logic outside the CLI.
126 lines
4.6 KiB
Markdown
126 lines
4.6 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.
|
|
|
|
## 📦 Installation
|
|
|
|
Add this to your `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
granc_core = "0.2.3"
|
|
tokio = { version = "1", features = ["full"] }
|
|
serde_json = "1"
|
|
```
|
|
|
|
## 🚀 High-Level Usage
|
|
|
|
The primary entry point is the [`GrancClient`]. It acts as an orchestrator that:
|
|
|
|
1. Connects to a gRPC server.
|
|
2. Resolves the schema (either from a local file or via Server Reflection).
|
|
3. Determines the method type (Unary, Server Streaming, etc.).
|
|
4. Execute the request using JSON.
|
|
|
|
### Example: Making a Dynamic Call
|
|
|
|
```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(())
|
|
}
|
|
|
|
```
|
|
|
|
## 🛠️ 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.
|
|
|
|
```rust
|
|
use granc_core::reflection::client::ReflectionClient;
|
|
|
|
let mut reflection = ReflectionClient::new(channel);
|
|
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.
|