Some checks failed
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Performance Benchmarks (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
CI/CD Pipeline / Build Docker Image (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Cleanup (push) Has been cancelled
22 KiB
22 KiB
Rustelo Foundation Integration Guide
Overview
The Rustelo Foundation provides a complete ecosystem of library crates for building modern web applications. This guide shows how all foundation crates work together to create powerful, maintainable applications.
Foundation Architecture
Application Layer (Your Implementation)
├── main.rs (imports and uses foundation libraries)
├── build.rs (uses foundation build utilities)
└── Cargo.toml (depends on foundation crates)
Foundation Library Layer
├── server/ # Server-side library with importable main functions
├── client/ # Client-side library with app mounting functions
├── components/ # Reusable UI component library
├── pages/ # Page generation and template system
├── core-lib/ # Shared utilities and business logic
└── core-types/ # Shared type definitions
Complete Application Example
1. Application Structure
my-rustelo-app/
├── Cargo.toml # Workspace and dependencies
├── build.rs # Build-time page generation
├── src/
│ ├── main.rs # Application entry point
│ └── lib.rs # Application library code
├── content/ # Markdown content and configuration
├── templates/ # Page templates
└── public/ # Static assets
2. Cargo.toml - Foundation Dependencies
[package]
name = "my-rustelo-app"
version = "0.1.0"
edition = "2021"
[dependencies]
# Foundation crates
server = { path = "path/to/rustelo/crates/foundation/crates/server" }
client = { path = "path/to/rustelo/crates/foundation/crates/client" }
components = { path = "path/to/rustelo/crates/foundation/crates/components" }
pages = { path = "path/to/rustelo/crates/foundation/crates/pages" }
core-lib = { path = "path/to/rustelo/crates/foundation/crates/core-lib" }
core-types = { path = "path/to/rustelo/crates/foundation/crates/core-types" }
# Leptos framework
leptos = { version = "0.8", features = ["ssr", "hydrate"] }
leptos_router = "0.8"
leptos_axum = "0.8"
# Additional dependencies
tokio = { version = "1.0", features = ["full"] }
axum = "0.8"
[features]
default = []
ssr = ["leptos/ssr", "server/ssr", "pages/ssr", "components/ssr"]
hydrate = ["leptos/hydrate", "client/hydrate", "pages/hydrate", "components/hydrate"]
[[bin]]
name = "server"
path = "src/main.rs"
required-features = ["ssr"]
[lib]
name = "my_rustelo_app"
crate-type = ["cdylib", "rlib"]
3. build.rs - Foundation Build Integration
//! Build script using foundation build utilities
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
// Use server foundation build utilities
server::build::build_foundation()?;
// Use pages foundation for page generation
pages::build::generate_pages_from_content("content/")?;
// Use core-lib for configuration processing
core_lib::build::process_configuration("config/")?;
// Set up rerun conditions
println!("cargo:rerun-if-changed=content/");
println!("cargo:rerun-if-changed=templates/");
println!("cargo:rerun-if-changed=config/");
Ok(())
}
4. src/main.rs - Complete Application Integration
//! Complete Rustelo application using all foundation crates
use server::{run_server_with_config, ServerConfig};
use client::{create_base_app, ClientConfig};
use components::{
navigation::{BrandHeader, Footer},
content::{UnifiedContentCard, ContentManager},
theme::{ThemeProvider, ThemeConfig},
};
use pages::{
HomePage, AboutPage, ContactPage,
content::{ContentIndexPage, ContentCategoryPage},
PostViewerPage,
};
use core_lib::{
config::{load_app_config, AppConfig},
i18n::{setup_translations, TranslationManager},
content::{ContentService, MarkdownProcessor},
};
use core_types::{
ContentItem, Route, User, AppError,
config::{DatabaseConfig, I18nConfig},
};
use leptos::*;
use leptos_router::*;
#[tokio::main]
async fn main() -> Result<(), AppError> {
// 1. Load application configuration using core-lib
let app_config: AppConfig = load_app_config("config/app.toml")?;
// 2. Setup internationalization using core-lib
let translation_manager = setup_translations(&app_config.i18n)?;
// 3. Initialize content service using core-lib
let content_service = ContentService::new(&app_config.content_path)?;
// 4. Configure server using server foundation
let server_config = ServerConfig::builder()
.from_app_config(&app_config)
.content_service(content_service.clone())
.translation_manager(translation_manager.clone())
.enable_features(["content", "auth", "i18n"])
.build();
// 5. Start server using foundation main function
run_server_with_config(server_config).await?;
Ok(())
}
/// Application component integrating all foundation components
#[component]
pub fn App() -> impl IntoView {
// Load app configuration
let app_config = use_context::<AppConfig>().unwrap();
// Setup theme configuration
let theme_config = ThemeConfig::builder()
.from_app_config(&app_config)
.build();
view! {
// Theme provider from components foundation
<ThemeProvider config=theme_config>
// Router setup
<Router>
<div class="min-h-screen flex flex-col">
// Header using components foundation
<AppHeader />
// Main content with routing using pages foundation
<main class="flex-1">
<Routes>
// Home page from pages foundation
<Route path="/" view=HomePage />
// Static pages from pages foundation
<Route path="/about" view=AboutPage />
<Route path="/contact" view=ContactPage />
// Content pages using pages foundation
<Route path="/blog" view=|| {
ContentIndexPage {
content_type: "blog".to_string(),
layout: "grid".to_string(),
per_page: Some(10),
}
} />
<Route path="/blog/:category" view=ContentCategoryPage />
<Route path="/blog/post/:slug" view=|| {
PostViewerPage {
content_type: "blog".to_string(),
show_related: true,
enable_comments: true,
}
} />
// Portfolio section
<Route path="/portfolio" view=|| {
ContentIndexPage {
content_type: "portfolio".to_string(),
layout: "cards".to_string(),
per_page: Some(12),
}
} />
// 404 page
<Route path="/*any" view=pages::NotFoundPage />
</Routes>
</main>
// Footer using components foundation
<AppFooter />
</div>
</Router>
</ThemeProvider>
}
}
/// Application header component
#[component]
fn AppHeader() -> impl IntoView {
let app_config = use_context::<AppConfig>().unwrap();
view! {
<BrandHeader
brand_name=app_config.site_name.clone()
logo_url=app_config.logo_url.clone()
>
// Navigation using components foundation
<components::navigation::NavMenu orientation="horizontal">
<components::ui::SpaLink href="/" active_class="text-blue-600">
"Home"
</components::ui::SpaLink>
<components::ui::SpaLink href="/about" active_class="text-blue-600">
"About"
</components::ui::SpaLink>
<components::ui::SpaLink href="/blog" active_class="text-blue-600">
"Blog"
</components::ui::SpaLink>
<components::ui::SpaLink href="/portfolio" active_class="text-blue-600">
"Portfolio"
</components::ui::SpaLink>
<components::ui::SpaLink href="/contact" active_class="text-blue-600">
"Contact"
</components::ui::SpaLink>
</components::navigation::NavMenu>
// Language selector if i18n enabled
{if app_config.i18n.enabled {
view! {
<components::navigation::LanguageSelector
current_lang=app_config.i18n.default_language.clone()
available_langs=app_config.i18n.supported_languages.clone()
/>
}.into_view()
} else {
view! {}.into_view()
}}
</BrandHeader>
}
}
/// Application footer component
#[component]
fn AppFooter() -> impl IntoView {
let app_config = use_context::<AppConfig>().unwrap();
view! {
<Footer copyright=format!("© 2024 {}", app_config.site_name)>
<div class="grid md:grid-cols-3 gap-8">
// Site links
<div>
<h4 class="font-semibold mb-4">"Site"</h4>
<div class="space-y-2">
<components::ui::SpaLink href="/about" class="block text-gray-600 hover:text-gray-800">
"About"
</components::ui::SpaLink>
<components::ui::SpaLink href="/blog" class="block text-gray-600 hover:text-gray-800">
"Blog"
</components::ui::SpaLink>
<components::ui::SpaLink href="/portfolio" class="block text-gray-600 hover:text-gray-800">
"Portfolio"
</components::ui::SpaLink>
</div>
</div>
// Content links
<div>
<h4 class="font-semibold mb-4">"Content"</h4>
<div class="space-y-2">
<components::ui::SpaLink href="/blog/category/tutorials" class="block text-gray-600 hover:text-gray-800">
"Tutorials"
</components::ui::SpaLink>
<components::ui::SpaLink href="/blog/category/news" class="block text-gray-600 hover:text-gray-800">
"News"
</components::ui::SpaLink>
<components::ui::SpaLink href="/portfolio" class="block text-gray-600 hover:text-gray-800">
"Work"
</components::ui::SpaLink>
</div>
</div>
// Legal links
<div>
<h4 class="font-semibold mb-4">"Legal"</h4>
<div class="space-y-2">
<components::ui::SpaLink href="/privacy" class="block text-gray-600 hover:text-gray-800">
"Privacy Policy"
</components::ui::SpaLink>
<components::ui::SpaLink href="/terms" class="block text-gray-600 hover:text-gray-800">
"Terms of Service"
</components::ui::SpaLink>
</div>
</div>
</div>
</Footer>
}
}
/// Client-side hydration using client foundation
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
use client::{hydrate_app_with_config, ClientConfig};
let client_config = ClientConfig::builder()
.mount_selector("#app")
.enable_hydration(true)
.enable_router(true)
.build();
hydrate_app_with_config(|| view! { <App /> }, client_config);
}
/// Server-side rendering setup
#[cfg(feature = "ssr")]
pub fn render_app() -> String {
leptos::ssr::render_to_string(|| view! { <App /> })
}
Cross-Crate Communication Patterns
1. Configuration Flow
// core-lib loads and validates configuration
let app_config = core_lib::config::load_app_config("config/app.toml")?;
// server uses configuration for setup
let server_config = server::ServerConfig::from_app_config(&app_config);
// components use configuration for theming
let theme_config = components::theme::ThemeConfig::from_app_config(&app_config);
// pages use configuration for content processing
let page_config = pages::PageConfig::from_app_config(&app_config);
2. Content Processing Pipeline
// core-lib processes raw content
let processor = core_lib::content::MarkdownProcessor::new(&app_config.content_path);
let content_items = processor.process_all().await?;
// pages generates pages from processed content
let page_generator = pages::PageGenerator::new()
.with_content_items(content_items)
.with_templates_from_config(&app_config);
// components display the content
let content_display = components::content::ContentManager::new()
.with_content_source(content_items)
.with_layout("grid");
3. Type Safety Across Crates
// core-types defines shared types
use core_types::{ContentItem, User, Route, AppError};
// All crates use the same types for consistency
fn process_content(item: ContentItem) -> Result<ContentItem, AppError> {
// Processing logic
}
fn render_user_profile(user: User) -> impl IntoView {
// Rendering logic
}
fn handle_route(route: Route) -> Result<(), AppError> {
// Routing logic
}
Build System Integration
1. Multi-Stage Build Process
// build.rs orchestrates all foundation build utilities
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Stage 1: Core configuration processing
core_lib::build::process_configuration()?;
// Stage 2: Content processing and validation
core_lib::build::process_content()?;
// Stage 3: Page generation
pages::build::generate_pages()?;
// Stage 4: Route generation
server::build::generate_routes()?;
// Stage 5: Asset processing
process_static_assets()?;
Ok(())
}
2. Feature Flag Coordination
# Features are coordinated across all foundation crates
[features]
default = []
# Core features
auth = ["server/auth", "core-lib/auth", "core-types/auth"]
i18n = ["server/i18n", "client/i18n", "pages/i18n", "core-lib/i18n"]
content-db = ["server/content-db", "pages/content-db", "core-lib/content-db"]
# Rendering features
ssr = ["server/ssr", "pages/ssr", "components/ssr"]
hydrate = ["client/hydrate", "pages/hydrate", "components/hydrate"]
# Development features
dev-tools = ["server/dev-tools", "client/dev-tools"]
Advanced Integration Patterns
1. Plugin Architecture
// Define plugin traits in core-types
pub trait ContentProcessor {
fn process(&self, content: &str) -> Result<String, ProcessingError>;
}
// Implement plugins in various crates
impl ContentProcessor for pages::MarkdownProcessor {
fn process(&self, content: &str) -> Result<String, ProcessingError> {
self.process_markdown(content)
}
}
// Register and use plugins through core-lib
let processor_registry = core_lib::plugins::ProcessorRegistry::new()
.register("markdown", Box::new(pages::MarkdownProcessor::new()))
.register("handlebars", Box::new(templates::HandlebarsProcessor::new()));
2. Event System
// Define events in core-types
#[derive(Debug, Clone)]
pub enum AppEvent {
ContentUpdated(ContentItem),
UserAuthenticated(User),
RouteChanged(Route),
ThemeChanged(String),
}
// Emit events from various crates
// In pages crate
self.event_bus.emit(AppEvent::ContentUpdated(content_item));
// In server crate
self.event_bus.emit(AppEvent::UserAuthenticated(user));
// Listen to events in components
self.event_bus.subscribe(|event| match event {
AppEvent::ThemeChanged(theme) => update_theme(theme),
AppEvent::ContentUpdated(item) => refresh_content_display(item),
_ => {}
});
3. State Management Integration
// Global state defined in core-types
#[derive(Debug, Clone)]
pub struct AppState {
pub user: Option<User>,
pub theme: Theme,
pub language: Language,
pub content_cache: HashMap<String, ContentItem>,
}
// State management in core-lib
pub struct StateManager {
state: RwSignal<AppState>,
event_bus: EventBus,
}
impl StateManager {
pub fn update_user(&self, user: Option<User>) {
self.state.update(|state| state.user = user);
if let Some(user) = user {
self.event_bus.emit(AppEvent::UserAuthenticated(user));
}
}
}
// Use state in components
#[component]
pub fn UserProfile() -> impl IntoView {
let state = use_context::<StateManager>().unwrap();
let user = create_memo(move |_| state.get_user());
view! {
<Show when=move || user.get().is_some()>
// Render user profile
</Show>
}
}
Testing Integration
1. Cross-Crate Testing
// Integration tests that span multiple crates
#[cfg(test)]
mod integration_tests {
use super::*;
#[tokio::test]
async fn test_full_content_pipeline() {
// Use core-lib to load configuration
let config = core_lib::config::load_test_config().await.unwrap();
// Use pages to process content
let content = pages::process_test_content(&config).await.unwrap();
// Use components to render content
let rendered = components::render_test_content(content).await.unwrap();
// Verify the full pipeline
assert!(rendered.contains("expected content"));
}
#[test]
fn test_type_consistency() {
// Verify types work across crate boundaries
let content_item = core_types::ContentItem::new("test", "Test Content");
let processed = core_lib::content::process_item(content_item.clone()).unwrap();
let rendered = components::content::render_item(&processed);
assert_eq!(processed.id, content_item.id);
}
}
2. End-to-End Testing
// E2E tests using all foundation crates together
#[cfg(test)]
mod e2e_tests {
use super::*;
#[tokio::test]
async fn test_complete_application() {
// Start server using server foundation
let server_handle = server::test_utils::start_test_server().await;
// Generate test content using pages foundation
pages::test_utils::generate_test_pages().await.unwrap();
// Test client-side functionality
let client_test = client::test_utils::TestClient::new()
.navigate_to("/")
.expect_content("Welcome")
.navigate_to("/blog")
.expect_content("Blog Posts")
.run()
.await;
assert!(client_test.passed());
// Cleanup
server_handle.stop().await;
}
}
Deployment Integration
1. Production Build
#!/bin/bash
# Build script using all foundation capabilities
# Build server with all features
cargo build --release --features "ssr,auth,content-db,i18n,metrics"
# Build client for WebAssembly
wasm-pack build --target web --features "hydrate,router,i18n"
# Generate static pages
cargo run --bin page-generator --features "generation"
# Process and optimize assets
cargo run --bin asset-processor
# Create deployment package
tar -czf rustelo-app.tar.gz target/release/server pkg/ generated/ public/
2. Docker Integration
# Multi-stage Docker build using foundation crates
FROM rust:1.70 as builder
# Copy foundation source
COPY rustelo/crates/foundation /app/foundation
COPY . /app/src
WORKDIR /app/src
# Build with all production features
RUN cargo build --release --features "production"
# Build client WASM
RUN wasm-pack build --target web --features "hydrate,router,i18n"
FROM nginx:alpine
# Copy server binary
COPY --from=builder /app/src/target/release/server /usr/local/bin/
# Copy static assets and generated pages
COPY --from=builder /app/src/pkg /var/www/html/pkg
COPY --from=builder /app/src/generated /var/www/html/generated
COPY --from=builder /app/src/public /var/www/html/public
# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Start script that runs both server and nginx
COPY start.sh /start.sh
RUN chmod +x /start.sh
CMD ["/start.sh"]
Best Practices for Foundation Integration
1. Dependency Management
- Use workspace dependencies for version consistency
- Enable only the features you need
- Use path dependencies during development
- Switch to published crates for production
2. Configuration Management
- Centralize configuration in core-lib
- Use type-safe configuration structs from core-types
- Validate configuration at startup
- Support environment-specific configs
3. Error Handling
- Define common errors in core-types
- Implement From traits for error conversion
- Use Result types consistently across crates
- Provide meaningful error contexts
4. Performance Optimization
- Use foundation build utilities for optimization
- Enable appropriate feature flags
- Leverage foundation caching mechanisms
- Profile across crate boundaries
5. Testing Strategy
- Test individual crates in isolation
- Write integration tests for crate interactions
- Use foundation test utilities
- Implement E2E testing for complete flows
The Rustelo Foundation provides a complete, integrated development experience where each crate is designed to work seamlessly with the others while maintaining clear separation of concerns and reusability.