diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index f1efcc327..f4def88b7 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -73,6 +73,7 @@ export const DEFAULT_CONFIG_BASE = { rustLibraries: [], rustLogging: "tracing", rustErrorHandling: "anyhow-thiserror", + rustCaching: "none", // Python ecosystem defaults pythonWebFramework: "fastapi", pythonOrm: "sqlalchemy", diff --git a/apps/cli/src/helpers/core/command-handlers.ts b/apps/cli/src/helpers/core/command-handlers.ts index 880f24924..86fd819ae 100644 --- a/apps/cli/src/helpers/core/command-handlers.ts +++ b/apps/cli/src/helpers/core/command-handlers.ts @@ -160,6 +160,7 @@ export async function createProjectHandler( rustLibraries: [], rustLogging: "none", rustErrorHandling: "none", + rustCaching: "none", cms: "none", caching: "none", search: "none", diff --git a/apps/cli/src/helpers/core/post-installation.ts b/apps/cli/src/helpers/core/post-installation.ts index 671ef6dbd..c7c9171c5 100644 --- a/apps/cli/src/helpers/core/post-installation.ts +++ b/apps/cli/src/helpers/core/post-installation.ts @@ -742,6 +742,15 @@ function displayRustInstructions(config: ProjectConfig & { depsInstalled: boolea output += `${pc.cyan("•")} Error Handling: ${errorHandlingNames[rustErrorHandling] || rustErrorHandling}\n`; } + const { rustCaching } = config; + if (rustCaching && rustCaching !== "none") { + const cachingNames: Record = { + moka: "Moka", + redis: "Redis", + }; + output += `${pc.cyan("•")} Caching: ${cachingNames[rustCaching] || rustCaching}\n`; + } + output += `\n${pc.bold("Common Cargo commands:")}\n`; output += `${pc.cyan("•")} Build: cargo build\n`; output += `${pc.cyan("•")} Run: cargo run\n`; diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 4f51f23b6..4297362ed 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -96,7 +96,9 @@ import { RustLoggingSchema, type RustLogging, RustErrorHandlingSchema, + RustCachingSchema, type RustErrorHandling, + type RustCaching, PythonWebFrameworkSchema, type PythonWebFramework, PythonOrmSchema, @@ -253,6 +255,7 @@ export const router = os.router({ rustLibraries: z.array(RustLibrariesSchema).optional().describe("Rust core libraries"), rustLogging: RustLoggingSchema.optional().describe("Rust logging (tracing, env-logger)"), rustErrorHandling: RustErrorHandlingSchema.optional().describe("Rust error handling (anyhow-thiserror, eyre)"), + rustCaching: RustCachingSchema.optional().describe("Rust caching (moka, redis)"), // Python ecosystem options pythonWebFramework: PythonWebFrameworkSchema.optional().describe( "Python web framework (fastapi, django)", @@ -582,6 +585,7 @@ export async function createVirtual( rustLibraries: options.rustLibraries || [], rustLogging: options.rustLogging || (options.ecosystem === "rust" ? "tracing" : "none"), rustErrorHandling: options.rustErrorHandling || (options.ecosystem === "rust" ? "anyhow-thiserror" : "none"), + rustCaching: options.rustCaching || "none", // Python ecosystem options pythonWebFramework: options.pythonWebFramework || "none", pythonOrm: options.pythonOrm || "none", diff --git a/apps/cli/src/mcp.ts b/apps/cli/src/mcp.ts index 05453db4b..e314a8126 100644 --- a/apps/cli/src/mcp.ts +++ b/apps/cli/src/mcp.ts @@ -52,6 +52,7 @@ import { RustLibrariesSchema, RustLoggingSchema, RustErrorHandlingSchema, + RustCachingSchema, RustOrmSchema, RustWebFrameworkSchema, SearchSchema, @@ -188,6 +189,7 @@ const SCHEMA_MAP: Record = { rustLibraries: RustLibrariesSchema, rustLogging: RustLoggingSchema, rustErrorHandling: RustErrorHandlingSchema, + rustCaching: RustCachingSchema, pythonWebFramework: PythonWebFrameworkSchema, pythonOrm: PythonOrmSchema, pythonValidation: PythonValidationSchema, @@ -210,7 +212,7 @@ const ECOSYSTEM_CATEGORIES: Record = { "logging", "observability", "featureFlags", "analytics", "cms", "caching", "search", "fileStorage", "astroIntegration", ], - rust: ["rustWebFramework", "rustFrontend", "rustOrm", "rustApi", "rustCli", "rustLibraries", "rustLogging", "rustErrorHandling"], + rust: ["rustWebFramework", "rustFrontend", "rustOrm", "rustApi", "rustCli", "rustLibraries", "rustLogging", "rustErrorHandling", "rustCaching"], python: ["pythonWebFramework", "pythonOrm", "pythonValidation", "pythonAi", "pythonAuth", "pythonTaskQueue", "pythonQuality"], go: ["goWebFramework", "goOrm", "goApi", "goCli", "goLogging"], shared: ["ecosystem", "packageManager", "addons", "examples", "webDeploy", "serverDeploy", "dbSetup"], @@ -330,6 +332,7 @@ function buildProjectConfig( rustLibraries: (input.rustLibraries as ProjectConfig["rustLibraries"]) ?? [], rustLogging: (input.rustLogging as ProjectConfig["rustLogging"]) ?? "none", rustErrorHandling: (input.rustErrorHandling as ProjectConfig["rustErrorHandling"]) ?? "none", + rustCaching: (input.rustCaching as ProjectConfig["rustCaching"]) ?? "none", pythonWebFramework: (input.pythonWebFramework as ProjectConfig["pythonWebFramework"]) ?? "none", pythonOrm: (input.pythonOrm as ProjectConfig["pythonOrm"]) ?? "none", pythonValidation: (input.pythonValidation as ProjectConfig["pythonValidation"]) ?? "none", @@ -425,6 +428,7 @@ function buildCompatibilityInput(input: Record): CompatibilityI rustLibraries: ((input.rustLibraries as string[]) ?? []).join(",") || "none", rustLogging: (input.rustLogging as string) ?? "none", rustErrorHandling: (input.rustErrorHandling as string) ?? "none", + rustCaching: (input.rustCaching as string) ?? "none", pythonWebFramework: (input.pythonWebFramework as string) ?? "none", pythonOrm: (input.pythonOrm as string) ?? "none", pythonValidation: (input.pythonValidation as string) ?? "none", @@ -645,6 +649,7 @@ export async function startMcpServer() { rustLibraries: z.array(RustLibrariesSchema).optional().describe("Rust libraries"), rustLogging: RustLoggingSchema.optional().describe("Rust logging library"), rustErrorHandling: RustErrorHandlingSchema.optional().describe("Rust error handling library"), + rustCaching: RustCachingSchema.optional().describe("Rust caching library"), pythonWebFramework: PythonWebFrameworkSchema.optional().describe("Python web framework"), pythonOrm: PythonOrmSchema.optional().describe("Python ORM"), pythonValidation: PythonValidationSchema.optional().describe("Python validation"), diff --git a/apps/cli/src/prompts/config-prompts.ts b/apps/cli/src/prompts/config-prompts.ts index 3c3f255a9..5f25a1412 100644 --- a/apps/cli/src/prompts/config-prompts.ts +++ b/apps/cli/src/prompts/config-prompts.ts @@ -44,6 +44,7 @@ import type { RustApi, RustCli, RustErrorHandling, + RustCaching, RustFrontend, RustLibraries, RustLogging, @@ -118,6 +119,7 @@ import { getRustLibrariesChoice, getRustLoggingChoice, getRustErrorHandlingChoice, + getRustCachingChoice, getRustOrmChoice, getRustWebFrameworkChoice, } from "./rust-ecosystem"; @@ -179,6 +181,7 @@ type PromptGroupResults = { rustLibraries: RustLibraries[]; rustLogging: RustLogging; rustErrorHandling: RustErrorHandling; + rustCaching: RustCaching; // Python ecosystem pythonWebFramework: PythonWebFramework; pythonOrm: PythonOrm; @@ -450,6 +453,10 @@ export async function gatherConfig( if (results.ecosystem !== "rust") return Promise.resolve("none" as RustErrorHandling); return getRustErrorHandlingChoice(flags.rustErrorHandling); }, + rustCaching: ({ results }) => { + if (results.ecosystem !== "rust") return Promise.resolve("none" as RustCaching); + return getRustCachingChoice(flags.rustCaching); + }, // Python ecosystem prompts (skip if TypeScript or Rust) pythonWebFramework: ({ results }) => { if (results.ecosystem !== "python") return Promise.resolve("none" as PythonWebFramework); @@ -575,6 +582,7 @@ export async function gatherConfig( rustLibraries: result.rustLibraries, rustLogging: result.rustLogging, rustErrorHandling: result.rustErrorHandling, + rustCaching: result.rustCaching, // Python ecosystem options pythonWebFramework: result.pythonWebFramework, pythonOrm: result.pythonOrm, diff --git a/apps/cli/src/prompts/rust-ecosystem.ts b/apps/cli/src/prompts/rust-ecosystem.ts index 2c24515a2..2904577d2 100644 --- a/apps/cli/src/prompts/rust-ecosystem.ts +++ b/apps/cli/src/prompts/rust-ecosystem.ts @@ -2,6 +2,7 @@ import type { RustApi, RustCli, RustErrorHandling, + RustCaching, RustFrontend, RustLibraries, RustLogging, @@ -288,3 +289,35 @@ export async function getRustErrorHandlingChoice(rustErrorHandling?: RustErrorHa return response; } + +export async function getRustCachingChoice(rustCaching?: RustCaching) { + if (rustCaching !== undefined) return rustCaching; + + const options = [ + { + value: "moka" as const, + label: "Moka", + hint: "High-performance concurrent in-memory cache (Caffeine-inspired)", + }, + { + value: "redis" as const, + label: "Redis", + hint: "Redis client with async support and connection pooling", + }, + { + value: "none" as const, + label: "None", + hint: "No caching library", + }, + ]; + + const response = await navigableSelect({ + message: "Select Rust caching library", + options, + initialValue: "none", + }); + + if (isCancel(response)) return exitCancelled("Operation cancelled"); + + return response; +} diff --git a/apps/cli/src/utils/bts-config.ts b/apps/cli/src/utils/bts-config.ts index 94983d213..c663643c3 100644 --- a/apps/cli/src/utils/bts-config.ts +++ b/apps/cli/src/utils/bts-config.ts @@ -57,6 +57,7 @@ export async function writeBtsConfig(projectConfig: ProjectConfig) { rustLibraries: projectConfig.rustLibraries, rustLogging: projectConfig.rustLogging, rustErrorHandling: projectConfig.rustErrorHandling, + rustCaching: projectConfig.rustCaching, pythonWebFramework: projectConfig.pythonWebFramework, pythonOrm: projectConfig.pythonOrm, pythonValidation: projectConfig.pythonValidation, @@ -121,6 +122,7 @@ export async function writeBtsConfig(projectConfig: ProjectConfig) { rustLibraries: btsConfig.rustLibraries, rustLogging: btsConfig.rustLogging, rustErrorHandling: btsConfig.rustErrorHandling, + rustCaching: btsConfig.rustCaching, pythonWebFramework: btsConfig.pythonWebFramework, pythonOrm: btsConfig.pythonOrm, pythonValidation: btsConfig.pythonValidation, diff --git a/apps/cli/src/utils/config-processing.ts b/apps/cli/src/utils/config-processing.ts index 560f23e5d..dd7a2f5d0 100644 --- a/apps/cli/src/utils/config-processing.ts +++ b/apps/cli/src/utils/config-processing.ts @@ -49,6 +49,7 @@ import type { RustLibraries, RustLogging, RustErrorHandling, + RustCaching, RustOrm, RustWebFramework, Runtime, @@ -312,6 +313,10 @@ export function processFlags(options: CLIInput, projectName?: string) { config.rustErrorHandling = options.rustErrorHandling as RustErrorHandling; } + if (options.rustCaching !== undefined) { + config.rustCaching = options.rustCaching as RustCaching; + } + // Python ecosystem options if (options.pythonWebFramework !== undefined) { config.pythonWebFramework = options.pythonWebFramework as PythonWebFramework; diff --git a/apps/cli/src/utils/generate-reproducible-command.ts b/apps/cli/src/utils/generate-reproducible-command.ts index 50845495b..8334eafcf 100644 --- a/apps/cli/src/utils/generate-reproducible-command.ts +++ b/apps/cli/src/utils/generate-reproducible-command.ts @@ -124,6 +124,7 @@ function getRustFlags(config: ProjectConfig) { flags.push(formatArrayFlag("rust-libraries", config.rustLibraries)); flags.push(`--rust-logging ${config.rustLogging}`); flags.push(`--rust-error-handling ${config.rustErrorHandling}`); + flags.push(`--rust-caching ${config.rustCaching}`); appendSharedNonTypeScriptFlags(flags, config); appendCommonFlags(flags, config); diff --git a/apps/cli/test/__snapshots__/template-snapshots.test.ts.snap b/apps/cli/test/__snapshots__/template-snapshots.test.ts.snap index 73c2428df..38588ace0 100644 --- a/apps/cli/test/__snapshots__/template-snapshots.test.ts.snap +++ b/apps/cli/test/__snapshots__/template-snapshots.test.ts.snap @@ -11139,6 +11139,7 @@ exports[`Template Snapshots - Rust Ecosystem Rust File Structure Snapshots file "crates/client/src/lib.rs", "crates/client/style/main.css", "crates/server/Cargo.toml", + "crates/server/src/cache.rs", "crates/server/src/error.rs", "crates/server/src/main.rs", "rust-toolchain.toml", @@ -11156,6 +11157,7 @@ exports[`Template Snapshots - Rust Ecosystem Rust File Structure Snapshots file "crates/dioxus-client/assets/main.css", "crates/dioxus-client/src/main.rs", "crates/server/Cargo.toml", + "crates/server/src/cache.rs", "crates/server/src/error.rs", "crates/server/src/main.rs", "rust-toolchain.toml", @@ -11171,6 +11173,7 @@ exports[`Template Snapshots - Rust Ecosystem Rust File Structure Snapshots file "crates/cli/Cargo.toml", "crates/cli/src/main.rs", "crates/server/Cargo.toml", + "crates/server/src/cache.rs", "crates/server/src/error.rs", "crates/server/src/main.rs", "rust-toolchain.toml", @@ -11184,6 +11187,7 @@ exports[`Template Snapshots - Rust Ecosystem Rust File Structure Snapshots file "Cargo.toml", "README.md", "crates/server/Cargo.toml", + "crates/server/src/cache.rs", "crates/server/src/error.rs", "crates/server/src/main.rs", "rust-toolchain.toml", @@ -11197,6 +11201,21 @@ exports[`Template Snapshots - Rust Ecosystem Rust File Structure Snapshots file "Cargo.toml", "README.md", "crates/server/Cargo.toml", + "crates/server/src/cache.rs", + "crates/server/src/error.rs", + "crates/server/src/main.rs", + "rust-toolchain.toml", +] +`; + +exports[`Template Snapshots - Rust Ecosystem Rust File Structure Snapshots file structure: axum-moka-cache 1`] = ` +[ + ".env.example", + "CLAUDE.md", + "Cargo.toml", + "README.md", + "crates/server/Cargo.toml", + "crates/server/src/cache.rs", "crates/server/src/error.rs", "crates/server/src/main.rs", "rust-toolchain.toml", @@ -11205,7 +11224,7 @@ exports[`Template Snapshots - Rust Ecosystem Rust File Structure Snapshots file exports[`Template Snapshots - Rust Ecosystem Rust Key File Content Snapshots key files: axum-leptos-seaorm 1`] = ` { - "fileCount": 14, + "fileCount": 15, "files": [ { "content": @@ -11288,6 +11307,7 @@ web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlE + [profile.dev] opt-level = 0 @@ -11494,6 +11514,7 @@ sea-orm.workspace = true + [[bin]] name = "server" path = "src/main.rs" @@ -11502,6 +11523,10 @@ path = "src/main.rs" , "path": "crates/server/Cargo.toml", }, + { + "content": "[exists]", + "path": "crates/server/src/cache.rs", + }, { "content": "[exists]", "path": "crates/server/src/error.rs", @@ -11555,6 +11580,7 @@ async fn main() -> anyhow::Result<()> { .with(tracing_subscriber::fmt::layer()) .init(); + // Initialize database connection let database_url = std::env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); @@ -11602,7 +11628,7 @@ async fn main() -> anyhow::Result<()> { exports[`Template Snapshots - Rust Ecosystem Rust Key File Content Snapshots key files: actix-dioxus-sqlx 1`] = ` { - "fileCount": 13, + "fileCount": 14, "files": [ { "content": @@ -11682,6 +11708,7 @@ wasm-bindgen = "0.2" # Validation validator = { version = "0.19", features = ["derive"] } + [profile.dev] opt-level = 0 @@ -11878,6 +11905,7 @@ sqlx.workspace = true validator.workspace = true + [[bin]] name = "server" path = "src/main.rs" @@ -11886,6 +11914,10 @@ path = "src/main.rs" , "path": "crates/server/Cargo.toml", }, + { + "content": "[exists]", + "path": "crates/server/src/cache.rs", + }, { "content": "[exists]", "path": "crates/server/src/error.rs", @@ -11937,6 +11969,7 @@ async fn main() -> anyhow::Result<()> { .with(tracing_subscriber::fmt::layer()) .init(); + // Initialize database connection pool let database_url = std::env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); @@ -11990,7 +12023,7 @@ async fn main() -> anyhow::Result<()> { exports[`Template Snapshots - Rust Ecosystem Rust Key File Content Snapshots key files: cli-clap 1`] = ` { - "fileCount": 11, + "fileCount": 12, "files": [ { "content": @@ -12058,6 +12091,7 @@ dotenvy = "0.15" clap = { version = "4", features = ["derive"] } + [profile.dev] opt-level = 0 @@ -12324,6 +12358,7 @@ dotenvy.workspace = true # CLI clap.workspace = true + [[bin]] name = "server" path = "src/main.rs" @@ -12332,6 +12367,10 @@ path = "src/main.rs" , "path": "crates/server/Cargo.toml", }, + { + "content": "[exists]", + "path": "crates/server/src/cache.rs", + }, { "content": "[exists]", "path": "crates/server/src/error.rs", @@ -12352,6 +12391,7 @@ async fn main() -> anyhow::Result<()> { .with(tracing_subscriber::fmt::layer()) .init(); + tracing::info!("Hello from snapshot-rust-cli-clap!"); tracing::info!("Add a web framework (axum or actix-web) to start building your API."); @@ -12375,7 +12415,7 @@ async fn main() -> anyhow::Result<()> { exports[`Template Snapshots - Rust Ecosystem Rust Key File Content Snapshots key files: axum-envlogger 1`] = ` { - "fileCount": 9, + "fileCount": 10, "files": [ { "content": @@ -12446,6 +12486,7 @@ sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "sqlite", "my + [profile.dev] opt-level = 0 @@ -12499,6 +12540,7 @@ sqlx.workspace = true + [[bin]] name = "server" path = "src/main.rs" @@ -12507,6 +12549,10 @@ path = "src/main.rs" , "path": "crates/server/Cargo.toml", }, + { + "content": "[exists]", + "path": "crates/server/src/cache.rs", + }, { "content": "[exists]", "path": "crates/server/src/error.rs", @@ -12556,6 +12602,7 @@ async fn main() -> anyhow::Result<()> { // Initialize logging env_logger::init(); + // Initialize database connection pool let database_url = std::env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); @@ -12606,7 +12653,7 @@ async fn main() -> anyhow::Result<()> { exports[`Template Snapshots - Rust Ecosystem Rust Key File Content Snapshots key files: axum-eyre 1`] = ` { - "fileCount": 9, + "fileCount": 10, "files": [ { "content": @@ -12675,6 +12722,7 @@ tower-http = { version = "0.6", features = ["cors", "trace"] } + [profile.dev] opt-level = 0 @@ -12726,6 +12774,7 @@ tower-http.workspace = true + [[bin]] name = "server" path = "src/main.rs" @@ -12734,6 +12783,10 @@ path = "src/main.rs" , "path": "crates/server/Cargo.toml", }, + { + "content": "[exists]", + "path": "crates/server/src/cache.rs", + }, { "content": "[exists]", "path": "crates/server/src/error.rs", @@ -12774,6 +12827,223 @@ async fn main() -> eyre::Result<()> { .with(tracing_subscriber::fmt::layer()) .init(); + + // Build router + let app = Router::new() + .route("/health", get(health)) + .layer(CorsLayer::permissive()); + + // Get host and port from environment + let host = std::env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string()); + let addr = format!("{}:{}", host, port); + + tracing::info!("Starting HTTP server at http://{}", addr); + + // Start server + let listener = tokio::net::TcpListener::bind(&addr).await?; + axum::serve(listener, app).await?; + + Ok(()) +} +" +, + "path": "crates/server/src/main.rs", + }, + { + "content": "[exists]", + "path": "README.md", + }, + { + "content": "[exists]", + "path": "rust-toolchain.toml", + }, + ], +} +`; + +exports[`Template Snapshots - Rust Ecosystem Rust Key File Content Snapshots key files: axum-moka-cache 1`] = ` +{ + "fileCount": 10, + "files": [ + { + "content": +"# Application +RUST_LOG=debug +APP_ENV=development + +# Server +HOST=127.0.0.1 +PORT=3000 + +# gRPC (if using tonic) +# GRPC_PORT=50051 + +# Database (if using) +# DATABASE_URL=postgres://user:password@localhost:5432/dbname +# DATABASE_URL=sqlite:./data.db + +# JWT Secret (if using jsonwebtoken) +# JWT_SECRET=your-secret-key-here +" +, + "path": ".env.example", + }, + { + "content": +"[workspace] +resolver = "2" +members = [ + "crates/server", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +authors = ["Your Name "] +license = "MIT" +repository = "" + +[workspace.dependencies] +# Async runtime +tokio = { version = "1.51", features = ["full"] } + +# Serialization +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# Error handling (anyhow + thiserror) +thiserror = "2.0" +anyhow = "1.0" + +# Tracing (used by server logging, CLI, TUI, and WASM frontends) +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# Environment variables +dotenvy = "0.15" + +# Web framework (Axum) +axum = "0.8" +tower = "0.5" +tower-http = { version = "0.6", features = ["cors", "trace"] } + + + + + + +# Caching (Moka) +moka = { version = "0.12", features = ["future"] } + +[profile.dev] +opt-level = 0 + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +" +, + "path": "Cargo.toml", + }, + { + "content": "[exists]", + "path": "CLAUDE.md", + }, + { + "content": +"[package] +name = "snapshot-rust-axum-moka-cache-server" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +# Async runtime +tokio.workspace = true + +# Serialization +serde.workspace = true +serde_json.workspace = true + +# Error handling +thiserror.workspace = true +anyhow.workspace = true + +# Logging +tracing.workspace = true +tracing-subscriber.workspace = true + +# Environment +dotenvy.workspace = true + +# Web framework +axum.workspace = true +tower.workspace = true +tower-http.workspace = true + + + + +# Caching +moka.workspace = true + +[[bin]] +name = "server" +path = "src/main.rs" + +" +, + "path": "crates/server/Cargo.toml", + }, + { + "content": "[exists]", + "path": "crates/server/src/cache.rs", + }, + { + "content": "[exists]", + "path": "crates/server/src/error.rs", + }, + { + "content": +"use axum::{routing::get, Json, Router}; +use serde::Serialize; +use tower_http::cors::CorsLayer; +mod error; +mod cache; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +#[derive(Serialize)] +struct HealthResponse { + status: &'static str, + message: &'static str, +} + +async fn health() -> Json { + Json(HealthResponse { + status: "ok", + message: "Server is running", + }) +} + + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Load environment variables + dotenvy::dotenv().ok(); + + // Initialize tracing + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "debug".into())) + .with(tracing_subscriber::fmt::layer()) + .init(); + + // Initialize in-memory cache + let _cache = cache::create_cache::(); + tracing::info!("Moka in-memory cache initialized"); + // Build router let app = Router::new() .route("/health", get(health)) diff --git a/apps/cli/test/generate-reproducible-command.test.ts b/apps/cli/test/generate-reproducible-command.test.ts index f6a4aa0eb..92bde28b3 100644 --- a/apps/cli/test/generate-reproducible-command.test.ts +++ b/apps/cli/test/generate-reproducible-command.test.ts @@ -62,6 +62,7 @@ function makeConfig(overrides: Partial = {}): ProjectConfig { rustCli: "none", rustLogging: "tracing", rustErrorHandling: "anyhow-thiserror", + rustCaching: "none", rustLibraries: [], pythonWebFramework: "none", pythonOrm: "none", @@ -228,6 +229,7 @@ describe("generateReproducibleCommand", () => { rustCli: "clap", rustLogging: "tracing", rustErrorHandling: "anyhow-thiserror", + rustCaching: "none", rustLibraries: ["serde", "validator"], aiDocs: [], }); @@ -243,6 +245,7 @@ describe("generateReproducibleCommand", () => { "--rust-libraries serde validator " + "--rust-logging tracing " + "--rust-error-handling anyhow-thiserror " + + "--rust-caching none " + "--addons none " + "--examples none " + "--db-setup none " + diff --git a/apps/cli/test/rust-ecosystem.test.ts b/apps/cli/test/rust-ecosystem.test.ts index aa45c5088..d2e90acae 100644 --- a/apps/cli/test/rust-ecosystem.test.ts +++ b/apps/cli/test/rust-ecosystem.test.ts @@ -2631,4 +2631,95 @@ describe("Rust Ecosystem", () => { expect(cargoContent).toContain('thiserror = "2.0"'); }); }); + + describe("Rust Caching Option", () => { + it("should include moka deps when rustCaching is moka", async () => { + const result = await createVirtual({ + projectName: "rust-moka", + ecosystem: "rust", + rustWebFramework: "axum", + rustFrontend: "none", + rustOrm: "none", + rustApi: "none", + rustCli: "none", + rustLibraries: [], + rustLogging: "tracing", + rustErrorHandling: "anyhow-thiserror", + rustCaching: "moka", + }); + + expect(result.success).toBe(true); + const root = result.tree!.root; + + const cargoContent = getFileContent(root, "Cargo.toml"); + expect(cargoContent).toContain("moka"); + + const serverCargoContent = getFileContent(root, "crates/server/Cargo.toml"); + expect(serverCargoContent).toContain("moka.workspace"); + + expect(hasFile(root, "crates/server/src/cache.rs")).toBe(true); + const cacheContent = getFileContent(root, "crates/server/src/cache.rs"); + expect(cacheContent).toContain("moka::future::Cache"); + + const mainContent = getFileContent(root, "crates/server/src/main.rs"); + expect(mainContent).toContain("mod cache;"); + }); + + it("should include redis deps when rustCaching is redis", async () => { + const result = await createVirtual({ + projectName: "rust-redis", + ecosystem: "rust", + rustWebFramework: "actix-web", + rustFrontend: "none", + rustOrm: "none", + rustApi: "none", + rustCli: "none", + rustLibraries: [], + rustLogging: "tracing", + rustErrorHandling: "anyhow-thiserror", + rustCaching: "redis", + }); + + expect(result.success).toBe(true); + const root = result.tree!.root; + + const cargoContent = getFileContent(root, "Cargo.toml"); + expect(cargoContent).toContain("redis"); + + const serverCargoContent = getFileContent(root, "crates/server/Cargo.toml"); + expect(serverCargoContent).toContain("redis.workspace"); + + expect(hasFile(root, "crates/server/src/cache.rs")).toBe(true); + const cacheContent = getFileContent(root, "crates/server/src/cache.rs"); + expect(cacheContent).toContain("redis::Client"); + + const mainContent = getFileContent(root, "crates/server/src/main.rs"); + expect(mainContent).toContain("mod cache;"); + }); + + it("should not include caching deps when rustCaching is none", async () => { + const result = await createVirtual({ + projectName: "rust-nocache", + ecosystem: "rust", + rustWebFramework: "axum", + rustFrontend: "none", + rustOrm: "none", + rustApi: "none", + rustCli: "none", + rustLibraries: [], + rustLogging: "tracing", + rustErrorHandling: "anyhow-thiserror", + rustCaching: "none", + }); + + expect(result.success).toBe(true); + const root = result.tree!.root; + + const cargoContent = getFileContent(root, "Cargo.toml"); + expect(cargoContent).not.toContain("moka"); + + const mainContent = getFileContent(root, "crates/server/src/main.rs"); + expect(mainContent).not.toContain("mod cache;"); + }); + }); }); diff --git a/apps/cli/test/template-snapshots.test.ts b/apps/cli/test/template-snapshots.test.ts index 2b754058e..84a81b4ad 100644 --- a/apps/cli/test/template-snapshots.test.ts +++ b/apps/cli/test/template-snapshots.test.ts @@ -327,6 +327,19 @@ describe("Template Snapshots - Rust Ecosystem", () => { rustLibraries: ["serde"] as const, }, }, + { + name: "axum-moka-cache", + config: { + ecosystem: "rust" as const, + rustWebFramework: "axum" as const, + rustFrontend: "none" as const, + rustOrm: "none" as const, + rustApi: "none" as const, + rustCli: "none" as const, + rustCaching: "moka" as const, + rustLibraries: ["serde"] as const, + }, + }, ]; describe("Rust File Structure Snapshots", () => { diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts index d8fdf195b..fefd56df3 100644 --- a/apps/web/src/lib/constant.ts +++ b/apps/web/src/lib/constant.ts @@ -2779,6 +2779,32 @@ export const TECH_OPTIONS: Record< default: false, }, ], + rustCaching: [ + { + id: "moka", + name: "Moka", + description: "High-performance concurrent in-memory cache inspired by Java's Caffeine", + icon: "", + color: "from-amber-500 to-yellow-600", + default: false, + }, + { + id: "redis", + name: "Redis", + description: "Official Redis client for Rust with async support and connection pooling", + icon: "https://cdn.simpleicons.org/redis/DC382D", + color: "from-red-500 to-red-700", + default: false, + }, + { + id: "none", + name: "None", + description: "No caching library", + icon: "", + color: "from-gray-400 to-gray-600", + default: true, + }, + ], // Python ecosystem options pythonWebFramework: [ { @@ -3220,6 +3246,7 @@ export const ECOSYSTEM_CATEGORIES: Record = { "rustLibraries", "rustLogging", "rustErrorHandling", + "rustCaching", "aiDocs", "git", "install", diff --git a/apps/web/src/lib/preview-config.ts b/apps/web/src/lib/preview-config.ts index 02a859073..8f3194a1f 100644 --- a/apps/web/src/lib/preview-config.ts +++ b/apps/web/src/lib/preview-config.ts @@ -104,6 +104,7 @@ export function stackStateToProjectConfig(input: Partial): ProjectCo rustCli: stack.rustCli as ProjectConfig["rustCli"], rustLogging: stack.rustLogging as ProjectConfig["rustLogging"], rustErrorHandling: stack.rustErrorHandling as ProjectConfig["rustErrorHandling"], + rustCaching: stack.rustCaching as ProjectConfig["rustCaching"], rustLibraries: stack.rustLibraries === "none" ? [] diff --git a/apps/web/src/lib/stack-defaults.ts b/apps/web/src/lib/stack-defaults.ts index c89233f6e..c835a0821 100644 --- a/apps/web/src/lib/stack-defaults.ts +++ b/apps/web/src/lib/stack-defaults.ts @@ -62,6 +62,7 @@ export type StackState = { rustLibraries: string; rustLogging: string; rustErrorHandling: string; + rustCaching: string; pythonWebFramework: string; pythonOrm: string; pythonValidation: string; @@ -138,6 +139,7 @@ export const DEFAULT_STACK: StackState = { rustLibraries: "serde", rustLogging: "tracing", rustErrorHandling: "anyhow-thiserror", + rustCaching: "none", pythonWebFramework: "fastapi", pythonOrm: "sqlalchemy", pythonValidation: "pydantic", diff --git a/apps/web/src/lib/stack-url-keys.ts b/apps/web/src/lib/stack-url-keys.ts index 617045187..276ff3f17 100644 --- a/apps/web/src/lib/stack-url-keys.ts +++ b/apps/web/src/lib/stack-url-keys.ts @@ -63,6 +63,7 @@ export const stackUrlKeys = { rustLibraries: "rlib", rustLogging: "rlog", rustErrorHandling: "reh", + rustCaching: "rca", pythonWebFramework: "pwf", pythonOrm: "porm", pythonValidation: "pval", diff --git a/apps/web/src/lib/stack-url-state.ts b/apps/web/src/lib/stack-url-state.ts index e8ab0a9a4..01d62bba8 100644 --- a/apps/web/src/lib/stack-url-state.ts +++ b/apps/web/src/lib/stack-url-state.ts @@ -98,6 +98,7 @@ export function loadStackParams( rustLibraries: getString("rustLibraries", DEFAULT_STACK.rustLibraries), rustLogging: getString("rustLogging", DEFAULT_STACK.rustLogging), rustErrorHandling: getString("rustErrorHandling", DEFAULT_STACK.rustErrorHandling), + rustCaching: getString("rustCaching", DEFAULT_STACK.rustCaching), pythonWebFramework: getString("pythonWebFramework", DEFAULT_STACK.pythonWebFramework), pythonOrm: getString("pythonOrm", DEFAULT_STACK.pythonOrm), pythonValidation: getString("pythonValidation", DEFAULT_STACK.pythonValidation), @@ -198,6 +199,7 @@ export function serializeStackParams(basePath: string, stack: StackState): strin addParam("rustLibraries", stack.rustLibraries); addParam("rustLogging", stack.rustLogging); addParam("rustErrorHandling", stack.rustErrorHandling); + addParam("rustCaching", stack.rustCaching); addParam("pythonWebFramework", stack.pythonWebFramework); addParam("pythonOrm", stack.pythonOrm); addParam("pythonValidation", stack.pythonValidation); @@ -283,6 +285,7 @@ function searchToStack(search: StackSearchParams | undefined): StackState { rustLibraries: search.rlib ?? DEFAULT_STACK.rustLibraries, rustLogging: search.rlog ?? DEFAULT_STACK.rustLogging, rustErrorHandling: search.reh ?? DEFAULT_STACK.rustErrorHandling, + rustCaching: search.rca ?? DEFAULT_STACK.rustCaching, pythonWebFramework: search.pwf ?? DEFAULT_STACK.pythonWebFramework, pythonOrm: search.porm ?? DEFAULT_STACK.pythonOrm, pythonValidation: search.pval ?? DEFAULT_STACK.pythonValidation, diff --git a/apps/web/src/lib/stack-utils.ts b/apps/web/src/lib/stack-utils.ts index 68d0097bd..cae093b67 100644 --- a/apps/web/src/lib/stack-utils.ts +++ b/apps/web/src/lib/stack-utils.ts @@ -73,6 +73,7 @@ const RUST_CATEGORY_ORDER: Array = [ "rustLibraries", "rustLogging", "rustErrorHandling", + "rustCaching", "aiDocs", "git", "install", @@ -322,6 +323,9 @@ function generateRustCommand(stack: StackState, projectName: string) { if (stack.rustErrorHandling !== "anyhow-thiserror") { flags.push(`--rust-error-handling ${stack.rustErrorHandling}`); } + if (stack.rustCaching !== "none") { + flags.push(`--rust-caching ${stack.rustCaching}`); + } if (stack.aiDocs.length > 0 && !stack.aiDocs.includes("none")) { flags.push(`--ai-docs ${stack.aiDocs.join(" ")}`); } diff --git a/apps/web/src/lib/tech-icons.ts b/apps/web/src/lib/tech-icons.ts index e2e07a2e0..1320c33e6 100644 --- a/apps/web/src/lib/tech-icons.ts +++ b/apps/web/src/lib/tech-icons.ts @@ -322,6 +322,7 @@ export const ICON_REGISTRY: Record = { "env-logger": { type: "si", slug: "rust", hex: "CE422B" }, "anyhow-thiserror": { type: "si", slug: "rust", hex: "CE422B" }, eyre: { type: "si", slug: "rust", hex: "CE422B" }, + moka: { type: "si", slug: "rust", hex: "CE422B" }, // ─── Python ──────────────────────────────────────────────────────────────── fastapi: { type: "si", slug: "fastapi", hex: "009688" }, diff --git a/apps/web/src/lib/tech-resource-links.ts b/apps/web/src/lib/tech-resource-links.ts index e820a80df..d2ce29746 100644 --- a/apps/web/src/lib/tech-resource-links.ts +++ b/apps/web/src/lib/tech-resource-links.ts @@ -677,6 +677,10 @@ const BASE_LINKS: LinkMap = { docsUrl: "https://docs.rs/eyre/latest/eyre/", githubUrl: "https://github.com/eyre-rs/eyre", }, + moka: { + docsUrl: "https://docs.rs/moka/latest/moka/", + githubUrl: "https://github.com/moka-rs/moka", + }, clap: { docsUrl: "https://docs.rs/clap/latest/clap/", githubUrl: "https://github.com/clap-rs/clap", diff --git a/packages/template-generator/src/processors/readme-generator.ts b/packages/template-generator/src/processors/readme-generator.ts index d66bd1dc8..45b4e363e 100644 --- a/packages/template-generator/src/processors/readme-generator.ts +++ b/packages/template-generator/src/processors/readme-generator.ts @@ -820,6 +820,14 @@ function generateRustReadmeContent(config: ProjectConfig): string { features.push("- **eyre + color-eyre** - Customizable error reports with pretty backtraces"); } + // Caching + const { rustCaching } = config; + if (rustCaching === "moka") { + features.push("- **Moka** - High-performance concurrent in-memory cache (Caffeine-inspired)"); + } else if (rustCaching === "redis") { + features.push("- **Redis** - Redis client with async support and connection pooling"); + } + // Project structure const structure: string[] = [`${projectName}/`, "├── Cargo.toml # Workspace manifest"]; diff --git a/packages/template-generator/templates/rust-base/Cargo.toml.hbs b/packages/template-generator/templates/rust-base/Cargo.toml.hbs index 9bdfc84b4..3701f77dd 100644 --- a/packages/template-generator/templates/rust-base/Cargo.toml.hbs +++ b/packages/template-generator/templates/rust-base/Cargo.toml.hbs @@ -156,6 +156,15 @@ tokio-test = "0.4" mockall = "0.13" {{/if}} +{{#if (eq rustCaching "moka")}} +# Caching (Moka) +moka = { version = "0.12", features = ["future"] } +{{/if}} +{{#if (eq rustCaching "redis")}} +# Caching (Redis) +redis = { version = "0.29", features = ["aio", "tokio-comp"] } +{{/if}} + [profile.dev] opt-level = 0 diff --git a/packages/template-generator/templates/rust-base/crates/server/Cargo.toml.hbs b/packages/template-generator/templates/rust-base/crates/server/Cargo.toml.hbs index 6deadcecb..de9342123 100644 --- a/packages/template-generator/templates/rust-base/crates/server/Cargo.toml.hbs +++ b/packages/template-generator/templates/rust-base/crates/server/Cargo.toml.hbs @@ -97,6 +97,15 @@ argon2.workspace = true clap.workspace = true {{/if}} +{{#if (eq rustCaching "moka")}} +# Caching +moka.workspace = true +{{/if}} +{{#if (eq rustCaching "redis")}} +# Caching +redis.workspace = true +{{/if}} + [[bin]] name = "server" path = "src/main.rs" diff --git a/packages/template-generator/templates/rust-base/crates/server/src/cache.rs.hbs b/packages/template-generator/templates/rust-base/crates/server/src/cache.rs.hbs new file mode 100644 index 000000000..7b40eeaf8 --- /dev/null +++ b/packages/template-generator/templates/rust-base/crates/server/src/cache.rs.hbs @@ -0,0 +1,31 @@ +{{#if (eq rustCaching "moka")}} +use moka::future::Cache; +use std::time::Duration; + +/// Create a new in-memory cache with default settings. +/// +/// - Maximum capacity: 10,000 entries +/// - Time to live: 10 minutes +/// - Time to idle: 5 minutes +pub fn create_cache() -> Cache { + Cache::builder() + .max_capacity(10_000) + .time_to_live(Duration::from_secs(600)) + .time_to_idle(Duration::from_secs(300)) + .build() +} +{{else if (eq rustCaching "redis")}} +use redis::Client; + +/// Create a new Redis client from the `REDIS_URL` environment variable. +/// +/// Defaults to `redis://127.0.0.1/` if `REDIS_URL` is not set. +/// +/// # Errors +/// +/// Returns `redis::RedisError` if the connection string is invalid. +pub fn create_redis_client() -> redis::RedisResult { + let redis_url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1/".to_string()); + Client::open(redis_url) +} +{{/if}} diff --git a/packages/template-generator/templates/rust-base/crates/server/src/main.rs.hbs b/packages/template-generator/templates/rust-base/crates/server/src/main.rs.hbs index 6d69d8a9a..213c124a0 100644 --- a/packages/template-generator/templates/rust-base/crates/server/src/main.rs.hbs +++ b/packages/template-generator/templates/rust-base/crates/server/src/main.rs.hbs @@ -5,6 +5,9 @@ use tower_http::cors::CorsLayer; {{#if (ne rustErrorHandling "none")}} mod error; {{/if}} +{{#if (ne rustCaching "none")}} +mod cache; +{{/if}} {{#if (eq rustLogging "tracing")}} use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; {{/if}} @@ -142,6 +145,17 @@ async fn main() -> {{#if (eq rustErrorHandling "anyhow-thiserror")}}anyhow::Resu env_logger::init(); {{/if}} +{{#if (eq rustCaching "moka")}} + // Initialize in-memory cache + let _cache = cache::create_cache::(); +{{#if (eq rustLogging "tracing")}} tracing::info!("Moka in-memory cache initialized");{{else if (eq rustLogging "env-logger")}} log::info!("Moka in-memory cache initialized");{{else}} println!("Moka in-memory cache initialized");{{/if}} +{{/if}} +{{#if (eq rustCaching "redis")}} + // Initialize Redis client + let _redis_client = cache::create_redis_client()?; +{{#if (eq rustLogging "tracing")}} tracing::info!("Redis client initialized");{{else if (eq rustLogging "env-logger")}} log::info!("Redis client initialized");{{else}} println!("Redis client initialized");{{/if}} +{{/if}} + {{#if (eq rustOrm "sea-orm")}} // Initialize database connection let database_url = std::env::var("DATABASE_URL") @@ -314,6 +328,9 @@ use serde::Serialize; {{#if (ne rustErrorHandling "none")}} mod error; {{/if}} +{{#if (ne rustCaching "none")}} +mod cache; +{{/if}} {{#if (eq rustLogging "tracing")}} use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; {{/if}} @@ -456,6 +473,17 @@ async fn main() -> {{#if (eq rustErrorHandling "anyhow-thiserror")}}anyhow::Resu env_logger::init(); {{/if}} +{{#if (eq rustCaching "moka")}} + // Initialize in-memory cache + let _cache = cache::create_cache::(); +{{#if (eq rustLogging "tracing")}} tracing::info!("Moka in-memory cache initialized");{{else if (eq rustLogging "env-logger")}} log::info!("Moka in-memory cache initialized");{{else}} println!("Moka in-memory cache initialized");{{/if}} +{{/if}} +{{#if (eq rustCaching "redis")}} + // Initialize Redis client + let _redis_client = cache::create_redis_client()?; +{{#if (eq rustLogging "tracing")}} tracing::info!("Redis client initialized");{{else if (eq rustLogging "env-logger")}} log::info!("Redis client initialized");{{else}} println!("Redis client initialized");{{/if}} +{{/if}} + {{#if (eq rustOrm "sea-orm")}} // Initialize database connection let database_url = std::env::var("DATABASE_URL") @@ -635,6 +663,9 @@ async fn main() -> {{#if (eq rustErrorHandling "anyhow-thiserror")}}anyhow::Resu } {{else}} {{#if (ne rustErrorHandling "none")}} +{{#if (ne rustCaching "none")}} +mod cache; +{{/if}} mod error; {{/if}} {{#if (eq rustLogging "tracing")}} @@ -666,6 +697,17 @@ async fn main() -> {{#if (eq rustErrorHandling "anyhow-thiserror")}}anyhow::Resu env_logger::init(); {{/if}} +{{#if (eq rustCaching "moka")}} + // Initialize in-memory cache + let _cache = cache::create_cache::(); +{{#if (eq rustLogging "tracing")}} tracing::info!("Moka in-memory cache initialized");{{else if (eq rustLogging "env-logger")}} log::info!("Moka in-memory cache initialized");{{else}} println!("Moka in-memory cache initialized");{{/if}} +{{/if}} +{{#if (eq rustCaching "redis")}} + // Initialize Redis client + let _redis_client = cache::create_redis_client()?; +{{#if (eq rustLogging "tracing")}} tracing::info!("Redis client initialized");{{else if (eq rustLogging "env-logger")}} log::info!("Redis client initialized");{{else}} println!("Redis client initialized");{{/if}} +{{/if}} + {{#if (eq rustApi "tonic")}} // Get host and port from environment let host = std::env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); diff --git a/packages/types/src/compatibility.ts b/packages/types/src/compatibility.ts index 6e97ca13b..082b077bd 100644 --- a/packages/types/src/compatibility.ts +++ b/packages/types/src/compatibility.ts @@ -150,6 +150,7 @@ export type CompatibilityInput = { rustLibraries: string; rustLogging: string; rustErrorHandling: string; + rustCaching: string; pythonWebFramework: string; pythonOrm: string; pythonValidation: string; @@ -302,6 +303,7 @@ export const getCategoryDisplayName = (categoryKey: string): string => { rustLibraries: "Rust Core Libraries", rustLogging: "Rust Logging", rustErrorHandling: "Rust Error Handling", + rustCaching: "Rust Caching", }; // Custom display names for Python categories diff --git a/packages/types/src/option-metadata.ts b/packages/types/src/option-metadata.ts index c915bae82..ac0a85b89 100644 --- a/packages/types/src/option-metadata.ts +++ b/packages/types/src/option-metadata.ts @@ -45,6 +45,7 @@ import { RUST_LIBRARIES_VALUES, RUST_LOGGING_VALUES, RUST_ERROR_HANDLING_VALUES, + RUST_CACHING_VALUES, RUST_ORM_VALUES, RUST_WEB_FRAMEWORK_VALUES, SEARCH_VALUES, @@ -123,6 +124,7 @@ export type OptionCategory = | "rustLibraries" | "rustLogging" | "rustErrorHandling" + | "rustCaching" | "pythonWebFramework" | "pythonOrm" | "pythonValidation" @@ -296,6 +298,7 @@ const CATEGORY_VALUE_IDS: Record = { rustLibraries: RUST_LIBRARIES_VALUES, rustLogging: RUST_LOGGING_VALUES, rustErrorHandling: RUST_ERROR_HANDLING_VALUES, + rustCaching: RUST_CACHING_VALUES, pythonWebFramework: PYTHON_WEB_FRAMEWORK_VALUES, pythonOrm: PYTHON_ORM_VALUES, pythonValidation: PYTHON_VALIDATION_VALUES, @@ -559,6 +562,10 @@ const EXACT_LABEL_OVERRIDES: Partial; export type RustLibraries = z.infer; export type RustLogging = z.infer; export type RustErrorHandling = z.infer; +export type RustCaching = z.infer; export type PythonWebFramework = z.infer; export type PythonOrm = z.infer; export type PythonValidation = z.infer; diff --git a/testing/lib/generate-combos/options.ts b/testing/lib/generate-combos/options.ts index d2700fb85..46c7ae116 100644 --- a/testing/lib/generate-combos/options.ts +++ b/testing/lib/generate-combos/options.ts @@ -49,6 +49,7 @@ import { RUST_LIBRARIES_VALUES, RUST_LOGGING_VALUES, RUST_ERROR_HANDLING_VALUES, + RUST_CACHING_VALUES, RUST_ORM_VALUES, RUST_WEB_FRAMEWORK_VALUES, SEARCH_VALUES, @@ -298,6 +299,7 @@ function makeRustDraft(args: GeneratorArgs): CandidateDraft { rustCli: sampleScalar(RUST_CLI_VALUES, 0.3), rustLogging: sampleScalar(RUST_LOGGING_VALUES, 0.15), rustErrorHandling: sampleScalar(RUST_ERROR_HANDLING_VALUES, 0.15), + rustCaching: sampleScalar(RUST_CACHING_VALUES, 0.15), rustLibraries: sampleArray(RUST_LIBRARIES_VALUES, 0.35, 2), }, }; @@ -410,6 +412,7 @@ function createValidationBase(projectName: string, draft: CandidateDraft): Proje rustCli: "none", rustLogging: "tracing", rustErrorHandling: "anyhow-thiserror", + rustCaching: "none", rustLibraries: [], pythonWebFramework: "none", pythonOrm: "none", diff --git a/testing/lib/presets.ts b/testing/lib/presets.ts index 4bd94cabe..07097c7d8 100644 --- a/testing/lib/presets.ts +++ b/testing/lib/presets.ts @@ -56,6 +56,7 @@ export function makeBaseConfig(name: string, ecosystem: Ecosystem): ProjectConfi rustCli: "none", rustLogging: "tracing", rustErrorHandling: "anyhow-thiserror", + rustCaching: "none", rustLibraries: [], pythonWebFramework: "none", pythonOrm: "none",