Skip to main content
When you use bomboni_prost to compile Protocol Buffers, it generates additional helper code beyond the standard prost output. This page explains the structure and features of the generated code.

Generated File Structure

Generated code is written to files with a .plus suffix to distinguish it from base prost output:
// Include both base and enhanced code
bomboni_proto::include_proto!("bookstore.v1");      // from prost-build
bomboni_proto::include_proto!("bookstore.v1.plus"); // from bomboni_prost
The .plus suffix makes it clear which code comes from Bomboni and allows you to use both codebases side-by-side.

Message Helpers

For each protobuf message, Bomboni generates field name constants and other utilities.

Field Name Constants

Given this protobuf definition:
message RetryInfo {
  google.protobuf.Duration retry_delay = 1;
}
Bomboni generates:
impl RetryInfo {
    pub const RETRY_DELAY_FIELD_NAME: &'static str = "retry_delay";
}
These constants are useful for:
  • Reflection and introspection
  • Building field masks programmatically
  • Error messages that reference field names
  • Logging and debugging
use bomboni_proto::google::rpc::RetryInfo;

// Use in error messages
fn validate(retry_info: &RetryInfo) -> Result<(), String> {
    if retry_info.retry_delay.is_none() {
        return Err(format!(
            "Field {} is required",
            RetryInfo::RETRY_DELAY_FIELD_NAME
        ));
    }
    Ok(())
}

Enum Helpers

Enum types receive extensive helper code for working with names and values.

Generated Constants

Given this protobuf enum:
enum Code {
  OK = 0;
  CANCELLED = 1;
  UNKNOWN = 2;
  INVALID_ARGUMENT = 3;
  // ...
}
Bomboni generates:
impl Code {
    // Enum metadata
    pub const NAME: &'static str = "Code";
    pub const PACKAGE: &'static str = "google.rpc";
    
    // Value name constants
    pub const OK_VALUE_NAME: &'static str = "OK";
    pub const CANCELLED_VALUE_NAME: &'static str = "CANCELLED";
    pub const UNKNOWN_VALUE_NAME: &'static str = "UNKNOWN";
    pub const INVALID_ARGUMENT_VALUE_NAME: &'static str = "INVALID_ARGUMENT";
    
    // Array of all value names
    pub const VALUE_NAMES: &'static [&'static str] = &[
        Self::OK_VALUE_NAME,
        Self::CANCELLED_VALUE_NAME,
        Self::UNKNOWN_VALUE_NAME,
        Self::INVALID_ARGUMENT_VALUE_NAME,
        // ...
    ];
}

Usage Examples

use bomboni_proto::google::rpc::Code;

// Get all possible values
for name in Code::VALUE_NAMES {
    println!("Code value: {}", name);
}

// Use in validation
fn is_valid_code_name(name: &str) -> bool {
    Code::VALUE_NAMES.contains(&name)
}

// Metadata for logging
println!("Enum: {}.{}", Code::PACKAGE, Code::NAME);

Serde Integration

When api.serde is enabled in CompileConfig, Bomboni generates Serialize and Deserialize implementations.

Enum Serialization

Enums serialize to their string names:
impl ::serde::Serialize for Code {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: ::serde::Serializer,
    {
        // Serializes as string name like "INVALID_ARGUMENT"
    }
}

impl<'de> ::serde::Deserialize<'de> for Code {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: ::serde::Deserializer<'de>,
    {
        // Deserializes from string name
    }
}
use bomboni_proto::google::rpc::Code;

let code = Code::InvalidArgument;
let json = serde_json::to_string(&code).unwrap();
assert_eq!(json, r#""INVALID_ARGUMENT""#);

let decoded: Code = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, Code::InvalidArgument);

Helper Modules

When api.helpers_mod is set, Bomboni generates helper modules for working with enum fields:
pub mod helpers {
    /// Utility for working with i32s in message fields.
    /// Usable with #[serde(with = "...")]
    pub mod code_serde {
        use ::serde::{Serialize, Deserialize};
        
        pub fn serialize<S>(
            value: &i32,
            serializer: S,
        ) -> Result<S::Ok, S::Error>
        where
            S: ::serde::Serializer,
        {
            // Serializes i32 as enum name
        }
        
        pub fn deserialize<'de, D>(deserializer: D) -> Result<i32, D::Error>
        where
            D: ::serde::Deserializer<'de>,
        {
            // Deserializes enum name to i32
        }
    }
}

Using Enum Helpers

Use the generated helpers with serde field attributes:
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct MyMessage {
    // Use helper to serialize i32 field as enum name
    #[serde(with = "helpers::code_serde")]
    code: i32,
}

Oneof Helpers

Oneof fields receive special treatment with utility functions for variant handling.

Generated Code

Given this protobuf message:
message Value {
  oneof kind {
    NullValue null_value = 1;
    double number_value = 2;
    string string_value = 3;
    bool bool_value = 4;
    Struct struct_value = 5;
    ListValue list_value = 6;
  }
}
Bomboni generates:
impl Value {
    // Field name constants
    pub const NULL_VALUE_FIELD_NAME: &'static str = "null_value";
    pub const NUMBER_VALUE_FIELD_NAME: &'static str = "number_value";
    pub const STRING_VALUE_FIELD_NAME: &'static str = "string_value";
    pub const BOOL_VALUE_FIELD_NAME: &'static str = "bool_value";
    pub const STRUCT_VALUE_FIELD_NAME: &'static str = "struct_value";
    pub const LIST_VALUE_FIELD_NAME: &'static str = "list_value";
    
