protto 0.6.2

Automatically derive Protobuf and Rust conversions
Documentation
//! # protto
//!
//! Derive seamless conversions between `prost`-generated Protobuf types and custom Rust types.
//!
//! ## Overview
//!
//! `protto` is a procedural macro for automatically deriving
//! efficient, bidirectional conversions between Protobuf types generated by
//! [`prost`](https://github.com/tokio-rs/prost) and your native Rust structs.
//! This macro will significantly reduce boilerplate when you're working with
//! Protobufs.
//!
//! ## Features
//!
//! - **Automatic Bidirectional Conversion:** Derives `From<Proto>` and `Into<Proto>` implementations.
//! - **Primitive Type Support:** Direct mapping for Rust primitive types (`u32`, `i64`, `String`, etc.).
//! - **Option and Collections:** Supports optional fields (`Option<T>`) and collections (`Vec<T>`).
//! - **Newtype Wrappers:** Transparent conversions for single-field tuple structs.
//! - **Field Renaming:** Customize mapping between Rust and Protobuf field names using `#[protto(proto_name = "...")]`.
//! - **Custom Conversion Functions:** Handle complex scenarios with user-defined functions via `#[protto(from_proto_fn = "...")]` and `#[protto(to_proto_fn = "...")]`.
//! - **Ignored Fields:** Exclude fields from conversion using `#[protto(ignore)]`.
//! - **Advanced Error Handling:** Support for custom error types and functions.
//! - **Smart Optionality Detection:** Automatic inference with manual override capabilities.
//! - **Configurable Protobuf Module:** Defaults to searching for types in a `proto` module, customizable per struct or globally.
//!
//! ## Basic Usage
//!
//! Given Protobuf definitions compiled with `prost`:
//!
//! ```protobuf
//! syntax = "proto3";
//! package service;
//!
//! message Track {
//!     uint64 track_id = 1;
//! }
//!
//! message State {
//!     repeated Track tracks = 1;
//! }
//! ```
//!
//! Derive conversions in Rust:
//!
//! ```rust,ignore
//! use protto::Protto;
//!
//! mod proto {
//!     tonic::include_proto!("service");
//! }
//!
//! #[derive(Protto)]
//! #[protto(module = "proto")]
//! pub struct Track {
//!     #[protto(transparent, proto_name = "track_id")]
//!     pub id: TrackId,
//! }
//!
//! #[derive(Protto)]
//! pub struct TrackId(u64);
//!
//! #[derive(Protto)]
//! pub struct State {
//!     pub tracks: Vec<Track>,
//! }
//! ```
//!
//! ## Attribute Reference
//!
//! ### Struct-Level Attributes
//!
//! #### `#[protto(module = "path")]`
//! Specifies the module path where protobuf types are located.
//! ```rust,ignore
//! #[derive(Protto)]
//! #[protto(module = "my_proto")]  // looks for types in my_proto::*
//! struct MyStruct { ... }
//! ```
//!
//! #### `#[protto(proto_name = "ProtoName")]`
//! Maps the struct to a different protobuf type name.
//! ```rust,ignore
//! #[derive(Protto)]
//! #[protto(proto_name = "StateMessage")]  // maps to proto::StateMessage
//! struct State { ... }
//! ```
//!
//! #### `#[protto(error_type = ErrorType)]`
//! Sets the error type for conversions that can fail.
//! ```rust,ignore
//! #[derive(Protto)]
//! #[protto(error_type = MyError)]
//! struct MyStruct { ... }
//! ```
//!
//! #### `#[protto(error_fn = "function_name")]`
//! Specifies a function to handle conversion errors at the struct level.
//!
//! ### Field-Level Attributes
//!
//! #### `#[protto(transparent)]`
//! For newtype wrappers - directly converts the inner value.
//! ```rust,ignore
//! #[derive(Protto)]
//! struct UserId(#[protto(transparent)] u64);
//! ```
//!
//! #### `#[protto(proto_name = "proto_field_name")]`
//! Maps the field to a different name in the protobuf.
//! ```rust,ignore
//! #[protto(proto_name = "user_id")]
//! pub id: u64,  // maps to proto.user_id
//! ```
//!
//! #### `#[protto(ignore)]`
//! Excludes the field from proto conversion (uses Default::default).
//! ```rust,ignore
//! #[protto(ignore)]
//! pub runtime_data: HashMap<String, String>,
//! ```
//!
//! #### Custom Conversion Functions
//!
//! ##### `#[protto(from_proto_fn = "function")]`
//! Uses a custom function for proto → rust conversion.
//! ```rust,ignore
//! #[protto(from_proto_fn = "parse_timestamp")]
//! pub created_at: DateTime<Utc>,
//! ```
//!
//! ##### `#[protto(to_proto_fn = "function")]`
//! Uses a custom function for rust → proto conversion.
//! ```rust,ignore
//! #[protto(to_proto_fn = "format_timestamp")]
//! pub created_at: DateTime<Utc>,
//! ```
//!
//! Both can be combined for bidirectional custom conversion:
//! ```rust,ignore
//! #[protto(from_proto_fn = "from_proto_map", to_proto_fn = "to_proto_map")]
//! pub metadata: HashMap<String, Value>,
//! ```
//!
//! #### Optionality Control
//!
//! ##### `#[protto(proto_optional)]`
//! Explicitly treats the proto field as optional.
//! ```rust,ignore
//! #[protto(proto_optional)]
//! pub field: String,  // proto field is Option<String>, gets unwrapped
//! ```
//!
//! ##### `#[protto(proto_required)]`
//! Explicitly treats the proto field as required.
//! ```rust,ignore
//! #[protto(proto_required)]
//! pub field: Option<String>,  // proto field is String, gets wrapped
//! ```
//!
//! #### Error Handling
//!
//! ##### `#[protto(expect)]`
//! Uses `.expect()` with panic on missing optional fields.
//! ```rust,ignore
//! #[protto(expect)]
//! pub required_field: String,  // panics if proto field is None
//! ```
//!
//! ##### `#[protto(error_type = ErrorType)]`
//! Field-level error type override.
//!
//! ##### `#[protto(error_fn = "function")]`
//! Custom error handling function for this field.
//! ```rust,ignore
//! #[protto(expect, error_fn = "handle_missing_field")]
//! pub critical_field: String,
//! ```
//!
//! #### Default Values
//!
//! ##### `#[protto(default)]`
//! Uses `Default::default()` for missing/empty fields.
//! ```rust,ignore
//! #[protto(default)]
//! pub optional_field: String,  // empty string if proto field is None
//! ```
//!
//! ##### `#[protto(default_fn = "function")]`
//! Uses a custom function for default values.
//! ```rust,ignore
//! #[protto(default_fn = "default_username")]
//! pub username: String,
//!
//! fn default_username() -> String {
//!     "anonymous".to_string()
//! }
//! ```
//!
//! ## Advanced Examples
//!
//! ### Complex conversions with custom functions
//!
//! ```rust,ignore
//! use std::collections::HashMap;
//!
//! #[derive(Protto)]
//! #[protto(proto_name = "State")]
//! pub struct StateMap {
//!     #[protto(from_proto_fn = "into_map", to_proto_fn = "from_map")]
//!     pub tracks: HashMap<TrackId, Track>,
//! }
//!
//! pub fn into_map(tracks: Vec<proto::Track>) -> HashMap<TrackId, Track> {
//!     tracks.into_iter().map(|t| (TrackId(t.track_id), t.into())).collect()
//! }
//!
//! pub fn from_map(tracks: HashMap<TrackId, Track>) -> Vec<proto::Track> {
//!     tracks.into_values().map(Into::into).collect()
//! }
//! ```
//!
//! ### Ignoring runtime fields
//!
//! ```rust,ignore
//! use std::sync::atomic::AtomicU64;
//!
//! #[derive(Protto)]
//! #[protto(proto_name = "State")]
//! pub struct ComplexState {
//!     pub tracks: Vec<Track>,
//!     #[protto(ignore)]
//!     pub counter: AtomicU64,
//!     #[protto(ignore, default = "default_cache")]
//!     pub cache: HashMap<String, String>,
//! }
//!
//! fn default_cache() -> HashMap<String, String> {
//!     HashMap::with_capacity(100)
//! }
//! ```
//!
//! ### Handling enums
//!
//! ```protobuf
//! enum Status {
//!     STATUS_OK = 0;
//!     STATUS_MOVED_PERMANENTLY = 1;
//!     STATUS_FOUND = 2;
//!     STATUS_NOT_FOUND = 3;
//! }
//!
//! message StatusResponse {
//!     Status status = 1;
//!     string message = 2;
//! }
//! ```
//!
//! ```rust,ignore
//! // Enum variant names are automatically mapped (prefix removal supported)
//! #[derive(Protto)]
//! pub enum Status {
//!     Ok,              // maps to STATUS_OK
//!     MovedPermanently, // maps to STATUS_MOVED_PERMANENTLY
//!     Found,           // maps to STATUS_FOUND
//!     NotFound,        // maps to STATUS_NOT_FOUND
//! }
//!
//! #[derive(Protto)]
//! pub struct StatusResponse {
//!     pub status: Status,
//!     pub message: String,
//! }
//! ```
//!
//! ### Error handling strategies
//!
//! ```rust,ignore
//! #[derive(Protto)]
//! #[protto(error_type = ConversionError)]
//! pub struct User {
//!     // Panic on missing field
//!     #[protto(expect)]
//!     pub id: UserId,
//!
//!     // Use default value
//!     #[protto(default)]
//!     pub name: String,
//!
//!     // Custom error handling
//!     #[protto(error_fn = handle_email_error)]
//!     pub email: String,
//!
//!     // Custom default function
//!     #[protto(default_fn = default_role)]
//!     pub role: UserRole,
//! }
//!
//! fn handle_email_error() -> ConversionError {
//!     ConversionError::MissingEmail
//! }
//!
//! fn default_role() -> UserRole {
//!     UserRole::Guest
//! }
//! ```
//!
//! ## Optionality Handling
//!
//! The macro automatically detects and handles optionality between proto and Rust types:
//!
//! | Proto Type | Rust Type | Behavior |
//! |------------|-----------|----------|
//! | `string field = 1;` | `String` | Direct assignment |
//! | `optional string field = 1;` | `Option<String>` | Direct assignment |
//! | `optional string field = 1;` | `String` | Use `#[protto(expect)]` or `#[protto(default)]` |
//! | `string field = 1;` | `Option<String>` | Wrapped in `Some()` |
//! | `repeated string items = 1;` | `Vec<String>` | Direct conversion |
//! | `repeated string items = 1;` | `Option<Vec<String>>` | `None` for empty, `Some(vec)` otherwise |
//!
//! ## Type Support
//!
//! ### Primitives
//! All Rust primitives are supported: `bool`, `u32`, `u64`, `i32`, `i64`, `f32`, `f64`, `String`
//!
//! ### Collections
//! - `Vec<T>` ↔ `repeated T`
//! - `Option<Vec<T>>` ↔ `repeated T` (with empty handling)
//! - Custom collections via `from_proto_fn`/`to_proto_fn`
//!
//! ### Optional Types
//! - `Option<T>` ↔ `optional T`
//! - Automatic unwrapping/wrapping with error handling
//!
//! ### Custom Types
//! - Any type implementing `From`/`Into` traits
//! - Newtype wrappers with `#[protto(transparent)]`
//! - Custom conversion functions
//!
//! ### Enums
//! - Rust enums ↔ proto enums (with automatic prefix handling)
//! - Custom discriminant handling
//!
//! ## Error Handling
//!
//! The macro supports multiple error handling strategies:
//!
//! 1. **Panic**: Use `#[protto(expect)]` - panics with descriptive messages
//! 2. **Default Values**: Use `#[protto(default)]` - provides sensible defaults
//! 3. **Custom Errors**: Use `#[protto(expect, error_type = T, error_fn = "f")]` - custom error handling
//! 4. **Result Types**: Generated `TryFrom` implementations for fallible conversions
//!
//! ## Limitations
//!
//! - Assumes Protobuf-generated types live in a single module (configurable).
//! - Optional Protobuf message fields use `.expect` and panic if missing (unless configured otherwise).
//! - Complex nested generics may require custom conversion functions.
//! - Recursive types require careful handling of conversion cycles.
//!
//! ## Best Practices
//!
//! 1. **Let the macro infer**: Start without attributes and add them only when needed.
//! 2. **Use transparent for newtypes**: `#[protto(transparent)]` for simple wrapper types.
//! 3. **Handle errors appropriately**: Choose between panic, default, or custom error strategies.
//! 4. **Test edge cases**: Verify behavior with None/empty values and boundary conditions.
//! 5. **Document custom functions**: Make conversion logic clear for maintenance.

#![doc(html_root_url = "https://docs.rs/protto/0.2.0")]

// re-export the derive macro
pub use protto_derive::*;