Skip to main content
This guide walks you through creating a simple application that demonstrates Bomboni’s core features: unique ID generation, filtering, and request handling.

Prerequisites

You need Rust 1.83 or later with the 2024 edition enabled.

Create a new project

Create a new Rust project and add Bomboni as a dependency:
1

Initialize the project

cargo new bomboni-quickstart
cd bomboni-quickstart
2

Add Bomboni to Cargo.toml

Cargo.toml
[package]
name = "bomboni-quickstart"
version = "0.1.0"
edition = "2024"

[dependencies]
bomboni = { version = "0.2.1", features = ["request", "serde"] }
serde_json = "1.0"

Generate unique identifiers

Let’s start by generating unique, sortable identifiers using Bomboni’s ULID-based ID system. Replace the contents of src/main.rs with:
src/main.rs
use bomboni::common::id::Id;

fn main() {
    // Generate a single unique ID
    let id = Id::generate();
    println!("Generated ID: {}", id);
    
    // Generate multiple IDs - they're monotonically increasing
    let ids = Id::generate_multiple(5);
    println!("\nGenerated {} IDs:", ids.len());
    for (i, id) in ids.iter().enumerate() {
        println!("  {}: {}", i + 1, id);
    }
    
    // Parse an ID from a string
    let id_str = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
    match id_str.parse::<Id>() {
        Ok(parsed_id) => println!("\nParsed ID: {}", parsed_id),
        Err(e) => println!("\nFailed to parse ID: {}", e),
    }
}
Run the example:
cargo run
You should see output similar to:
Generated ID: 01JQZX8Y0K5XQJZJ9WQVX4WX0M

Generated 5 IDs:
  1: 01JQZX8Y0K5XQJZJ9WQVX4WX0N
  2: 01JQZX8Y0K5XQJZJ9WQVX4WX0P
  3: 01JQZX8Y0K5XQJZJ9WQVX4WX0Q
  4: 01JQZX8Y0K5XQJZJ9WQVX4WX0R
  5: 01JQZX8Y0K5XQJZJ9WQVX4WX0S

Parsed ID: 01ARZ3NDEKTSV4RRFFQ69G5FAV
The IDs are sortable by creation time and are unique across distributed systems without coordination.

Work with UTC datetime

Bomboni provides a UTC-focused datetime type. Add this to your main.rs:
src/main.rs
use bomboni::common::date_time::UtcDateTime;

fn datetime_example() {
    // Get the current time
    let now = UtcDateTime::now();
    println!("Current time: {}", now);
    
    // Create from Unix timestamp
    let dt = UtcDateTime::from_seconds(1609459200).unwrap();
    println!("From timestamp: {}", dt); // 2021-01-01T00:00:00Z
    
    // Parse from string
    let parsed = "2024-03-15T10:30:00Z".parse::<UtcDateTime>().unwrap();
    println!("Parsed datetime: {}", parsed);
    
    // Get timestamp
    let (seconds, nanos) = parsed.timestamp();
    println!("Timestamp: {} seconds, {} nanoseconds", seconds, nanos);
}
Call it from main:
src/main.rs
fn main() {
    // ... previous ID code ...
    
    println!("\n=== DateTime Examples ===");
    datetime_example();
}

Use worker-based ID generation

For distributed systems where multiple workers generate IDs, use the worker-based generator:
src/main.rs
use bomboni::common::id::worker::WorkerIdGenerator;

fn worker_id_example() {
    // Create a generator for worker #1
    let mut generator = WorkerIdGenerator::new(1);
    
    // Generate IDs that are unique to this worker
    let id1 = generator.generate();
    let id2 = generator.generate();
    
    println!("Worker 1 generated: {}", id1);
    println!("Worker 1 generated: {}", id2);
    
    // Generate multiple IDs efficiently
    let batch = generator.generate_multiple(3);
    println!("\nWorker 1 batch:");
    for id in batch {
        println!("  {}", id);
    }
    
    // Create a generator for worker #2
    let mut generator2 = WorkerIdGenerator::new(2);
    let id3 = generator2.generate();
    println!("\nWorker 2 generated: {}", id3);
}
Add it to main:
src/main.rs
fn main() {
    // ... previous code ...
    
    println!("\n=== Worker-Based ID Generation ===");
    worker_id_example();
}

Filter and query data

Now let’s use Bomboni’s powerful filtering capabilities. First, define a data structure:
src/main.rs
use bomboni::request::filter::Filter;
use bomboni::request::schema::{SchemaMapped, Schema, FieldMemberSchema, ValueType};
use bomboni::request::value::Value;
use bomboni::macros::btree_map_into;

#[derive(Debug, Clone)]
struct User {
    id: String,
    name: String,
    age: i32,
    email: String,
}