    // Oneof name
    pub const KIND_ONEOF_NAME: &'static str = "kind";
}

impl value::Kind {
    // Variant name constants
    pub const NULL_VALUE_VARIANT_NAME: &'static str = "null_value";
    pub const NUMBER_VALUE_VARIANT_NAME: &'static str = "number_value";
    pub const STRING_VALUE_VARIANT_NAME: &'static str = "string_value";
    pub const BOOL_VALUE_VARIANT_NAME: &'static str = "bool_value";
    pub const STRUCT_VALUE_VARIANT_NAME: &'static str = "struct_value";
    pub const LIST_VALUE_VARIANT_NAME: &'static str = "list_value";
    
    /// Get the name of the current variant
    pub fn get_variant_name(&self) -> &'static str {
        match self {
            Self::NullValue(_) => Self::NULL_VALUE_VARIANT_NAME,
            Self::NumberValue(_) => Self::NUMBER_VALUE_VARIANT_NAME,
            Self::StringValue(_) => Self::STRING_VALUE_VARIANT_NAME,
            Self::BoolValue(_) => Self::BOOL_VALUE_VARIANT_NAME,
            Self::StructValue(_) => Self::STRUCT_VALUE_VARIANT_NAME,
            Self::ListValue(_) => Self::LIST_VALUE_VARIANT_NAME,
        }
    }
}

Convenient Conversions

Bomboni generates From implementations for easy construction:
// From variant to message
impl From<value::Kind> for Value {
    fn from(value: value::Kind) -> Self {
        Self { kind: Some(value) }
    }
}

// From inner type to variant
impl From<String> for value::Kind {
    fn from(value: String) -> Self {
        Self::StringValue(value)
    }
}

// From inner type directly to message
impl From<String> for Value {
    fn from(value: String) -> Self {
        Self { kind: Some(value.into()) }
    }
}

Usage Examples

use bomboni_proto::google::protobuf::Value;

// Create using From
let value = Value::from("hello".to_string());
let value = Value::from(42.0);
let value = Value::from(true);

// Get variant name
if let Some(kind) = &value.kind {
    println!("Variant: {}", kind.get_variant_name());
}

// Pattern matching with constants
match &value.kind {
    Some(kind) if kind.get_variant_name() == value::Kind::STRING_VALUE_VARIANT_NAME => {
        println!("It's a string");
    }
    Some(kind) if kind.get_variant_name() == value::Kind::NUMBER_VALUE_VARIANT_NAME => {
        println!("It's a number");
    }
    _ => {}
}

Real-World Example

Here’s a complete example showing generated code in action:
use bomboni_proto::google::rpc::{Status, Code, BadRequest, bad_request::FieldViolation};
use bomboni_proto::google::protobuf::{Any, FieldMask};

// Using enum constants
fn create_error_status(violations: Vec<FieldViolation>) -> Status {
    let bad_request = BadRequest { field_violations: violations };
    
    Status::new(
        Code::InvalidArgument,
        "Invalid request parameters".to_string(),
        vec![Any::from_msg(&bad_request).unwrap()],
    )
}

// Using field name constants for field masks
fn update_mask_for_user() -> FieldMask {
    // In a real application, these would come from generated User message
    FieldMask::from(vec!["name", "email", "profile"])
}

// Using variant name utilities
fn describe_value(value: &Value) -> String {
    match &value.kind {
        Some(kind) => format!("Value of type: {}", kind.get_variant_name()),
        None => "Empty value".to_string(),
    }
}

// Serialization with generated helpers
let status = create_error_status(vec![
    FieldViolation {
        field: "email".to_string(),
        description: "Invalid format".to_string(),
    }
]);

let json = serde_json::to_string_pretty(&status).unwrap();
println!("{}", json);
// Output:
// {
//   "code": "INVALID_ARGUMENT",
//   "message": "Invalid request parameters",
//   "details": [
//     {
//       "@type": "type.googleapis.com/google.rpc.BadRequest",
//       "fieldViolations": [...]
//     }
//   ]
// }

Configuration Impact

The generated code depends on your ApiConfig settings:
names
bool
default:"true"
Controls generation of NAME and PACKAGE constants
field_names
bool
default:"true"
Controls generation of field name constants like FIELD_NAME
type_url
bool
default:"true"
Controls generation of TYPE_URL constants for use with Any types
oneof_utility
bool
default:"true"
Controls generation of oneof helper functions and conversions
serde
bool
default:"true"
Controls generation of Serialize and Deserialize implementations
helpers_mod
Option<String>
When set, generates a module with the given name containing helper functions

Best Practices

Instead of hardcoding field names as strings, use the generated constants:
// Bad
if field_mask.contains("retry_delay") { ... }

// Good
if field_mask.contains(RetryInfo::RETRY_DELAY_FIELD_NAME) { ... }
Use the generated From implementations for concise code:
// Instead of:
Value { kind: Some(value::Kind::StringValue("hello".to_string())) }

// Use:
Value::from("hello".to_string())
For logging and debugging, use get_variant_name() instead of matching:
// For logging
log::debug!("Received value of type: {}", kind.get_variant_name());
When working with JSON APIs, enable serde helpers for proper enum serialization:
#[derive(Serialize, Deserialize)]
struct Response {
    #[serde(with = "helpers::code_serde")]
    status_code: i32,
}

Next Steps

Prost Configuration

Configure code generation options

Proto Types

Explore runtime protobuf types

Build docs developers (and LLMs) love