diff --git a/.cursor/rules/rust/features/protobuf-grpc.mdc b/.cursor/rules/rust/features/protobuf-grpc.mdc
new file mode 100644
index 0000000..3313599
--- /dev/null
+++ b/.cursor/rules/rust/features/protobuf-grpc.mdc
@@ -0,0 +1,1182 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# 🛜 RUST PROTOBUF & GRPC STANDARDS
+
+> **TL;DR:** Modern protobuf and gRPC patterns using prost/tonic 0.13+ with clean code generation, Inner data structures, MessageSanitizer trait, gRPC reflection, and simplified service implementations.
+
+## 🎯 PROTOBUF & GRPC FRAMEWORK REQUIREMENTS
+
+### Prost/Tonic Configuration
+- **Use prost/tonic latest versions** - Modern protobuf and gRPC implementation
+- **Clean code generation** - Organized pb module structure with proper imports
+- **Inner data structures** - Simplified, optional-free data structures for business logic
+- **MessageSanitizer trait** - Consistent data transformation patterns
+- **Simplified service methods** - Clean separation between gRPC and business logic
+
+## 📦 PROTOBUF & GRPC DEPENDENCIES
+
+```toml
+# Cargo.toml - Protobuf & gRPC dependencies
+[dependencies]
+# Core protobuf and gRPC
+prost = "0.13"
+prost-types = "0.13"
+tonic = { version = "0.13", features = ["gzip", "tls", "tls-roots", "compression"] }
+
+# Build-time dependencies
+[build-dependencies]
+prost-build = "0.13"
+tonic-build = { version = "0.13", features = ["prost"] }
+
+# Data structures and serialization
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+typed_builder = "0.18"
+
+# Error handling
+anyhow = "1.0"
+thiserror = "2.0"
+
+# Async runtime
+tokio = { version = "1.45", features = ["macros", "rt-multi-thread", "signal"] }
+tokio-stream = { version = "0.1", features = ["net"] }
+
+# Logging and tracing
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
+
+# Optional: Additional features
+tower = "0.4" # Middleware
+tower-http = { version = "0.5", features = ["trace", "cors"] }
+uuid = { version = "1.0", features = ["v4", "serde"] }
+chrono = { version = "0.4", features = ["serde"] }
+
+[dev-dependencies]
+tonic-health = "0.13" # Health check service
+tonic-reflection = "0.13" # gRPC reflection service
+```
+
+## 🏗️ PROTOBUF & GRPC ARCHITECTURE
+
+```mermaid
+graph TD
+ Proto["Proto Files"] --> Build["build.rs"]
+ Build --> Generate["Code Generation"]
+ Generate --> PbModule["src/pb/ Module"]
+
+ PbModule --> Generated["Generated Structs
(Foo, Bar, etc.)"]
+ PbModule --> Services["Generated Services
(GreeterService, etc.)"]
+
+ Generated --> Inner["Inner Structs
(FooInner, BarInner)"]
+ Generated --> Sanitizer["MessageSanitizer
Implementation"]
+
+ Inner --> Business["Business Logic
Methods"]
+ Services --> Trait["Service Trait
Implementation"]
+
+ Business --> Trait
+ Sanitizer --> Trait
+
+ Trait --> Server["gRPC Server"]
+
+ style Proto fill:#4da6ff,stroke:#0066cc,color:white
+ style Build fill:#4dbb5f,stroke:#36873f,color:white
+ style Inner fill:#ffa64d,stroke:#cc7a30,color:white
+ style Sanitizer fill:#d94dbb,stroke:#a3378a,color:white
+```
+
+## 🚀 BUILD CONFIGURATION
+
+### build.rs Setup
+
+```rust
+// build.rs
+use std::path::PathBuf;
+
+fn main() -> Result<(), Box> {
+ let pb_dir = PathBuf::from("src/pb");
+
+ // Ensure pb directory exists
+ if !pb_dir.exists() {
+ std::fs::create_dir_all(&pb_dir)?;
+ }
+
+ // Configure tonic-build with prost_types
+ let mut tonic_build = tonic_build::configure()
+ .out_dir(&pb_dir)
+ .format(true) // Enable code formatting with prettyplease
+ .build_server(true)
+ .build_client(true)
+ .build_transport(true) // Include transport utilities
+ .emit_rerun_if_changed(false) // We handle this manually
+ // Use prost_types instead of compile_well_known_types
+ .extern_path(".google.protobuf.Timestamp", "::prost_types::Timestamp")
+ .extern_path(".google.protobuf.Duration", "::prost_types::Duration")
+ .extern_path(".google.protobuf.Empty", "::prost_types::Empty")
+ .extern_path(".google.protobuf.Any", "::prost_types::Any")
+ .extern_path(".google.protobuf.Struct", "::prost_types::Struct")
+ .extern_path(".google.protobuf.Value", "::prost_types::Value");
+
+ // Compile proto files
+ let proto_files = [
+ "proto/greeting.proto",
+ "proto/user.proto",
+ "proto/common.proto",
+ ];
+
+ // Generate file descriptor set for reflection
+ tonic_build
+ .file_descriptor_set_path(&pb_dir.join("greeter_descriptor.bin"))
+ .compile(&proto_files, &["proto"])?;
+
+ // Generate mod.rs file
+ generate_mod_file(&pb_dir)?;
+
+ // Rename generated files for better organization
+ rename_generated_files(&pb_dir)?;
+
+ // Emit rerun-if-changed directives
+ println!("cargo:rerun-if-changed=proto/");
+ println!("cargo:rerun-if-changed=build.rs");
+
+ // Emit rerun-if-env-changed for protoc
+ println!("cargo:rerun-if-env-changed=PROTOC");
+ println!("cargo:rerun-if-env-changed=PROTOC_INCLUDE");
+
+ Ok(())
+}
+
+fn generate_mod_file(pb_dir: &PathBuf) -> Result<(), Box> {
+ let mut mod_content = String::new();
+ mod_content.push_str("// Auto-generated module file\n");
+ mod_content.push_str("// DO NOT EDIT MANUALLY\n\n");
+
+ // Add file descriptor set for reflection
+ mod_content.push_str("/// File descriptor set for gRPC reflection\n");
+ mod_content.push_str("pub const GREETER_FILE_DESCRIPTOR_SET: &[u8] = include_bytes!(\"greeter_descriptor.bin\");\n\n");
+
+ // Scan for generated .rs files
+ for entry in std::fs::read_dir(pb_dir)? {
+ let entry = entry?;
+ let path = entry.path();
+
+ if let Some(extension) = path.extension() {
+ if extension == "rs" {
+ if let Some(file_stem) = path.file_stem() {
+ let module_name = file_stem.to_string_lossy();
+ if module_name != "mod" {
+ mod_content.push_str(&format!("pub mod {};\n", module_name));
+ }
+ }
+ }
+ }
+ }
+
+ // Write mod.rs file
+ let mod_file_path = pb_dir.join("mod.rs");
+ std::fs::write(mod_file_path, mod_content)?;
+
+ Ok(())
+}
+
+fn rename_generated_files(pb_dir: &PathBuf) -> Result<(), Box> {
+ // Rename files like "a.b.rs" to "b.rs" or "a/b.rs" based on naming conflicts
+ for entry in std::fs::read_dir(pb_dir)? {
+ let entry = entry?;
+ let path = entry.path();
+
+ if let Some(file_name) = path.file_name() {
+ let file_name_str = file_name.to_string_lossy();
+
+ // Check if file has package prefix (contains dots)
+ if file_name_str.contains('.') && file_name_str.ends_with(".rs") {
+ let parts: Vec<&str> = file_name_str
+ .strip_suffix(".rs")
+ .unwrap()
+ .split('.')
+ .collect();
+
+ if parts.len() > 1 {
+ // Use the last part as the new file name
+ let new_name = format!("{}.rs", parts.last().unwrap());
+ let new_path = pb_dir.join(&new_name);
+
+ // Check for conflicts
+ if !new_path.exists() {
+ std::fs::rename(&path, &new_path)?;
+ println!("Renamed {} to {}", file_name_str, new_name);
+ } else {
+ // Create subdirectory structure
+ let package_name = parts[0];
+ let package_dir = pb_dir.join(package_name);
+ std::fs::create_dir_all(&package_dir)?;
+
+ let new_path = package_dir.join(&new_name);
+ std::fs::rename(&path, &new_path)?;
+ println!("Moved {} to {}/{}", file_name_str, package_name, new_name);
+ }
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
+```
+
+### Project Structure
+
+```
+my-grpc-service/
+├── proto/ # Protocol buffer definitions
+│ ├── common.proto # Common types and enums
+│ ├── greeting.proto # Greeting service definition
+│ └── user.proto # User service definition
+├── src/
+│ ├── pb/ # Generated protobuf code
+│ │ ├── mod.rs # Auto-generated module file
+│ │ ├── common.rs # Generated from common.proto
+│ │ ├── greeting.rs # Generated from greeting.proto
+│ │ └── user.rs # Generated from user.proto
+│ ├── inner/ # Inner data structures
+│ │ ├── mod.rs
+│ │ ├── common.rs # CommonInner types
+│ │ ├── greeting.rs # GreetingInner types
+│ │ └── user.rs # UserInner types
+│ ├── services/ # Service implementations
+│ │ ├── mod.rs
+│ │ ├── greeting.rs # Greeting service impl
+│ │ └── user.rs # User service impl
+│ ├── sanitizers/ # MessageSanitizer implementations
+│ │ ├── mod.rs
+│ │ └── mod_common.rs
+│ ├── lib.rs # Library root
+│ └── main.rs # Server binary
+├── build.rs # Build script
+└── Cargo.toml
+```
+
+## 🏛️ CORE TRAITS AND PATTERNS
+
+### MessageSanitizer Trait
+
+```rust
+// src/sanitizers/mod.rs
+use crate::inner;
+
+/// Trait for sanitizing protobuf messages into clean Inner types
+pub trait MessageSanitizer {
+ type Output;
+
+ /// Convert protobuf message to clean Inner type with proper defaults
+ fn sanitize(self) -> Self::Output;
+}
+
+// Use standard From trait instead of custom ToProtobuf trait
+
+// Common sanitization utilities for complex types only
+
+pub fn sanitize_timestamp(opt: Option) -> chrono::DateTime {
+ opt.map(|ts| {
+ chrono::DateTime::from_timestamp(ts.seconds, ts.nanos as u32)
+ .unwrap_or_default()
+ })
+ .unwrap_or_else(chrono::Utc::now)
+}
+```
+
+### Example Proto Definition
+
+```protobuf
+// proto/greeting.proto
+syntax = "proto3";
+
+package greeting.v1;
+
+import "google/protobuf/timestamp.proto";
+import "common.proto";
+
+// Greeting service definition
+service GreeterService {
+ rpc SayHello(HelloRequest) returns (HelloReply);
+ rpc SayHelloStream(HelloRequest) returns (stream HelloReply);
+ rpc GetUserGreeting(UserGreetingRequest) returns (UserGreetingReply);
+}
+
+// Request message for saying hello
+message HelloRequest {
+ string name = 1;
+ optional string language = 2;
+ optional common.v1.UserContext user_context = 3;
+ optional google.protobuf.Timestamp request_time = 4;
+}
+
+// Reply message for hello
+message HelloReply {
+ string message = 1;
+ string language = 2;
+ optional google.protobuf.Timestamp reply_time = 3;
+ optional common.v1.ServerInfo server_info = 4;
+}
+
+message UserGreetingRequest {
+ string user_id = 1;
+ optional string custom_message = 2;
+}
+
+message UserGreetingReply {
+ string greeting = 1;
+ optional common.v1.UserProfile user_profile = 2;
+}
+```
+
+```protobuf
+// proto/common.proto
+syntax = "proto3";
+
+package common.v1;
+
+message UserContext {
+ string user_id = 1;
+ string session_id = 2;
+ repeated string roles = 3;
+}
+
+message ServerInfo {
+ string version = 1;
+ string environment = 2;
+ string instance_id = 3;
+}
+
+message UserProfile {
+ string id = 1;
+ string name = 2;
+ string email = 3;
+ bool is_active = 4;
+}
+```
+
+## 📊 INNER DATA STRUCTURES
+
+### Inner Types Implementation
+
+```rust
+// src/inner/common.rs
+use serde::{Deserialize, Serialize};
+use typed_builder::TypedBuilder;
+use uuid::Uuid;
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
+pub struct UserContextInner {
+ #[builder(default, setter(into))]
+ pub user_id: String,
+ #[builder(default, setter(into))]
+ pub session_id: String,
+ #[builder(default)]
+ pub roles: Vec,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
+pub struct ServerInfoInner {
+ #[builder(default = "1.0.0".to_string(), setter(into))]
+ pub version: String,
+ #[builder(default = "development".to_string(), setter(into))]
+ pub environment: String,
+ #[builder(default_code = "Uuid::new_v4().to_string()", setter(into))]
+ pub instance_id: String,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
+pub struct UserProfileInner {
+ #[builder(default, setter(into))]
+ pub id: String,
+ #[builder(default, setter(into))]
+ pub name: String,
+ #[builder(default, setter(into))]
+ pub email: String,
+ #[builder(default = true)]
+ pub is_active: bool,
+}
+```
+
+```rust
+// src/inner/greeting.rs
+use serde::{Deserialize, Serialize};
+use typed_builder::TypedBuilder;
+use chrono::{DateTime, Utc};
+use super::common::{UserContextInner, ServerInfoInner, UserProfileInner};
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
+pub struct HelloRequestInner {
+ #[builder(setter(into))]
+ pub name: String,
+ #[builder(default = "en".to_string(), setter(into))]
+ pub language: String,
+ #[builder(default, setter(strip_option))]
+ pub user_context: Option,
+ #[builder(default_code = "Utc::now()")]
+ pub request_time: DateTime,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
+pub struct HelloReplyInner {
+ #[builder(default, setter(into))]
+ pub message: String,
+ #[builder(default = "en".to_string(), setter(into))]
+ pub language: String,
+ #[builder(default_code = "Utc::now()")]
+ pub reply_time: DateTime,
+ #[builder(default, setter(strip_option))]
+ pub server_info: Option,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
+pub struct UserGreetingRequestInner {
+ #[builder(default, setter(into))]
+ pub user_id: String,
+ #[builder(default, setter(into))]
+ pub custom_message: String,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
+pub struct UserGreetingReplyInner {
+ #[builder(default, setter(into))]
+ pub greeting: String,
+ #[builder(default)]
+ pub user_profile: UserProfileInner,
+}
+```
+
+## 🔄 MESSAGE SANITIZER IMPLEMENTATIONS
+
+```rust
+// src/sanitizers/greeting.rs
+use crate::{
+ pb::greeting::*,
+ inner::{greeting::*, common::*},
+ sanitizers::{MessageSanitizer, sanitize_timestamp}
+};
+use chrono::{DateTime, Utc};
+
+impl MessageSanitizer for HelloRequest {
+ type Output = HelloRequestInner;
+
+ fn sanitize(self) -> Self::Output {
+ HelloRequestInner::builder()
+ .name(self.name)
+ .language(self.language.unwrap_or_default())
+ .user_context(self.user_context.map(|ctx| ctx.sanitize()))
+ .request_time(
+ self.request_time
+ .map(|ts| {
+ DateTime::from_timestamp(ts.seconds, ts.nanos as u32)
+ .unwrap_or_else(Utc::now)
+ })
+ .unwrap_or_else(Utc::now)
+ )
+ .build()
+ }
+}
+
+impl From for HelloRequest {
+ fn from(inner: HelloRequestInner) -> Self {
+ Self {
+ name: inner.name,
+ language: if inner.language.is_empty() { None } else { Some(inner.language) },
+ user_context: inner.user_context.map(|ctx| ctx.into()),
+ request_time: Some(prost_types::Timestamp {
+ seconds: inner.request_time.timestamp(),
+ nanos: inner.request_time.timestamp_subsec_nanos() as i32,
+ }),
+ }
+ }
+}
+
+impl MessageSanitizer for HelloReply {
+ type Output = HelloReplyInner;
+
+ fn sanitize(self) -> Self::Output {
+ HelloReplyInner::builder()
+ .message(self.message)
+ .language(self.language)
+ .reply_time(
+ self.reply_time
+ .map(|ts| {
+ DateTime::from_timestamp(ts.seconds, ts.nanos as u32)
+ .unwrap_or_else(Utc::now)
+ })
+ .unwrap_or_else(Utc::now)
+ )
+ .server_info(self.server_info.map(|info| info.sanitize()))
+ .build()
+ }
+}
+
+impl From for HelloReply {
+ fn from(inner: HelloReplyInner) -> Self {
+ Self {
+ message: inner.message,
+ language: inner.language,
+ reply_time: Some(prost_types::Timestamp {
+ seconds: inner.reply_time.timestamp(),
+ nanos: inner.reply_time.timestamp_subsec_nanos() as i32,
+ }),
+ server_info: inner.server_info.map(|info| info.into()),
+ }
+ }
+}
+
+impl MessageSanitizer for UserGreetingRequest {
+ type Output = UserGreetingRequestInner;
+
+ fn sanitize(self) -> Self::Output {
+ UserGreetingRequestInner::builder()
+ .user_id(self.user_id)
+ .custom_message(self.custom_message.unwrap_or_default())
+ .build()
+ }
+}
+
+impl From for UserGreetingRequest {
+ fn from(inner: UserGreetingRequestInner) -> Self {
+ Self {
+ user_id: inner.user_id,
+ custom_message: if inner.custom_message.is_empty() {
+ None
+ } else {
+ Some(inner.custom_message)
+ },
+ }
+ }
+}
+
+impl MessageSanitizer for UserGreetingReply {
+ type Output = UserGreetingReplyInner;
+
+ fn sanitize(self) -> Self::Output {
+ UserGreetingReplyInner::builder()
+ .greeting(self.greeting)
+ .user_profile(
+ self.user_profile
+ .map(|profile| profile.sanitize())
+ .unwrap_or_default()
+ )
+ .build()
+ }
+}
+
+impl From for UserGreetingReply {
+ fn from(inner: UserGreetingReplyInner) -> Self {
+ Self {
+ greeting: inner.greeting,
+ user_profile: Some(inner.user_profile.into()),
+ }
+ }
+}
+```
+
+```rust
+// src/sanitizers/common.rs
+use crate::{
+ pb::common::*,
+ inner::common::*,
+ sanitizers::MessageSanitizer
+};
+
+impl MessageSanitizer for UserContext {
+ type Output = UserContextInner;
+
+ fn sanitize(self) -> Self::Output {
+ UserContextInner::builder()
+ .user_id(self.user_id)
+ .session_id(self.session_id)
+ .roles(self.roles)
+ .build()
+ }
+}
+
+impl From for UserContext {
+ fn from(inner: UserContextInner) -> Self {
+ Self {
+ user_id: inner.user_id,
+ session_id: inner.session_id,
+ roles: inner.roles,
+ }
+ }
+}
+
+impl MessageSanitizer for ServerInfo {
+ type Output = ServerInfoInner;
+
+ fn sanitize(self) -> Self::Output {
+ ServerInfoInner::builder()
+ .version(self.version)
+ .environment(self.environment)
+ .instance_id(self.instance_id)
+ .build()
+ }
+}
+
+impl From for ServerInfo {
+ fn from(inner: ServerInfoInner) -> Self {
+ Self {
+ version: inner.version,
+ environment: inner.environment,
+ instance_id: inner.instance_id,
+ }
+ }
+}
+
+impl MessageSanitizer for UserProfile {
+ type Output = UserProfileInner;
+
+ fn sanitize(self) -> Self::Output {
+ UserProfileInner::builder()
+ .id(self.id)
+ .name(self.name)
+ .email(self.email)
+ .is_active(self.is_active)
+ .build()
+ }
+}
+
+impl From for UserProfile {
+ fn from(inner: UserProfileInner) -> Self {
+ Self {
+ id: inner.id,
+ name: inner.name,
+ email: inner.email,
+ is_active: inner.is_active,
+ }
+ }
+}
+```
+
+## 🛠️ SERVICE IMPLEMENTATION PATTERN
+
+### Business Logic Implementation
+
+```rust
+// src/services/greeting.rs
+use anyhow::Result;
+use tracing::{info, instrument};
+use crate::inner::{greeting::*, common::*};
+
+pub struct GreeterService {
+ server_info: ServerInfoInner,
+}
+
+impl GreeterService {
+ pub fn new() -> Self {
+ Self {
+ server_info: ServerInfoInner::builder()
+ .version(env!("CARGO_PKG_VERSION").to_string())
+ .environment(std::env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()))
+ .build(),
+ }
+ }
+
+ /// Business logic for saying hello - clean and testable
+ #[instrument(skip(self))]
+ pub async fn say_hello_inner(
+ &self,
+ request: HelloRequestInner,
+ ) -> Result {
+ info!("Processing hello request for user: {}", request.name);
+
+ let greeting = match request.language.as_str() {
+ "es" => format!("¡Hola, {}!", request.name),
+ "fr" => format!("Bonjour, {}!", request.name),
+ "de" => format!("Hallo, {}!", request.name),
+ "ja" => format!("こんにちは、{}さん!", request.name),
+ _ => format!("Hello, {}!", request.name),
+ };
+
+ let reply = HelloReplyInner::builder()
+ .message(greeting)
+ .language(request.language.clone())
+ .server_info(Some(self.server_info.clone()))
+ .build();
+
+ Ok(reply)
+ }
+
+ /// Business logic for user-specific greeting
+ #[instrument(skip(self))]
+ pub async fn get_user_greeting_inner(
+ &self,
+ request: UserGreetingRequestInner,
+ ) -> Result {
+ info!("Processing user greeting request for user: {}", request.user_id);
+
+ // Simulate user lookup (in real app, this would be a database call)
+ let user_profile = UserProfileInner::builder()
+ .id(request.user_id.clone())
+ .name(format!("User_{}", request.user_id))
+ .email(format!("user_{}@example.com", request.user_id))
+ .is_active(true)
+ .build();
+
+ let greeting = if !request.custom_message.is_empty() {
+ format!("{}, {}!", request.custom_message, user_profile.name)
+ } else {
+ format!("Welcome back, {}!", user_profile.name)
+ };
+
+ let reply = UserGreetingReplyInner::builder()
+ .greeting(greeting)
+ .user_profile(user_profile)
+ .build();
+
+ Ok(reply)
+ }
+}
+
+impl Default for GreeterService {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+```
+
+### gRPC Service Trait Implementation
+
+```rust
+// src/services/greeting.rs (continued)
+use tonic::{Request, Response, Status, Code};
+use tokio_stream::{wrappers::ReceiverStream, StreamExt};
+use crate::{
+ pb::greeting::{
+ greeter_service_server::GreeterService as GreeterServiceTrait,
+ HelloRequest, HelloReply, UserGreetingRequest, UserGreetingReply
+ },
+ sanitizers::{MessageSanitizer, ToProtobuf}
+};
+
+#[tonic::async_trait]
+impl GreeterServiceTrait for GreeterService {
+ /// gRPC trait implementation - thin wrapper around business logic
+ async fn say_hello(
+ &self,
+ request: Request,
+ ) -> Result, Status> {
+ let remote_addr = request.remote_addr();
+ let request_inner = request.into_inner().sanitize();
+
+ match self.say_hello_inner(request_inner).await {
+ Ok(reply_inner) => {
+ info!("Successful hello response to {:?}", remote_addr);
+ let reply = reply_inner.into();
+ Ok(Response::new(reply))
+ }
+ Err(e) => {
+ tracing::error!("Error processing hello request: {}", e);
+ Err(Status::new(Code::Internal, "Internal server error"))
+ }
+ }
+ }
+
+ /// Streaming response example
+ type SayHelloStreamStream = ReceiverStream>;
+
+ async fn say_hello_stream(
+ &self,
+ request: Request,
+ ) -> Result, Status> {
+ let request_inner = request.into_inner().sanitize();
+ let (tx, rx) = tokio::sync::mpsc::channel(128);
+
+ // Clone necessary data for the async task
+ let service = self.clone();
+
+ tokio::spawn(async move {
+ for i in 0..5 {
+ let mut req = request_inner.clone();
+ req.name = format!("{} ({})", req.name, i + 1);
+
+ match service.say_hello_inner(req).await {
+ Ok(reply_inner) => {
+ let reply = reply_inner.into();
+ if tx.send(Ok(reply)).await.is_err() {
+ break; // Client disconnected
+ }
+ }
+ Err(e) => {
+ let _ = tx.send(Err(Status::internal(format!("Error: {}", e)))).await;
+ break;
+ }
+ }
+
+ tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
+ }
+ });
+
+ Ok(Response::new(ReceiverStream::new(rx)))
+ }
+
+ async fn get_user_greeting(
+ &self,
+ request: Request,
+ ) -> Result, Status> {
+ let request_inner = request.into_inner().sanitize();
+
+ match self.get_user_greeting_inner(request_inner).await {
+ Ok(reply_inner) => {
+ let reply = reply_inner.into();
+ Ok(Response::new(reply))
+ }
+ Err(e) => {
+ tracing::error!("Error processing user greeting request: {}", e);
+ Err(Status::new(Code::Internal, "Internal server error"))
+ }
+ }
+ }
+}
+
+// Make service cloneable for streaming
+impl Clone for GreeterService {
+ fn clone(&self) -> Self {
+ Self {
+ server_info: self.server_info.clone(),
+ }
+ }
+}
+```
+
+## 🏁 SERVER SETUP
+
+### Main Server Implementation
+
+```rust
+// src/main.rs
+use anyhow::Result;
+use tonic::transport::Server;
+use tonic_health::server::health_reporter;
+use tonic_reflection::server::ServerReflectionServer;
+use tower_http::trace::TraceLayer;
+use tracing::{info, level_filters::LevelFilter};
+use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
+
+mod pb;
+mod inner;
+mod sanitizers;
+mod services;
+
+use pb::greeting::greeter_service_server::GreeterServiceServer;
+use services::greeting::GreeterService;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ // Initialize tracing with structured logging
+ tracing_subscriber::registry()
+ .with(
+ EnvFilter::try_from_default_env()
+ .unwrap_or_else(|_| EnvFilter::new("info")),
+ )
+ .with(
+ tracing_subscriber::fmt::layer()
+ .with_target(true)
+ .with_thread_ids(true)
+ .with_file(true)
+ .with_line_number(true)
+ )
+ .init();
+
+ let addr = "127.0.0.1:50051".parse()?;
+ info!("Starting gRPC server on {}", addr);
+
+ // Create health reporter
+ let (mut health_reporter, health_service) = health_reporter();
+
+ // Create reflection service for service discovery
+ let reflection_service = ServerReflectionServer::configure()
+ .register_encoded_file_descriptor_set(pb::GREETER_FILE_DESCRIPTOR_SET)
+ .build()
+ .unwrap();
+
+ // Set service as serving
+ health_reporter
+ .set_serving::>()
+ .await;
+
+ // Create services
+ let greeter_service = GreeterService::new();
+
+ // Start server with enhanced configuration
+ Server::builder()
+ .layer(TraceLayer::new_for_grpc())
+ .timeout(std::time::Duration::from_secs(30))
+ .concurrency_limit_per_connection(256)
+ .tcp_keepalive(Some(std::time::Duration::from_secs(60)))
+ .add_service(health_service)
+ .add_service(reflection_service)
+ .add_service(GreeterServiceServer::new(greeter_service))
+ .serve_with_shutdown(addr, shutdown_signal())
+ .await?;
+
+ Ok(())
+}
+
+async fn shutdown_signal() {
+ let ctrl_c = async {
+ tokio::signal::ctrl_c()
+ .await
+ .expect("failed to install Ctrl+C handler");
+ };
+
+ #[cfg(unix)]
+ let terminate = async {
+ tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
+ .expect("failed to install signal handler")
+ .recv()
+ .await;
+ };
+
+ #[cfg(not(unix))]
+ let terminate = std::future::pending::<()>();
+
+ tokio::select! {
+ _ = ctrl_c => {},
+ _ = terminate => {},
+ }
+
+ info!("Signal received, starting graceful shutdown");
+}
+```
+
+### Library Root
+
+```rust
+// src/lib.rs
+pub mod pb;
+pub mod inner;
+pub mod sanitizers;
+pub mod services;
+
+// Re-export commonly used types
+pub use inner::*;
+pub use sanitizers::MessageSanitizer;
+pub use services::*;
+
+// Health check endpoint for service discovery
+pub async fn health_check() -> Result<(), Box> {
+ // Basic health check logic
+ Ok(())
+}
+```
+
+## 🧪 TESTING PATTERNS
+
+### Unit Tests for Business Logic
+
+```rust
+// src/services/greeting.rs (test module)
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::inner::{greeting::*, common::*};
+
+ #[tokio::test]
+ async fn test_say_hello_english() {
+ let service = GreeterService::new();
+ let request = HelloRequestInner::builder()
+ .name("World".to_string())
+ .language("en".to_string())
+ .build();
+
+ let result = service.say_hello_inner(request).await.unwrap();
+
+ assert_eq!(result.message, "Hello, World!");
+ assert_eq!(result.language, "en");
+ assert!(result.server_info.is_some());
+ }
+
+ #[tokio::test]
+ async fn test_say_hello_spanish() {
+ let service = GreeterService::new();
+ let request = HelloRequestInner::builder()
+ .name("Mundo".to_string())
+ .language("es".to_string())
+ .build();
+
+ let result = service.say_hello_inner(request).await.unwrap();
+
+ assert_eq!(result.message, "¡Hola, Mundo!");
+ assert_eq!(result.language, "es");
+ }
+
+ #[tokio::test]
+ async fn test_get_user_greeting_default() {
+ let service = GreeterService::new();
+ let request = UserGreetingRequestInner::builder()
+ .user_id("123".to_string())
+ .build();
+
+ let result = service.get_user_greeting_inner(request).await.unwrap();
+
+ assert_eq!(result.greeting, "Welcome back, User_123!");
+ assert_eq!(result.user_profile.id, "123");
+ assert_eq!(result.user_profile.email, "user_123@example.com");
+ assert!(result.user_profile.is_active);
+ }
+
+ #[tokio::test]
+ async fn test_get_user_greeting_custom() {
+ let service = GreeterService::new();
+ let request = UserGreetingRequestInner::builder()
+ .user_id("456".to_string())
+ .custom_message("Good morning".to_string())
+ .build();
+
+ let result = service.get_user_greeting_inner(request).await.unwrap();
+
+ assert_eq!(result.greeting, "Good morning, User_456!");
+ }
+}
+```
+
+### Integration Tests for gRPC Service
+
+```rust
+// tests/integration_test.rs
+use anyhow::Result;
+use tonic::Request;
+use my_grpc_service::{
+ pb::greeting::{
+ greeter_service_server::GreeterServiceServer,
+ greeter_service_client::GreeterServiceClient,
+ HelloRequest
+ },
+ services::greeting::GreeterService
+};
+
+#[tokio::test]
+async fn test_grpc_say_hello() -> Result<()> {
+ // Start test server
+ let (client, _server) = setup_test_server().await?;
+
+ // Make request
+ let request = Request::new(HelloRequest {
+ name: "Integration Test".to_string(),
+ language: Some("en".to_string()),
+ user_context: None,
+ request_time: Some(prost_types::Timestamp {
+ seconds: chrono::Utc::now().timestamp(),
+ nanos: 0,
+ }),
+ });
+
+ let response = client.say_hello(request).await?;
+ let reply = response.into_inner();
+
+ assert_eq!(reply.message, "Hello, Integration Test!");
+ assert_eq!(reply.language, "en");
+
+ Ok(())
+}
+
+async fn setup_test_server() -> Result<(GreeterServiceClient, tokio::task::JoinHandle<()>)> {
+ use tonic::transport::{Server, Channel, Endpoint};
+ use std::net::SocketAddr;
+
+ let addr: SocketAddr = "127.0.0.1:0".parse()?;
+ let greeter_service = GreeterService::new();
+
+ let listener = tokio::net::TcpListener::bind(addr).await?;
+ let addr = listener.local_addr()?;
+
+ let server_handle = tokio::spawn(async move {
+ Server::builder()
+ .timeout(std::time::Duration::from_secs(10))
+ .add_service(GreeterServiceServer::new(greeter_service))
+ .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
+ .await
+ .unwrap();
+ });
+
+ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+
+ // Create channel with connection configuration
+ let channel = Endpoint::from_shared(format!("http://{}", addr))?
+ .timeout(std::time::Duration::from_secs(5))
+ .connect_timeout(std::time::Duration::from_secs(5))
+ .connect()
+ .await?;
+ let client = GreeterServiceClient::new(channel);
+
+ Ok((client, server_handle))
+}
+```
+
+## 📝 PROTOBUF & GRPC BEST PRACTICES CHECKLIST
+
+```markdown
+## Protobuf & gRPC Implementation Verification
+
+### Code Generation
+- [ ] Uses prost/tonic 0.13+ versions
+- [ ] Generated code placed in src/pb/ directory
+- [ ] build.rs includes format(true) with prettyplease
+- [ ] File descriptor set generated for reflection
+- [ ] Auto-generated mod.rs file references all modules
+- [ ] Files renamed from package.name.rs to name.rs format
+- [ ] No naming conflicts in generated files
+- [ ] Proto3 optional fields properly configured
+
+### Inner Data Structures
+- [ ] Inner structs created for all protobuf messages
+- [ ] Inner structs use TypedBuilder for construction
+- [ ] Inner structs include proper derives (Debug, Clone, Default, Serialize, Deserialize)
+- [ ] Inner structs remove unnecessary Option wrappers
+- [ ] Inner structs use appropriate default values
+
+### MessageSanitizer Implementation
+- [ ] All protobuf messages implement MessageSanitizer trait
+- [ ] Sanitization handles Option fields properly
+- [ ] Default values provided for missing fields
+- [ ] Timestamp conversion handled correctly
+- [ ] ToProtobuf trait implemented for reverse conversion
+
+### Service Implementation
+- [ ] Business logic methods use Inner types only
+- [ ] Business logic methods are easily testable
+- [ ] gRPC trait implementation is thin wrapper
+- [ ] Error handling converts business errors to gRPC Status
+- [ ] Streaming responses handled properly
+- [ ] Request/response logging implemented
+
+### Project Structure
+- [ ] Clear separation: pb/, inner/, sanitizers/, services/
+- [ ] Module organization follows domain boundaries
+- [ ] build.rs handles code generation properly
+- [ ] Proto files organized by service/domain
+- [ ] Common types extracted to shared proto files
+
+### Testing
+- [ ] Unit tests for business logic methods
+- [ ] Integration tests for gRPC endpoints
+- [ ] Tests use Inner types for simple construction
+- [ ] Test server setup for integration testing
+- [ ] Error cases covered in tests
+
+### Performance & Reliability
+- [ ] Connection pooling for clients (if needed)
+- [ ] Proper timeout and keepalive configuration
+- [ ] Health check service implemented
+- [ ] gRPC reflection service enabled
+- [ ] Concurrency limits configured
+- [ ] Graceful shutdown handling
+- [ ] Structured tracing and logging configured
+- [ ] Compression enabled (gzip/deflate/zstd)
+
+### Security
+- [ ] Input validation in business logic
+- [ ] Authentication/authorization patterns
+- [ ] TLS configuration for production
+- [ ] Rate limiting considerations
+- [ ] Error messages don't leak sensitive data
+```
+
+This comprehensive protobuf and gRPC standard ensures clean, testable, and maintainable code following modern Rust patterns with prost/tonic.
diff --git a/.cursor/rules/rust/main.mdc b/.cursor/rules/rust/main.mdc
index a725d6c..60b17b0 100644
--- a/.cursor/rules/rust/main.mdc
+++ b/.cursor/rules/rust/main.mdc
@@ -35,12 +35,14 @@ graph TD
Features --> Web{"Web Framework
needed?"}
Features --> DB{"Database
access needed?"}
Features --> CLI{"CLI interface
needed?"}
+ Features --> GRPC{"gRPC/Protobuf
needed?"}
Features --> Concurrent{"Heavy
concurrency?"}
Features --> Config{"Complex config
or templating?"}
Web -->|Yes| WebRules["Load Axum Rules"]
DB -->|Yes| DBRules["Load Database Rules"]
CLI -->|Yes| CLIRules["Load CLI Rules"]
+ GRPC -->|Yes| GRPCRules["Load Protobuf & gRPC Rules"]
Concurrent -->|Yes| ConcurrencyRules["Load Concurrency Rules"]
Config -->|Yes| ToolsRules["Load Tools & Config Rules"]
@@ -90,12 +92,14 @@ graph TD
FeatureDetection --> WebFeature{"Web Framework?"}
FeatureDetection --> DBFeature{"Database?"}
FeatureDetection --> CLIFeature{"CLI Interface?"}
+ FeatureDetection --> GRPCFeature{"gRPC/Protobuf?"}
FeatureDetection --> SerdeFeature{"Serialization?"}
FeatureDetection --> BuilderFeature{"Complex Types?"}
WebFeature -->|Yes| AxumRules["Axum Framework Rules"]
DBFeature -->|Yes| SQLxRules["SQLx Database Rules"]
CLIFeature -->|Yes| CLIRules["CLI Application Rules"]
+ GRPCFeature -->|Yes| GRPCRules["Protobuf & gRPC Rules"]
SerdeFeature -->|Yes| SerdeRules["Serde Best Practices"]
BuilderFeature -->|Yes| TypedBuilderRules["TypedBuilder Rules"]
@@ -206,6 +210,14 @@ sequenceDiagram
- [ ] Configuration file support
- [ ] → Load CLI rules if YES to any
+### Protobuf & gRPC Requirements
+- [ ] Protocol buffers for data serialization
+- [ ] gRPC service implementation needed
+- [ ] Inter-service communication required
+- [ ] Schema evolution support needed
+- [ ] High-performance RPC required
+- [ ] → Load Protobuf & gRPC rules if YES to any
+
### HTTP Client Requirements
- [ ] External API integration
- [ ] HTTP requests needed
@@ -230,6 +242,7 @@ Based on project analysis, load specific rule sets:
# Web: core + axum + serde + utilities (JWT)
# Database: core + sqlx + utilities (error handling)
# CLI: core + cli + utilities (enum_dispatch + error handling)
+# gRPC: core + protobuf-grpc + utilities (typed_builder + sanitization)
# Auth: core + utilities (JWT + validation)
```
@@ -243,6 +256,7 @@ Based on project analysis, load specific rule sets:
| **Web** | `features/axum.mdc` | Axum 0.8 patterns, OpenAPI with utoipa |
| **Database** | `features/database.mdc` | SQLx patterns, repository design, testing |
| **CLI** | `features/cli.mdc` | Clap 4.0+ patterns, subcommands, enum_dispatch |
+| **Protobuf & gRPC** | `features/protobuf-grpc.mdc` | Prost/Tonic 0.13+, Inner types, MessageSanitizer, reflection |
| **Concurrency** | `features/concurrency.mdc` | Tokio, DashMap, async patterns |
| **Tools & Config** | `features/tools-and-config.mdc` | Tracing, YAML config, MiniJinja templates |
| **Utilities** | `features/utilities.mdc` | JWT auth, CLI tools, builders, enhanced derives |
@@ -261,6 +275,7 @@ Based on project analysis, load specific rule sets:
### Complex Project Examples
- **Workflow engines** (multi-node processing systems)
- **Multi-service applications** (auth + business + gateway)
+- **Microservices with gRPC** (inter-service communication)
- **Enterprise applications** (multiple domains)
- **Database systems** with multiple engines
- **Distributed systems** with event processing
diff --git a/specs/instructions.md b/specs/instructions.md
index c1b8bf8..146106d 100644
--- a/specs/instructions.md
+++ b/specs/instructions.md
@@ -1,5 +1,99 @@
# Instructions
+请仔细阅读 @/isolation_rules ,根据它的 rule set 体系,比如在不同的场景或条件下加载不同的 rule,通过 mermaid chart 来指导如何使用 rules 等等。请根据类似的思路帮我处理和扩充以下规则,构建一套处理大型 rust 项目的 rule set:
+
+1. 如果项目规模不大,则使用单 crate;如果项目复杂,则拆分成多个 crate,使用 workspace 管理,每个 crate 都放入 workspace 的 dependency 中
+2. crate 内部要有合理的文件设置,以功能而非类型划分文件,比如: lib.rs, models.rs, handlers.rs,而非 lib.rs, types.rs, traits.rs, impl.rs。每个文件除去 unit test 的代码行数不超过 500 行,否则将其转换成目录,然后在目录下拆分成多个文件。
+3. 每个函数要遵循 DRY / SRP,函数大小不超过 150 行。
+4. 每个 crate 集中使用 errors.rs 定义错误。如果是 lib crate 则使用 thiserror,如果是 bin crate 则使用 anyhow。
+5. bin crate 要保持 main.rs 简洁,核心逻辑放在其他文件中,由 lib.rs 统一管理。
+6. 如果使用 serde,那么遵循 serde best practice,并且使用 serde 的数据结构要 rename all CamelCase,以便生成的 json 适合前端。
+7. 对于复杂的数据结构如果要能够用 new 构造,那么如果 new 的参数复杂(>=4),请引入 typed_builder,对数据结构使用 TypedBuilder,并对每个字段根据情况引入 default, default_code, 以及 setter(strip_option), setter(into), 或者 setter(strip_option, into)。比如 Option 要使用 `#[builder(default, setter(strip_option, into)]`.
+8. 如果需要 web framework,那么必须使用 axum,axum 必须构造 AppConfig / AppState,AppConfig 通过 arc_swap 放入 AppState 中。同时,API 和输入输出需要使用 utoipa,让 API 支持 openapi spec,并引入 utoipa swagger 支持 swagger endpoint。
+9. 如果使用 sqlx,那么写入数据库和读取的数据都需要定义合适的类型,并使用 FromRow。使用 sqlx::query_as,不要使用任何 query! 宏。sqlx 相关 unit test 代码使用 sqlx-db-tester。
+10. 在并发场景下,遵循 Rust 并发处理最佳实践。如果是 primitive type,使用 AtomicXXX 类型,否则如果非频繁更新,可以考虑使用 arc_swap,否则如果可以使用 dashmap,则使用 dashmap,不能使用,则可以选择 tokio 下的 Mutex 或者 RwLock。
+11. unit test 必须写在和代码同一文件中,所有公开接口都需要足够正交的 unit test 来覆盖。
+整套规则写入 .cursor/rules/rust 目录,以 .cursor/rules/rust/main.mdc 为入口规则。所有规则都用 English.
+
+请仔细阅读已有的 @/rust rule set,
+@instructions.md 是我的一个 real world rust application 里面使用的所有 prompt,里面包含了一些 rust 项目的 best practice,请抽取这些 best practice 并更新 rust rule sets。
+
+刚才更新和生成的 rule 中的示例代码包含特定的系统(比如 workflow / node),请重新审视所有的 rules,确保 rules 是尽可能 general。
+
+我这里有一个我经常使用的 crate 的列表,请加入或者更新到 rules 中(这些 deps 根据需要引入):
+
+```toml
+anyhow = "1.0"
+async-trait = "0.1"
+atomic_enum = "0.3"
+axum = { version = "0.8", features = ["macros", "http2"] }
+base64 = "0.22"
+chrono = { version = "0.4", features = ["serde"] }
+clap = { version = "4.0", features = ["derive"] }
+dashmap = { version = "6", features = ["serde"] }
+derive_more = { version = "2", features = ["full"] }
+futures = "0.3"
+getrandom = "0.3"
+htmd = "0.2"
+http = "1"
+jsonpath-rust = "1"
+jsonwebtoken = "9.0"
+minijinja = { version = "2", features = [
+"json",
+"loader",
+"loop_controls",
+"speedups",
+] }
+rand = "0.8"
+regex = "1"
+reqwest = { version = "0.12", default-features = false, features = [
+"charset",
+"rustls-tls-webpki-roots",
+"http2",
+"json",
+"cookies",
+"gzip",
+"brotli",
+"zstd",
+"deflate",
+] }
+schemars = { version = "0.8", features = ["chrono", "url"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+serde_yaml = "0.9"
+sqlx = { version = "0.8", features = [
+"chrono",
+"postgres",
+"runtime-tokio-rustls",
+"sqlite",
+"time",
+"uuid",
+] }
+thiserror = "2.0"
+time = { version = "0.3", features = ["serde"] }
+tokio = { version = "1.45", features = [
+"macros",
+"rt-multi-thread",
+"signal",
+"sync",
+] }
+tower = { version = "0.5", features = ["util"] }
+tower-http = { version = "0.6", features = ["cors", "trace"] }
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+typed-builder = "0.21"
+url = "2.5"
+utoipa = { version = "5", features = ["axum_extras"] }
+utoipa-axum = { version = "0.2" }
+utoipa-swagger-ui = { version = "9", features = [
+"axum",
+"vendored",
+], default-features = false }
+uuid = { version = "1.17", features = ["v4", "serde"] }
+```
+
+@workspace.mdc 里面 workspace 的例子不好,我们应该根据系统的各个子系统来划分 crate,请更新 example
+
请构建 Rust CLI 项目的 rules:
1. 如果项目需要使用到 CLI,则引入 clap,使用 derive feature。
@@ -14,3 +108,62 @@ pub trait CommandExecutor {
```
4. 其他请遵循 clap 最佳实践
+
+现在请帮我构建 protobuf / grpc rules,并更新 @main.mdc。当项目需要构建 protobuf / grpc 应用时,需要使用 prost / tonic 最新版本。一些 best practice:
+
+1. prost / tonic 生成的代码放在 src/pb 中,注意添加 src/pb/mod.rs 引用所有生成的文件,然后在 lib.rs 中 `pub mod pb;` 进行引用。
+2. 使能 format feature,并且设置 format(true)。
+2. 如果生成的代码名为 src/pb/a.b.rs,请在 build.rs 中将其重命名(比如如果不存在重名风险,则 src/pb/b.rs 或者 src/pb/a/b.rs)。注意生成对应的 mod.rs
+3. 为 prost/tonic 生成的每个数据结构提供对应的结构,比如 Foo,则生成 FooInner。相对于 Foo,它不包含不必要的 Option,并且包含 #[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)] 等 attribute。它也实现了 From for Foo 的 trait。
+4. prost/tonic 生成的数据结构要实现一个 MessageSanitizer trait:
+
+```rust
+pub trait MessageSanitizer {
+ type Output;
+ fn sanitize(self) -> Self::Output;
+}
+比如 type 是 Foo,那么 Output 是 FooInner,sanitize 会处理各种 default 场景,比如 Option default 是 None,我们转换时为 FooInner 提供 BarInner 的 default 值。
+5. 对于 grpc service,先为数据结构生成同名的,输入输出更简洁的方法,再在 grpc service trait 的实现中引用这些方法。比如:
+```rust
+// 不要这样实现
+#[tonic::async_trait]
+impl Greeter for MyGreeter {
+ async fn say_hello(
+ &self,
+ request: Request,
+ ) -> Result, Status> {
+ println!("Got a request from {:?}", request.remote_addr());
+
+ let reply = hello_world::HelloReply {
+ message: format!("Hello {}!", request.into_inner().name),
+ };
+ Ok(Response::new(reply))
+ }
+}
+// 使用如下实现
+impl MyGreeter {
+async pub fn say_hello(
+ &self,
+ request: HelloRequestInner,
+ ) -> Result {
+ println!("Got a request from {:?}", request.remote_addr());
+
+ let reply = HelloReplyInner {
+ message: format!("Hello {}!", request.into_inner().name),
+ };
+ Ok(reply)
+ }
+}
+// 然后在 impl Greeter for MyGreeter 中调用这个方法(需要转换输入输出)
+```
+
+通过这种方法,unit test 可以更好地测试数据结构的方法,而避免复杂的输入输出的构建。
+
+几处修改:
+
+1. 使用 From trait,不要额外定义 ToProtobuf。
+2. 在 build.rs 中使用 pb_dir,不要使用 out_dir。
+3. 使用 prost_types,不要使用compile_well_known_types(true)
+4. 不要添加 protoc_arg
+5. 对 primitive type 不需要 sanitize_otional_xxx。
+6. TypedBuilder 用法遵循:并对每个字段根据情况引入 default, default_code, 以及 setter(strip_option), setter(into), 或者 setter(strip_option, into)。比如 Option 要使用 `#[builder(default, setter(strip_option, into)]`. 不要滥用 default。