696 lines
22 KiB
Markdown
696 lines
22 KiB
Markdown
|
|
# 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
|
||
|
|
```toml
|
||
|
|
[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
|
||
|
|
```rust
|
||
|
|
//! 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
|
||
|
|
```rust
|
||
|
|
//! 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```toml
|
||
|
|
# 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```rust
|
||
|
|
// 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
|
||
|
|
```bash
|
||
|
|
#!/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
|
||
|
|
```dockerfile
|
||
|
|
# 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.
|