Skip to main content

Introduction

The bomboni_request crate provides comprehensive utilities for building and processing API requests that follow Google AIP (API Improvement Proposals) standards. It offers a complete toolkit for implementing production-ready API endpoints with filtering, ordering, pagination, and SQL generation capabilities.

Google AIP Standards

This library implements several key Google AIP standards:

AIP-132

Standard List method with pagination, filtering, and ordering support

AIP-160

CEL-based filtering language with logical and comparison operators
These standards provide a consistent, well-documented approach to API design that’s battle-tested across Google’s APIs.

Core Concepts

Filtering

Filter expressions use the Common Expression Language (CEL) syntax, supporting:
  • Logical operators: AND, OR, NOT
  • Comparison operators: =, !=, <, <=, >, >=, :
  • Complex nested expressions with parentheses
  • Field validation against schemas
use bomboni_request::filter::Filter;

let filter = Filter::parse(r#"
    user.age >= 18
    AND user.id:"4" 
    AND NOT (task.deleted = false)
"#).unwrap();

Ordering

Ordering specifications allow sorting by multiple fields with explicit directions:
use bomboni_request::ordering::Ordering;

let ordering = Ordering::parse("displayName desc, age asc").unwrap();

Pagination

Secure pagination with multiple token types:
  • Plain: No encoding (development/internal use)
  • Base64: Simple encoding
  • AES256: Encrypted tokens with symmetric key
  • RSA: Encrypted tokens with asymmetric keys

Schema Validation

The SchemaMapped trait enables type-safe validation of filters and ordering against defined schemas:
use bomboni_request::schema::{Schema, FieldMemberSchema, ValueType};
use bomboni_macros::btree_map_into;

let schema = Schema {
    members: btree_map_into! {
        "id" => FieldMemberSchema::new_ordered(ValueType::String),
        "age" => FieldMemberSchema::new_ordered(ValueType::Integer),
        "name" => FieldMemberSchema::new(ValueType::String),
    },
};

filter.validate(&schema, None).unwrap();

When to Use These Utilities

Use ListQueryBuilder when implementing standard list endpoints that need pagination, filtering, and ordering. This is ideal for resource listing endpoints following AIP-132.
Use SearchQueryBuilder when you need fuzzy text search combined with filters and pagination. Perfect for search endpoints that need to support query strings.
Use SqlFilterBuilder to convert filter expressions and ordering into SQL WHERE clauses and ORDER BY statements. Supports both PostgreSQL and MySQL dialects.
Use the Parse derive macro to automatically validate and transform incoming API requests (e.g., from Protocol Buffers) into validated domain models.

Feature Flags

The crate supports several optional features:
FeatureDescription
deriveEnable derive macros for request parsing
testingEnable testing utilities and schemas
tonicEnable gRPC integration with tonic
wasmEnable WebAssembly support
postgresEnable PostgreSQL type conversions
mysqlEnable MySQL type conversions

Architecture Overview

The request utilities follow a layered architecture:
1

Parsing Layer

Parse filter strings, ordering specifications, and page tokens from request parameters
2

Validation Layer

Validate parsed expressions against schemas to ensure type safety and field existence
3

Evaluation Layer

Evaluate filters and ordering against in-memory data structures, or convert to SQL
4

Response Generation

Build page tokens for next page of results based on the last returned item

Next Steps

Filtering

Learn about filter expressions and AIP-160 compliance

Ordering

Understand ordering specifications

Pagination

Implement pagination with ListQuery and SearchQuery

SQL Generation

Convert filters to SQL queries

Parse Derive

Auto-generate request parsers

Example: Complete API Endpoint

use bomboni_request::{
    filter::Filter,
    ordering::Ordering,
    query::list::{ListQueryBuilder, ListQueryConfig},
    query::page_token::plain::PlainPageTokenBuilder,
};

// Define your schema
let schema = UserItem::get_schema();

// Create a list query builder
let builder = ListQueryBuilder::new(
    schema,
    BTreeMap::new(),
    ListQueryConfig {
        max_page_size: Some(100),
        default_page_size: 20,
        primary_ordering_term: Some(OrderingTerm {
            name: "id".into(),
            direction: OrderingDirection::Ascending,
        }),
        max_filter_length: Some(1000),
        max_ordering_length: Some(100),
    },
    PlainPageTokenBuilder {},
);

// Parse incoming request
let query = builder.build(
    Some(50),                        // page_size
    None,                            // page_token
    Some(r#"age >= 18"#),            // filter
    Some("name desc")                // ordering
).unwrap();

// Use query.filter, query.ordering, query.page_size in your handler

Build docs developers (and LLMs) love