impl SchemaMapped for User {
    fn get_field(&self, name: &str) -> Value {
        match name {
            "id" => self.id.clone().into(),
            "name" => self.name.clone().into(),
            "age" => self.age.into(),
            "email" => self.email.clone().into(),
            _ => Value::Null,
        }
    }
}

impl User {
    fn get_schema() -> Schema {
        Schema {
            members: btree_map_into! {
                "id" => FieldMemberSchema::new_ordered(ValueType::String),
                "name" => FieldMemberSchema::new_ordered(ValueType::String),
                "age" => FieldMemberSchema::new_ordered(ValueType::Integer),
                "email" => FieldMemberSchema::new(ValueType::String),
            },
        }
    }
}
Now create a filtering example:
src/main.rs
fn filtering_example() {
    // Create sample users
    let users = vec![
        User {
            id: "1".into(),
            name: "Alice".into(),
            age: 30,
            email: "[email protected]".into(),
        },
        User {
            id: "2".into(),
            name: "Bob".into(),
            age: 25,
            email: "[email protected]".into(),
        },
        User {
            id: "3".into(),
            name: "Charlie".into(),
            age: 35,
            email: "[email protected]".into(),
        },
    ];
    
    // Parse a filter expression
    let filter = Filter::parse(r#"age >= 30 AND name = "Alice""#).unwrap();
    println!("Filter: {}", filter);
    
    // Validate the filter against the schema
    let schema = User::get_schema();
    filter.validate(&schema, None).unwrap();
    println!("Filter is valid!");
    
    // Apply filter to users
    println!("\nFiltering users:");
    for user in &users {
        match filter.evaluate(user) {
            Ok(Value::Boolean(true)) => {
                println!("  ✓ {} (age {}) matches", user.name, user.age);
            }
            Ok(Value::Boolean(false)) => {
                println!("  ✗ {} (age {}) doesn't match", user.name, user.age);
            }
            Ok(other) => {
                println!("  ? {} returned unexpected value: {:?}", user.name, other);
            }
            Err(e) => {
                println!("  ! Error evaluating {}: {}", user.name, e);
            }
        }
    }
    
    // Try a more complex filter
    let complex_filter = Filter::parse("age >= 25 AND age <= 35").unwrap();
    println!("\nComplex filter: {}", complex_filter);
    
    let matching_users: Vec<_> = users
        .iter()
        .filter(|user| {
            matches!(complex_filter.evaluate(*user), Ok(Value::Boolean(true)))
        })
        .collect();
    
    println!("Found {} matching users:", matching_users.len());
    for user in matching_users {
        println!("  - {} (age {})", user.name, user.age);
    }
}
Add it to main:
src/main.rs
fn main() {
    // ... previous code ...
    
    println!("\n=== Filtering Examples ===");
    filtering_example();
}

Complete example

Here’s the complete src/main.rs file:
use bomboni::common::id::{Id, worker::WorkerIdGenerator};
use bomboni::common::date_time::UtcDateTime;
use bomboni::request::filter::Filter;
use bomboni::request::schema::{SchemaMapped, Schema, FieldMemberSchema, ValueType};
use bomboni::request::value::Value;
use bomboni::macros::btree_map_into;

#[derive(Debug, Clone)]
struct User {
    id: String,
    name: String,
    age: i32,
    email: String,
}

impl SchemaMapped for User {
    fn get_field(&self, name: &str) -> Value {
        match name {
            "id" => self.id.clone().into(),
            "name" => self.name.clone().into(),
            "age" => self.age.into(),
            "email" => self.email.clone().into(),
            _ => Value::Null,
        }
    }
}

impl User {
    fn get_schema() -> Schema {
        Schema {
            members: btree_map_into! {
                "id" => FieldMemberSchema::new_ordered(ValueType::String),
                "name" => FieldMemberSchema::new_ordered(ValueType::String),
                "age" => FieldMemberSchema::new_ordered(ValueType::Integer),
                "email" => FieldMemberSchema::new(ValueType::String),
            },
        }
    }
}

fn datetime_example() {
    let now = UtcDateTime::now();
    println!("Current time: {}", now);
    
    let dt = UtcDateTime::from_seconds(1609459200).unwrap();
    println!("From timestamp: {}", dt);
    
    let parsed = "2024-03-15T10:30:00Z".parse::<UtcDateTime>().unwrap();
    println!("Parsed datetime: {}", parsed);
    
    let (seconds, nanos) = parsed.timestamp();
    println!("Timestamp: {} seconds, {} nanoseconds", seconds, nanos);
}

fn worker_id_example() {
    let mut generator = WorkerIdGenerator::new(1);
    
    let id1 = generator.generate();
    let id2 = generator.generate();
    
    println!("Worker 1 generated: {}", id1);
    println!("Worker 1 generated: {}", id2);
    
    let batch = generator.generate_multiple(3);
    println!("\nWorker 1 batch:");
    for id in batch {
        println!("  {}", id);
    }
    
    let mut generator2 = WorkerIdGenerator::new(2);
    let id3 = generator2.generate();
    println!("\nWorker 2 generated: {}", id3);
}

fn filtering_example() {
    let users = vec![
        User {
            id: "1".into(),
            name: "Alice".into(),
            age: 30,
            email: "[email protected]".into(),
        },
        User {
            id: "2".into(),
            name: "Bob".into(),
            age: 25,
            email: "[email protected]".into(),
        },
        User {
            id: "3".into(),
            name: "Charlie".into(),
            age: 35,
            email: "[email protected]".into(),
        },
    ];
    
    let filter = Filter::parse(r#"age >= 30 AND name = "Alice""#).unwrap();
    println!("Filter: {}", filter);
    
    let schema = User::get_schema();
    filter.validate(&schema, None).unwrap();
    println!("Filter is valid!");
    
    println!("\nFiltering users:");
    for user in &users {
        match filter.evaluate(user) {
            Ok(Value::Boolean(true)) => {
                println!("  ✓ {} (age {}) matches", user.name, user.age);
            }
            Ok(Value::Boolean(false)) => {
                println!("  ✗ {} (age {}) doesn't match", user.name, user.age);
            }
            _ => {}
        }
    }
    
    let complex_filter = Filter::parse("age >= 25 AND age <= 35").unwrap();
    println!("\nComplex filter: {}", complex_filter);
    
    let matching_users: Vec<_> = users
        .iter()
        .filter(|user| {
            matches!(complex_filter.evaluate(*user), Ok(Value::Boolean(true)))
        })
        .collect();
    
    println!("Found {} matching users:", matching_users.len());
    for user in matching_users {
        println!("  - {} (age {})", user.name, user.age);
    }
}

fn main() {
    println!("=== ID Generation ===");
    let id = Id::generate();
    println!("Generated ID: {}", id);
    
    let ids = Id::generate_multiple(5);
    println!("\nGenerated {} IDs:", ids.len());
    for (i, id) in ids.iter().enumerate() {
        println!("  {}: {}", i + 1, id);
    }
    
    let id_str = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
    match id_str.parse::<Id>() {
        Ok(parsed_id) => println!("\nParsed ID: {}", parsed_id),
        Err(e) => println!("\nFailed to parse ID: {}", e),
    }
    
    println!("\n=== DateTime Examples ===");
    datetime_example();
    
    println!("\n=== Worker-Based ID Generation ===");
    worker_id_example();
    
    println!("\n=== Filtering Examples ===");
    filtering_example();
}

Run the complete example

Run the complete example:
cargo run
You should see output demonstrating:
  • Unique ID generation
  • UTC datetime handling
  • Worker-based ID generation
  • Filter parsing and evaluation

Expected output

=== ID Generation ===
Generated ID: 01JQZX8Y0K5XQJZJ9WQVX4WX0M

Generated 5 IDs:
  1: 01JQZX8Y0K5XQJZJ9WQVX4WX0N
  2: 01JQZX8Y0K5XQJZJ9WQVX4WX0P
  3: 01JQZX8Y0K5XQJZJ9WQVX4WX0Q
  4: 01JQZX8Y0K5XQJZJ9WQVX4WX0R
  5: 01JQZX8Y0K5XQJZJ9WQVX4WX0S

Parsed ID: 01ARZ3NDEKTSV4RRFFQ69G5FAV

=== DateTime Examples ===
Current time: 2026-03-04T12:34:56Z
From timestamp: 2021-01-01T00:00:00Z
Parsed datetime: 2024-03-15T10:30:00Z
Timestamp: 1710497400 seconds, 0 nanoseconds

=== Worker-Based ID Generation ===
Worker 1 generated: 01JQZX8Y0L...
Worker 1 generated: 01JQZX8Y0M...

Worker 1 batch:
  01JQZX8Y0N...
  01JQZX8Y0P...
  01JQZX8Y0Q...

Worker 2 generated: 01JQZX8Y0R...

=== Filtering Examples ===
Filter: age >= 30 AND name = "Alice"
Filter is valid!

Filtering users:
  ✓ Alice (age 30) matches
  ✗ Bob (age 25) doesn't match
  ✗ Charlie (age 35) doesn't match

Complex filter: age >= 25 AND age <= 35
Found 3 matching users:
  - Alice (age 30)
  - Bob (age 25)
  - Charlie (age 35)

Next steps

Now that you understand the basics, explore more advanced features:

Request handling

Learn about list queries, pagination, and SQL generation

Protocol Buffers

Work with enhanced protobuf types and code generation

WebAssembly

Build WASM libraries with TypeScript bindings

Examples

Check out complete example projects

Build docs developers (and LLMs) love