///|
pub(all) struct LimiterRecipe {
recipe_name : String
audience : String
engine : PolicyEngine
workload : Workload
} derive(Debug)
///|
pub fn LimiterRecipe::name(self : LimiterRecipe) -> String {
self.recipe_name
}
///|
pub fn LimiterRecipe::describe(self : LimiterRecipe) -> String {
"\{self.recipe_name} for \{self.audience}: \{self.engine.describe()}"
}
///|
pub fn LimiterRecipe::run(self : LimiterRecipe) -> SimulationReport {
self.engine.simulate(self.workload)
}
///|
pub fn recipe_api_gateway() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_token_bucket("global-burst", 100, 20, 1000)
engine.add_sliding_log("edge-smooth", 120, 1000)
LimiterRecipe::{
recipe_name: "api-gateway",
audience: "HTTP API edge services",
engine,
workload: Workload::steady(120, 0, 8),
}
}
///|
pub fn recipe_login_guard() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_fixed_window("login-window", 5, 60000)
engine.add_quota("login-day", 50, 86400000)
LimiterRecipe::{
recipe_name: "login-guard",
audience: "authentication endpoints",
engine,
workload: Workload::burst(8, 0),
}
}
///|
pub fn recipe_search_box() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_sliding_log("typeahead", 20, 1000)
engine.add_token_bucket("search-burst", 10, 5, 1000)
LimiterRecipe::{
recipe_name: "search-box",
audience: "interactive search and autocomplete",
engine,
workload: Workload::ramp(20, 0, 100, 4),
}
}
///|
pub fn recipe_crawler_politeness() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_token_bucket("host-bucket", 2, 1, 1000)
engine.add_fixed_window("host-minute", 30, 60000)
LimiterRecipe::{
recipe_name: "crawler-politeness",
audience: "web crawlers and sync jobs",
engine,
workload: Workload::steady(10, 0, 250),
}
}
///|
pub fn recipe_webhook_dispatch() -> LimiterRecipe {
let engine = PolicyEngine::any()
engine.add_token_bucket("normal", 5, 2, 1000)
engine.add_fixed_window("fallback-minute", 60, 60000)
LimiterRecipe::{
recipe_name: "webhook-dispatch",
audience: "outbound webhook workers",
engine,
workload: Workload::burst(12, 0),
}
}
///|
pub fn recipe_email_sender() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_fixed_window("per-minute", 120, 60000)
engine.add_quota("per-day", 1000, 86400000)
LimiterRecipe::{
recipe_name: "email-sender",
audience: "transactional email services",
engine,
workload: Workload::steady(130, 0, 450),
}
}
///|
pub fn recipe_sms_sender() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_fixed_window("per-minute", 20, 60000)
engine.add_quota("per-day", 200, 86400000)
LimiterRecipe::{
recipe_name: "sms-sender",
audience: "SMS notification workers",
engine,
workload: Workload::burst(25, 0),
}
}
///|
pub fn recipe_image_renderer() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_token_bucket("render-burst", 4, 1, 1000)
engine.add_fixed_window("render-minute", 30, 60000)
LimiterRecipe::{
recipe_name: "image-renderer",
audience: "expensive CPU-bound rendering jobs",
engine,
workload: Workload::ramp(12, 0, 500, 20),
}
}
///|
pub fn recipe_checkout_guard() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_sliding_log("checkout", 3, 10000)
engine.add_quota("daily-checkout", 30, 86400000)
LimiterRecipe::{
recipe_name: "checkout-guard",
audience: "payment and checkout workflows",
engine,
workload: Workload::burst(5, 0),
}
}
///|
pub fn recipe_password_reset() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_fixed_window("reset-hour", 3, 3600000)
engine.add_quota("reset-day", 5, 86400000)
LimiterRecipe::{
recipe_name: "password-reset",
audience: "account recovery flows",
engine,
workload: Workload::burst(6, 0),
}
}
///|
pub fn recipe_batch_import() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_token_bucket("import-rate", 50, 10, 1000)
engine.add_fixed_window("import-minute", 500, 60000)
LimiterRecipe::{
recipe_name: "batch-import",
audience: "bulk import jobs",
engine,
workload: Workload::steady(80, 0, 50),
}
}
///|
pub fn recipe_tenant_fairness() -> LimiterRecipe {
let engine = PolicyEngine::all()
engine.add_token_bucket("tenant-burst", 20, 5, 1000)
engine.add_quota("tenant-day", 10000, 86400000)
LimiterRecipe::{
recipe_name: "tenant-fairness",
audience: "multi-tenant SaaS backends",
engine,
workload: Workload::alternating_keys(40, 0, 50, "tenant-a", "tenant-b"),
}
}
///|
pub fn recipe_all() -> Array[LimiterRecipe] {
[
recipe_api_gateway(),
recipe_login_guard(),
recipe_search_box(),
recipe_crawler_politeness(),
recipe_webhook_dispatch(),
recipe_email_sender(),
recipe_sms_sender(),
recipe_image_renderer(),
recipe_checkout_guard(),
recipe_password_reset(),
recipe_batch_import(),
recipe_tenant_fairness(),
]
}
///|
pub fn recipe_names() -> Array[String] {
let names : Array[String] = []
for recipe in recipe_all() {
names.push(recipe.name())
}
names
}
///|
pub fn recipe_markdown_index() -> String {
let mut out = "# MoonLimit Recipes\n"
for recipe in recipe_all() {
out = out + "- " + recipe.describe() + "\n"
}
out
}