diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a68b1c..b47cb62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ See [Upgrading from 2.x to 3.0](docs/upgrading-to-3.0.md) for what to change. - CI checks the feature combinations `full`, `postgres,full`, `mysql,full` and `test-queue`. - The library no longer depends on the `rsa` crate (RUSTSEC-2023-0071), which sqlx 0.9 made optional. It stays in the lockfile only through the CLI and dashboard binaries. +- **`cargo-hammerwork` is a binary only** ([#88](https://github.com/CodingAnarchy/hammerwork/issues/88)). The crate no longer has a library target: `cargo_hammerwork::commands`, `cargo_hammerwork::config`, `cargo_hammerwork::utils` and their glob re-exports are gone. Drive the CLI through the `cargo hammerwork` executable. +- **`hammerwork-web` exposes only `WebDashboard`, `DashboardConfig`, `AuthConfig` and `WebSocketConfig`** ([#88](https://github.com/CodingAnarchy/hammerwork/issues/88)). The `api`, `auth`, `config`, `live`, `security`, `server` and `websocket` modules are private, so `hammerwork_web::server::app_routes` and the other building blocks are no longer public API. `WebSocketConfig` is now re-exported at the crate root (it was `hammerwork_web::config::WebSocketConfig`). ## [2.5.0] - 2026-10-11 diff --git a/CLAUDE.md b/CLAUDE.md index 0cc1c7b..85f2711 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -287,7 +287,7 @@ The `cargo-hammerwork` crate uses **dynamic SQL queries** with `sqlx::query()` r - Database-agnostic queries (PostgreSQL and MySQL) - Runtime query building for CLI flexibility -**Security**: All queries use parameterized statements to prevent SQL injection. Comprehensive tests in `tests/sql_query_tests.rs` validate query correctness. +**Security**: All queries use parameterized statements to prevent SQL injection. Comprehensive tests in `cargo-hammerwork/src/integration_tests/sql_query.rs` validate query correctness. ### Feature Integration with CLI Tooling @@ -309,7 +309,7 @@ The `cargo-hammerwork` crate uses **dynamic SQL queries** with `sqlx::query()` r 3. **CLI Testing Requirements** - **Unit tests for command modules**: Each command module must have inline `#[cfg(test)]` modules - - **SQL Query Validation**: Comprehensive tests in `tests/sql_query_tests.rs` validate all dynamic SQL queries + - **SQL Query Validation**: Comprehensive tests in `cargo-hammerwork/src/integration_tests/sql_query.rs` validate all dynamic SQL queries - **Integration tests**: Database-backed tests with `#[ignore]` attribute for optional execution - **CLI argument parsing tests**: Validate clap command structure and argument validation - **Configuration loading tests**: Test config file loading, environment variable precedence diff --git a/cargo-hammerwork/src/commands/workflow.rs b/cargo-hammerwork/src/commands/workflow.rs index e2365b8..8d9dfbf 100644 --- a/cargo-hammerwork/src/commands/workflow.rs +++ b/cargo-hammerwork/src/commands/workflow.rs @@ -590,6 +590,7 @@ impl WorkflowCommand { Ok(()) } + #[cfg(test)] pub fn print_json_graph(&self, jobs: &[JobNode]) -> Result<()> { println!("{}", render_json_graph(jobs)?); Ok(()) diff --git a/cargo-hammerwork/src/config/mod.rs b/cargo-hammerwork/src/config/mod.rs index 94b88f9..63b501a 100644 --- a/cargo-hammerwork/src/config/mod.rs +++ b/cargo-hammerwork/src/config/mod.rs @@ -18,7 +18,7 @@ //! //! ## Loading Configuration //! -//! ```rust,no_run +//! ```rust,ignore //! use cargo_hammerwork::config::Config; //! //! // Load configuration from file and environment @@ -35,7 +35,7 @@ //! //! ## Creating and Saving Configuration //! -//! ```rust,no_run +//! ```rust,ignore //! use cargo_hammerwork::config::Config; //! //! // Create a new configuration with custom values @@ -51,7 +51,7 @@ //! //! ## Environment Variable Override //! -//! ```rust,no_run +//! ```rust,ignore //! use std::env; //! use cargo_hammerwork::config::Config; //! @@ -107,7 +107,7 @@ use std::path::{Path, PathBuf}; /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// use cargo_hammerwork::config::Config; /// /// // Create configuration with defaults @@ -191,17 +191,18 @@ impl Config { /// /// # Examples /// - /// ```rust,no_run + /// ```rust,ignore /// use cargo_hammerwork::config::Config; /// /// let config = Config::load().expect("Failed to load configuration"); /// println!("Database URL: {:?}", config.database_url); /// ``` + #[cfg(test)] pub fn load() -> Result { Self::load_with(|key| env::var(key).ok()) } - /// [`Config::load`] reading environment variables through `get`. + /// `Config::load` reading environment variables through `get`. /// /// A missing config file means defaults; a file that exists but cannot be read or parsed /// is an error, so a typo in `config.toml` is reported instead of silently ignored. @@ -212,7 +213,7 @@ impl Config { Ok(config) } - /// [`Config::load`], or, when the config file cannot be read or parsed, the defaults + /// `Config::load`, or, when the config file cannot be read or parsed, the defaults /// with the environment variables applied, together with the error. A broken /// `config.toml` must not also drop `DATABASE_URL` and the `HAMMERWORK_*` variables, /// which take precedence over the file. @@ -316,13 +317,14 @@ impl Config { /// /// # Examples /// - /// ```rust,no_run + /// ```rust,ignore /// use cargo_hammerwork::config::Config; /// /// let mut config = Config::default(); /// config.database_url = Some("postgresql://localhost/hammerwork".to_string()); /// config.save().expect("Failed to save configuration"); /// ``` + #[cfg(test)] pub fn save(&self) -> Result<()> { self.save_to(&Self::config_file_path()?) } diff --git a/cargo-hammerwork/tests/graph_output_format_tests.rs b/cargo-hammerwork/src/integration_tests/graph_output_format.rs similarity index 99% rename from cargo-hammerwork/tests/graph_output_format_tests.rs rename to cargo-hammerwork/src/integration_tests/graph_output_format.rs index 0424ea5..4b0c607 100644 --- a/cargo-hammerwork/tests/graph_output_format_tests.rs +++ b/cargo-hammerwork/src/integration_tests/graph_output_format.rs @@ -1,4 +1,4 @@ -use cargo_hammerwork::commands::workflow::{JobNode, WorkflowCommand}; +use crate::commands::workflow::{JobNode, WorkflowCommand}; /// Create a simple workflow command for testing output formats fn create_test_command() -> WorkflowCommand { diff --git a/cargo-hammerwork/tests/job_state_tests.rs b/cargo-hammerwork/src/integration_tests/job_state.rs similarity index 95% rename from cargo-hammerwork/tests/job_state_tests.rs rename to cargo-hammerwork/src/integration_tests/job_state.rs index 6b48593..1419a23 100644 --- a/cargo-hammerwork/tests/job_state_tests.rs +++ b/cargo-hammerwork/src/integration_tests/job_state.rs @@ -3,12 +3,12 @@ //! they fail if a command writes statuses the library does not recognise. //! //! Run with `DATABASE_URL` (Postgres) / `MYSQL_DATABASE_URL` (MySQL) pointing at a -//! migrated database: `cargo test -p cargo-hammerwork --test job_state_tests -- --ignored`. +//! migrated database: `cargo test -p cargo-hammerwork job_state -- --ignored`. -use cargo_hammerwork::commands::batch::BatchCommand; -use cargo_hammerwork::commands::job::JobCommand; -use cargo_hammerwork::config::Config; -use cargo_hammerwork::utils::database::{DatabasePool, JobQueueWrapper}; +use crate::commands::batch::BatchCommand; +use crate::commands::job::JobCommand; +use crate::config::Config; +use crate::utils::database::{DatabasePool, JobQueueWrapper}; use hammerwork::queue::DatabaseQueue; use hammerwork::{Job, JobId, JobStatus}; use serde_json::json; diff --git a/cargo-hammerwork/src/integration_tests/mod.rs b/cargo-hammerwork/src/integration_tests/mod.rs new file mode 100644 index 0000000..f21447d --- /dev/null +++ b/cargo-hammerwork/src/integration_tests/mod.rs @@ -0,0 +1,7 @@ +//! Tests that span several command and utility modules. `cargo-hammerwork` is a binary +//! crate, so these live here instead of in `tests/`, where its internals are not visible. + +mod graph_output_format; +mod job_state; +mod sql_query; +mod workflow_visualization; diff --git a/cargo-hammerwork/tests/sql_query_tests.rs b/cargo-hammerwork/src/integration_tests/sql_query.rs similarity index 96% rename from cargo-hammerwork/tests/sql_query_tests.rs rename to cargo-hammerwork/src/integration_tests/sql_query.rs index c978d4d..8f39c00 100644 --- a/cargo-hammerwork/tests/sql_query_tests.rs +++ b/cargo-hammerwork/src/integration_tests/sql_query.rs @@ -253,24 +253,24 @@ mod error_handling_tests { #[cfg(test)] mod builder_tests { use super::*; - use cargo_hammerwork::commands::archive::build_archive_candidates_query; - use cargo_hammerwork::commands::backup::build_backup_query; - use cargo_hammerwork::commands::cron::build_cron_select_query; - use cargo_hammerwork::commands::job::{build_list_jobs_query, build_purge_query}; - use cargo_hammerwork::commands::maintenance::{VacuumKind, build_vacuum_query}; - use cargo_hammerwork::commands::monitor::{ + use crate::commands::archive::build_archive_candidates_query; + use crate::commands::backup::build_backup_query; + use crate::commands::cron::build_cron_select_query; + use crate::commands::job::{build_list_jobs_query, build_purge_query}; + use crate::commands::maintenance::{VacuumKind, build_vacuum_query}; + use crate::commands::monitor::{ build_avg_time_query, build_recent_jobs_query, build_status_counts_query, build_throughput_query, }; - use cargo_hammerwork::commands::queue::{ + use crate::commands::queue::{ HealthMetric, build_detailed_stats_query, build_health_query, build_queue_total_query, }; - use cargo_hammerwork::commands::spawn::{ + use crate::commands::spawn::{ build_pending_spawns_query, build_spawn_list_query, build_spawn_stats_breakdown_query, build_spawn_stats_total_query, }; - use cargo_hammerwork::utils::job_ops::{JobSelector, StaleRunning}; - use cargo_hammerwork::utils::sql::{Backend, Bind, FAILURE_STATUSES_SQL, bind_mysql, bind_pg}; + use crate::utils::job_ops::{JobSelector, StaleRunning}; + use crate::utils::sql::{Backend, Bind, FAILURE_STATUSES_SQL, bind_mysql, bind_pg}; use hammerwork::{JobPriority, JobStatus}; const HOSTILE: &str = "it's; DROP TABLE hammerwork_jobs --"; @@ -467,8 +467,8 @@ mod builder_tests { #[test] fn test_failure_statuses_include_timed_out_everywhere() { - use cargo_hammerwork::commands::monitor::{HealthCount, build_health_count_query}; - use cargo_hammerwork::commands::queue::{HealthMetric, build_health_query}; + use crate::commands::monitor::{HealthCount, build_health_count_query}; + use crate::commands::queue::{HealthMetric, build_health_query}; for backend in [Backend::Postgres, Backend::MySql] { let (sql, _) = build_health_query(backend, HealthMetric::RecentFailures, None); diff --git a/cargo-hammerwork/tests/workflow_visualization_tests.rs b/cargo-hammerwork/src/integration_tests/workflow_visualization.rs similarity index 99% rename from cargo-hammerwork/tests/workflow_visualization_tests.rs rename to cargo-hammerwork/src/integration_tests/workflow_visualization.rs index f7d47e5..f5a5754 100644 --- a/cargo-hammerwork/tests/workflow_visualization_tests.rs +++ b/cargo-hammerwork/src/integration_tests/workflow_visualization.rs @@ -1,4 +1,4 @@ -use cargo_hammerwork::commands::workflow::{JobNode, WorkflowCommand}; +use crate::commands::workflow::{JobNode, WorkflowCommand}; use std::collections::{HashMap, HashSet}; use uuid::Uuid; @@ -196,7 +196,7 @@ fn test_job_node_status_variations() { #[test] fn test_workflow_command_database_url_priority() { - use cargo_hammerwork::config::Config; + use crate::config::Config; let config_with_url = Config { database_url: Some("postgres://config/db".to_string()), diff --git a/cargo-hammerwork/src/lib.rs b/cargo-hammerwork/src/lib.rs deleted file mode 100644 index 1e1f1a0..0000000 --- a/cargo-hammerwork/src/lib.rs +++ /dev/null @@ -1,223 +0,0 @@ -//! # Cargo Hammerwork CLI -//! -//! A comprehensive command-line interface for managing Hammerwork job queues. -//! -//! This crate provides the `cargo hammerwork` CLI tool for database migrations, -//! job management, worker lease visibility, queue operations, and monitoring of Hammerwork -//! job processing systems. -//! -//! ## Installation -//! -//! ```bash -//! cargo install cargo-hammerwork --features postgres -//! # or -//! cargo install cargo-hammerwork --features mysql -//! ``` -//! -//! ## Basic Usage -//! -//! The CLI can be invoked either as a cargo subcommand or directly: -//! -//! ```bash -//! # As cargo subcommand -//! cargo hammerwork migration status --database-url postgresql://localhost/mydb -//! -//! # Direct invocation -//! cargo-hammerwork migration status --database-url postgresql://localhost/mydb -//! ``` -//! -//! ## Common Commands -//! -//! ### Database Migrations -//! -//! ```bash -//! # Run all pending migrations -//! cargo hammerwork migration run --database-url postgresql://localhost/mydb -//! -//! # Check migration status -//! cargo hammerwork migration status --database-url postgresql://localhost/mydb -//! -//! # Rollback last migration -//! cargo hammerwork migration rollback --database-url postgresql://localhost/mydb -//! ``` -//! -//! ### Job Management -//! -//! ```bash -//! # List jobs in a queue -//! cargo hammerwork job list --database-url postgresql://localhost/mydb --queue default -//! -//! # Enqueue a new job -//! cargo hammerwork job enqueue --database-url postgresql://localhost/mydb \ -//! --queue email --payload '{"to": "user@example.com"}' -//! -//! # Retry failed jobs -//! cargo hammerwork job retry --database-url postgresql://localhost/mydb --all-failed -//! -//! # Cancel a specific job -//! cargo hammerwork job cancel --database-url postgresql://localhost/mydb -//! ``` -//! -//! ### Queue Operations -//! -//! ```bash -//! # List all queues with statistics -//! cargo hammerwork queue list --database-url postgresql://localhost/mydb -//! -//! # Clear dead jobs from a queue -//! cargo hammerwork queue clear --database-url postgresql://localhost/mydb \ -//! --queue default --dead -//! -//! # Show queue statistics -//! cargo hammerwork queue stats --database-url postgresql://localhost/mydb \ -//! --queue default -//! ``` -//! -//! ### Worker Management -//! -//! ```bash -//! # Running jobs and the worker leases (heartbeats) behind them, per queue -//! cargo hammerwork worker status --database-url postgresql://localhost/mydb -//! -//! # Also list every running job -//! cargo hammerwork worker status --database-url postgresql://localhost/mydb --jobs -//! ``` -//! -//! ### Monitoring -//! -//! ```bash -//! # Show real-time dashboard -//! cargo hammerwork monitor dashboard --database-url postgresql://localhost/mydb -//! -//! # Display queue metrics -//! cargo hammerwork monitor metrics --database-url postgresql://localhost/mydb -//! -//! # Check system health -//! cargo hammerwork monitor health --database-url postgresql://localhost/mydb -//! ``` -//! -//! ## Configuration -//! -//! The CLI supports configuration files to avoid repetitive arguments: -//! -//! ```bash -//! # Create default config -//! cargo hammerwork config init -//! -//! # Show current config -//! cargo hammerwork config show -//! -//! # Set database URL in config -//! cargo hammerwork config set database_url postgresql://localhost/mydb -//! ``` -//! -//! Configuration files are stored at: -//! - Linux/Mac: `~/.config/hammerwork/config.toml` -//! - Windows: `%APPDATA%\hammerwork\config.toml` -//! -//! ## Environment Variables -//! -//! The CLI respects these environment variables: -//! -//! - `DATABASE_URL` - Default database connection URL -//! - `HAMMERWORK_CONFIG` - Path to config file -//! - `HAMMERWORK_LOG` - Log level (error, warn, info, debug, trace) -//! -//! ## Examples -//! -//! ### Complete Workflow Example -//! -//! ```bash -//! # 1. Initialize configuration -//! cargo hammerwork config init -//! cargo hammerwork config set database_url postgresql://localhost/hammerwork -//! -//! # 2. Run migrations to set up database -//! cargo hammerwork migration run -//! -//! # 3. Enqueue some jobs -//! cargo hammerwork job enqueue --queue email \ -//! --payload '{"to": "user@example.com", "subject": "Welcome!"}' -//! -//! cargo hammerwork job enqueue --queue email \ -//! --payload '{"to": "admin@example.com", "subject": "New user"}' \ -//! --priority high -//! -//! # 4. Check queue status -//! cargo hammerwork queue list -//! -//! # 5. Process jobs with a worker in your application (the CLI does not run workers) -//! -//! # 6. Monitor progress -//! cargo hammerwork monitor dashboard -//! ``` -//! -//! ### Batch Operations Example -//! -//! ```bash -//! # Enqueue multiple jobs at once -//! cargo hammerwork batch enqueue --queue data-processing \ -//! --payloads '[ -//! {"file": "data1.csv"}, -//! {"file": "data2.csv"}, -//! {"file": "data3.csv"} -//! ]' -//! -//! # Cancel all jobs in a batch -//! cargo hammerwork batch cancel -//! -//! # Show batch status -//! cargo hammerwork batch status -//! ``` -//! -//! ### Cron Scheduling Example -//! -//! ```bash -//! # Create a recurring job -//! cargo hammerwork cron create --queue reports \ -//! --schedule "0 9 * * MON-FRI" \ -//! --timezone "America/New_York" \ -//! --payload '{"type": "daily_report"}' -//! -//! # List cron jobs -//! cargo hammerwork cron list -//! -//! # Disable a cron job -//! cargo hammerwork cron disable -//! ``` -//! -//! ## Feature Flags -//! -//! - `postgres` - PostgreSQL support (recommended) -//! - `mysql` - MySQL support -//! - `completions` - Shell completion generation -//! -//! ## Error Handling -//! -//! The CLI provides detailed error messages and non-zero exit codes on failure: -//! -//! - Exit code 0: Success -//! - Exit code 1: General error -//! - Exit code 2: Configuration error -//! - Exit code 3: Database connection error -//! - Exit code 4: Invalid arguments -//! -//! ## Verbose and Quiet Modes -//! -//! Control output verbosity with global flags: -//! -//! ```bash -//! # Verbose mode - show debug information -//! cargo hammerwork -v migration run -//! -//! # Quiet mode - suppress all but errors -//! cargo hammerwork -q job list -//! ``` - -pub mod commands; -pub mod config; -pub mod utils; - -pub use commands::*; -pub use config::*; -pub use utils::*; diff --git a/cargo-hammerwork/src/main.rs b/cargo-hammerwork/src/main.rs index 7bd5048..71e38de 100644 --- a/cargo-hammerwork/src/main.rs +++ b/cargo-hammerwork/src/main.rs @@ -1,11 +1,237 @@ +//! # Cargo Hammerwork CLI +//! +//! A comprehensive command-line interface for managing Hammerwork job queues. +//! +//! This crate provides the `cargo hammerwork` CLI tool for database migrations, +//! job management, worker lease visibility, queue operations, and monitoring of Hammerwork +//! job processing systems. +//! +//! Rust examples in the doc comments of the internal modules are marked `ignore`: this is a +//! binary crate, so rustdoc does not compile or run them. The behaviour they describe is +//! covered by unit tests next to the code. +//! +//! ## Installation +//! +//! ```bash +//! cargo install cargo-hammerwork --features postgres +//! # or +//! cargo install cargo-hammerwork --features mysql +//! ``` +//! +//! ## Basic Usage +//! +//! The CLI can be invoked either as a cargo subcommand or directly: +//! +//! ```bash +//! # As cargo subcommand +//! cargo hammerwork migration status --database-url postgresql://localhost/mydb +//! +//! # Direct invocation +//! cargo-hammerwork migration status --database-url postgresql://localhost/mydb +//! ``` +//! +//! ## Common Commands +//! +//! ### Database Migrations +//! +//! ```bash +//! # Run all pending migrations +//! cargo hammerwork migration run --database-url postgresql://localhost/mydb +//! +//! # Check migration status +//! cargo hammerwork migration status --database-url postgresql://localhost/mydb +//! +//! # Rollback last migration +//! cargo hammerwork migration rollback --database-url postgresql://localhost/mydb +//! ``` +//! +//! ### Job Management +//! +//! ```bash +//! # List jobs in a queue +//! cargo hammerwork job list --database-url postgresql://localhost/mydb --queue default +//! +//! # Enqueue a new job +//! cargo hammerwork job enqueue --database-url postgresql://localhost/mydb \ +//! --queue email --payload '{"to": "user@example.com"}' +//! +//! # Retry failed jobs +//! cargo hammerwork job retry --database-url postgresql://localhost/mydb --all-failed +//! +//! # Cancel a specific job +//! cargo hammerwork job cancel --database-url postgresql://localhost/mydb +//! ``` +//! +//! ### Queue Operations +//! +//! ```bash +//! # List all queues with statistics +//! cargo hammerwork queue list --database-url postgresql://localhost/mydb +//! +//! # Clear dead jobs from a queue +//! cargo hammerwork queue clear --database-url postgresql://localhost/mydb \ +//! --queue default --dead +//! +//! # Show queue statistics +//! cargo hammerwork queue stats --database-url postgresql://localhost/mydb \ +//! --queue default +//! ``` +//! +//! ### Worker Management +//! +//! ```bash +//! # Running jobs and the worker leases (heartbeats) behind them, per queue +//! cargo hammerwork worker status --database-url postgresql://localhost/mydb +//! +//! # Also list every running job +//! cargo hammerwork worker status --database-url postgresql://localhost/mydb --jobs +//! ``` +//! +//! ### Monitoring +//! +//! ```bash +//! # Show real-time dashboard +//! cargo hammerwork monitor dashboard --database-url postgresql://localhost/mydb +//! +//! # Display queue metrics +//! cargo hammerwork monitor metrics --database-url postgresql://localhost/mydb +//! +//! # Check system health +//! cargo hammerwork monitor health --database-url postgresql://localhost/mydb +//! ``` +//! +//! ## Configuration +//! +//! The CLI supports configuration files to avoid repetitive arguments: +//! +//! ```bash +//! # Create default config +//! cargo hammerwork config init +//! +//! # Show current config +//! cargo hammerwork config show +//! +//! # Set database URL in config +//! cargo hammerwork config set database_url postgresql://localhost/mydb +//! ``` +//! +//! Configuration files are stored at: +//! - Linux/Mac: `~/.config/hammerwork/config.toml` +//! - Windows: `%APPDATA%\hammerwork\config.toml` +//! +//! ## Environment Variables +//! +//! The CLI respects these environment variables: +//! +//! - `DATABASE_URL` - Default database connection URL +//! - `HAMMERWORK_CONFIG` - Path to config file +//! - `HAMMERWORK_LOG` - Log level (error, warn, info, debug, trace) +//! +//! ## Examples +//! +//! ### Complete Workflow Example +//! +//! ```bash +//! # 1. Initialize configuration +//! cargo hammerwork config init +//! cargo hammerwork config set database_url postgresql://localhost/hammerwork +//! +//! # 2. Run migrations to set up database +//! cargo hammerwork migration run +//! +//! # 3. Enqueue some jobs +//! cargo hammerwork job enqueue --queue email \ +//! --payload '{"to": "user@example.com", "subject": "Welcome!"}' +//! +//! cargo hammerwork job enqueue --queue email \ +//! --payload '{"to": "admin@example.com", "subject": "New user"}' \ +//! --priority high +//! +//! # 4. Check queue status +//! cargo hammerwork queue list +//! +//! # 5. Process jobs with a worker in your application (the CLI does not run workers) +//! +//! # 6. Monitor progress +//! cargo hammerwork monitor dashboard +//! ``` +//! +//! ### Batch Operations Example +//! +//! ```bash +//! # Enqueue multiple jobs at once +//! cargo hammerwork batch enqueue --queue data-processing \ +//! --payloads '[ +//! {"file": "data1.csv"}, +//! {"file": "data2.csv"}, +//! {"file": "data3.csv"} +//! ]' +//! +//! # Cancel all jobs in a batch +//! cargo hammerwork batch cancel +//! +//! # Show batch status +//! cargo hammerwork batch status +//! ``` +//! +//! ### Cron Scheduling Example +//! +//! ```bash +//! # Create a recurring job +//! cargo hammerwork cron create --queue reports \ +//! --schedule "0 9 * * MON-FRI" \ +//! --timezone "America/New_York" \ +//! --payload '{"type": "daily_report"}' +//! +//! # List cron jobs +//! cargo hammerwork cron list +//! +//! # Disable a cron job +//! cargo hammerwork cron disable +//! ``` +//! +//! ## Feature Flags +//! +//! - `postgres` - PostgreSQL support (recommended) +//! - `mysql` - MySQL support +//! - `completions` - Shell completion generation +//! +//! ## Error Handling +//! +//! The CLI provides detailed error messages and non-zero exit codes on failure: +//! +//! - Exit code 0: Success +//! - Exit code 1: General error +//! - Exit code 2: Configuration error +//! - Exit code 3: Database connection error +//! - Exit code 4: Invalid arguments +//! +//! ## Verbose and Quiet Modes +//! +//! Control output verbosity with global flags: +//! +//! ```bash +//! # Verbose mode - show debug information +//! cargo hammerwork -v migration run +//! +//! # Quiet mode - suppress all but errors +//! cargo hammerwork -q job list +//! ``` + +mod commands; +mod config; +#[cfg(test)] +mod integration_tests; +mod utils; + use anyhow::Result; use clap::{Parser, Subcommand}; use std::env; use tracing::{error, info}; use tracing_subscriber::EnvFilter; -use cargo_hammerwork::commands::*; -use cargo_hammerwork::config::Config; +use commands::*; +use config::Config; #[derive(Parser)] #[command(name = "cargo-hammerwork")] diff --git a/cargo-hammerwork/src/utils/database.rs b/cargo-hammerwork/src/utils/database.rs index 6478f01..572a08b 100644 --- a/cargo-hammerwork/src/utils/database.rs +++ b/cargo-hammerwork/src/utils/database.rs @@ -8,7 +8,7 @@ //! //! ## Connecting to a Database //! -//! ```rust,no_run +//! ```rust,ignore //! use cargo_hammerwork::utils::database::DatabasePool; //! //! # #[tokio::main] @@ -30,7 +30,7 @@ //! //! ## Running Migrations //! -//! ```rust,no_run +//! ```rust,ignore //! use cargo_hammerwork::utils::database::DatabasePool; //! //! # #[tokio::main] @@ -48,7 +48,7 @@ //! //! ## Creating a Job Queue //! -//! ```rust,no_run +//! ```rust,ignore //! use cargo_hammerwork::utils::database::DatabasePool; //! //! # #[tokio::main] @@ -116,7 +116,7 @@ fn connect_error(err: sqlx::Error, timeout: Duration) -> anyhow::Error { /// /// # Examples /// -/// ```rust,no_run +/// ```rust,ignore /// use cargo_hammerwork::utils::database::DatabasePool; /// /// # #[tokio::main] @@ -149,7 +149,7 @@ impl DatabasePool { /// /// # Examples /// - /// ```rust,no_run + /// ```rust,ignore /// use cargo_hammerwork::utils::database::DatabasePool; /// /// # #[tokio::main] @@ -168,6 +168,7 @@ impl DatabasePool { /// # Ok(()) /// # } /// ``` + #[cfg(test)] pub async fn connect(database_url: &str, pool_size: u32) -> Result { Self::connect_with_timeout( database_url, @@ -177,7 +178,7 @@ impl DatabasePool { .await } - /// Like [`connect`](Self::connect), but fails with an error if no connection + /// Like `connect`, but fails with an error if no connection /// can be established within `connect_timeout`, instead of hanging on an /// unreachable host. pub async fn connect_with_timeout( @@ -237,7 +238,7 @@ impl DatabasePool { /// /// # Examples /// - /// ```rust,no_run + /// ```rust,ignore /// use cargo_hammerwork::utils::database::DatabasePool; /// /// # #[tokio::main] @@ -307,7 +308,7 @@ impl DatabasePool { /// /// # Examples /// - /// ```rust,no_run + /// ```rust,ignore /// use cargo_hammerwork::utils::database::DatabasePool; /// /// # #[tokio::main] @@ -376,7 +377,7 @@ impl DatabasePool { /// /// # Examples /// -/// ```rust,no_run +/// ```rust,ignore /// use cargo_hammerwork::utils::database::{DatabasePool, JobQueueWrapper}; /// /// # #[tokio::main] diff --git a/cargo-hammerwork/src/utils/display.rs b/cargo-hammerwork/src/utils/display.rs index 53b3246..e9f4d6b 100644 --- a/cargo-hammerwork/src/utils/display.rs +++ b/cargo-hammerwork/src/utils/display.rs @@ -7,7 +7,7 @@ //! //! ## Creating a Job Table //! -//! ```rust +//! ```rust,ignore //! use cargo_hammerwork::utils::display::JobTable; //! //! let mut table = JobTable::new(); @@ -27,7 +27,7 @@ //! //! ## Creating a Statistics Table //! -//! ```rust +//! ```rust,ignore //! use cargo_hammerwork::utils::display::StatsTable; //! //! let mut stats = StatsTable::new(); @@ -40,7 +40,7 @@ //! //! ## Formatting Helpers //! -//! ```rust +//! ```rust,ignore //! use cargo_hammerwork::utils::display::{format_duration, format_size}; //! //! // Format durations @@ -73,7 +73,7 @@ pub fn create_table() -> Table { /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// use cargo_hammerwork::utils::display::JobTable; /// /// let mut table = JobTable::new(); @@ -189,7 +189,7 @@ impl fmt::Display for JobTable { /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// use cargo_hammerwork::utils::display::StatsTable; /// /// let mut stats = StatsTable::new(); @@ -254,7 +254,7 @@ impl fmt::Display for StatsTable { /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// use cargo_hammerwork::utils::display::format_duration; /// /// assert_eq!(format_duration(Some(30)), "30s"); @@ -263,6 +263,7 @@ impl fmt::Display for StatsTable { /// assert_eq!(format_duration(Some(7200)), "2h 0m"); /// assert_eq!(format_duration(None), "N/A"); /// ``` +#[cfg(test)] pub fn format_duration(seconds: Option) -> String { match seconds { Some(secs) if secs < 60 => format!("{}s", secs), @@ -276,7 +277,7 @@ pub fn format_duration(seconds: Option) -> String { /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// use cargo_hammerwork::utils::display::format_size; /// /// assert_eq!(format_size(Some(100)), "100B"); @@ -286,6 +287,7 @@ pub fn format_duration(seconds: Option) -> String { /// assert_eq!(format_size(Some(1073741824)), "1.0GB"); /// assert_eq!(format_size(None), "N/A"); /// ``` +#[cfg(test)] pub fn format_size(bytes: Option) -> String { match bytes { Some(b) if b < 1024 => format!("{}B", b), @@ -312,6 +314,17 @@ mod tests { assert_eq!(format_duration(None), "N/A"); } + #[test] + fn formatters_handle_the_documented_examples() { + assert_eq!(format_duration(Some(30)), "30s"); + assert_eq!(format_duration(Some(90)), "1m 30s"); + assert_eq!(format_duration(Some(3661)), "1h 1m"); + assert_eq!(format_duration(Some(7200)), "2h 0m"); + assert_eq!(format_size(Some(100)), "100B"); + assert_eq!(format_size(Some(2048)), "2.0KB"); + assert_eq!(format_size(Some(1_073_741_824)), "1.0GB"); + } + #[test] fn size_formatting_switches_units_at_powers_of_1024() { assert_eq!(format_size(Some(0)), "0B"); diff --git a/cargo-hammerwork/src/utils/job_rows.rs b/cargo-hammerwork/src/utils/job_rows.rs index d758f0d..7c4b78a 100644 --- a/cargo-hammerwork/src/utils/job_rows.rs +++ b/cargo-hammerwork/src/utils/job_rows.rs @@ -145,7 +145,7 @@ fn quoted(backend: Backend, name: &str) -> String { } } -/// The `SELECT` list reading `columns` in the form [`row_to_json`] decodes. +/// The `SELECT` list reading `columns` in the form `row_to_json` decodes. pub fn select_list(backend: Backend, columns: &[Column]) -> String { columns .iter() diff --git a/cargo-hammerwork/src/utils/mod.rs b/cargo-hammerwork/src/utils/mod.rs index 4ee8a75..6920dfa 100644 --- a/cargo-hammerwork/src/utils/mod.rs +++ b/cargo-hammerwork/src/utils/mod.rs @@ -7,8 +7,3 @@ pub mod sql; #[cfg(test)] pub mod test_support; pub mod validation; - -pub use database::*; -pub use display::*; -pub use sql::*; -pub use validation::*; diff --git a/cargo-hammerwork/src/utils/sql.rs b/cargo-hammerwork/src/utils/sql.rs index 792867a..a493969 100644 --- a/cargo-hammerwork/src/utils/sql.rs +++ b/cargo-hammerwork/src/utils/sql.rs @@ -76,10 +76,6 @@ impl SqlParams { } } - pub fn backend(&self) -> Backend { - self.backend - } - /// Record `value` and return its placeholder. pub fn bind(&mut self, value: Bind) -> String { self.binds.push(value); @@ -265,30 +261,18 @@ pub(crate) use fetch_rows; pub trait IdText { /// The id in column `column`, as text. fn id_text(&self, column: &str) -> Result; - /// The nullable id in column `column`, as text. - fn opt_id_text(&self, column: &str) -> Result>; } impl IdText for sqlx::postgres::PgRow { fn id_text(&self, column: &str) -> Result { Ok(self.try_get::(column)?.to_string()) } - - fn opt_id_text(&self, column: &str) -> Result> { - Ok(self - .try_get::, _>(column)? - .map(|id| id.to_string())) - } } impl IdText for sqlx::mysql::MySqlRow { fn id_text(&self, column: &str) -> Result { Ok(self.try_get(column)?) } - - fn opt_id_text(&self, column: &str) -> Result> { - Ok(self.try_get(column)?) - } } #[cfg(test)] diff --git a/cargo-hammerwork/src/utils/validation.rs b/cargo-hammerwork/src/utils/validation.rs index b2cdd24..42b7288 100644 --- a/cargo-hammerwork/src/utils/validation.rs +++ b/cargo-hammerwork/src/utils/validation.rs @@ -1,5 +1,6 @@ use anyhow::Result; use hammerwork::JobPriority; +#[cfg(test)] use std::str::FromStr; pub fn validate_priority(priority: &str) -> Result { @@ -51,6 +52,7 @@ pub fn redact_url(url: &str) -> String { hammerwork::config::redact_url(url) } +#[cfg(test)] pub fn validate_cron_expression(cron: &str) -> Result<()> { cron::Schedule::from_str(cron) .map_err(|e| anyhow::anyhow!("Invalid cron expression '{}': {}", cron, e))?; diff --git a/docs/upgrading-to-3.0.md b/docs/upgrading-to-3.0.md index f8f0fa0..ccf8695 100644 --- a/docs/upgrading-to-3.0.md +++ b/docs/upgrading-to-3.0.md @@ -143,3 +143,45 @@ hammerwork = { version = "3", features = ["test-queue"] } A compile error such as `unresolved import hammerwork::metrics`, `hammerwork::alerting` or `hammerwork::webhooks`, or a missing `TestQueue`, means a feature is not enabled. `cargo-hammerwork` and `hammerwork-web` already enable the features they need. If you gate your own code with `cfg(feature = "...")` on features that you forward to hammerwork, rename them in the same way. + +## cargo-hammerwork and hammerwork-web as libraries + +In 2.x both companion crates exported their internals as library API. In 3.0 they do not, +so their modules can change without a semver break. + +### cargo-hammerwork + +`cargo-hammerwork` is a binary only. The `cargo_hammerwork::commands`, +`cargo_hammerwork::config` and `cargo_hammerwork::utils` modules (and the glob re-exports +of them at the crate root) no longer exist, and the crate has no library target to depend +on. Use the CLI through the `cargo hammerwork` executable. If you need job, queue or +migration operations from Rust code, use the `hammerwork` crate (`DatabaseQueue`, +`JobQueue`) directly. + +### hammerwork-web + +The public API is `WebDashboard`, `DashboardConfig`, `AuthConfig` and `WebSocketConfig`, +all re-exported at the crate root. The `api`, `auth`, `config`, `live`, `security`, +`server` and `websocket` modules are private, including `server::app_routes`, which needed +other private types (`AuthState`, `SystemState`, `WebSocketState`, `AllowedOrigins`) to call. + +To embed the dashboard in your own process, build a `DashboardConfig` and run a +`WebDashboard` (optionally `with_event_manager` to forward worker events): + +```rust,ignore +use hammerwork_web::{DashboardConfig, WebDashboard}; + +let config = DashboardConfig::new() + .with_database_url("postgresql://localhost/hammerwork"); +WebDashboard::new(config).await?.start().await?; +``` + +Migrate imports as follows: + +| 2.x path | 3.0 | +| --- | --- | +| `hammerwork_web::config::DashboardConfig` | `hammerwork_web::DashboardConfig` | +| `hammerwork_web::config::AuthConfig` | `hammerwork_web::AuthConfig` | +| `hammerwork_web::config::WebSocketConfig` | `hammerwork_web::WebSocketConfig` | +| `hammerwork_web::server::WebDashboard` | `hammerwork_web::WebDashboard` | +| `hammerwork_web::server::app_routes`, `api::*`, `auth::*`, `security::*`, `websocket::*`, `live::*` | removed; mount the dashboard with `WebDashboard` | diff --git a/hammerwork-web/src/api/archive.rs b/hammerwork-web/src/api/archive.rs index ecd74cb..a349776 100644 --- a/hammerwork-web/src/api/archive.rs +++ b/hammerwork-web/src/api/archive.rs @@ -17,7 +17,7 @@ //! ## Archive Jobs //! //! ```rust -//! use hammerwork_web::api::archive::{ArchiveRequest, ArchiveResponse}; +//! use hammerwork_web::__private::api::archive::{ArchiveRequest, ArchiveResponse}; //! use hammerwork::archive::{ArchivalReason, ArchivalPolicy}; //! use chrono::Duration; //! @@ -39,7 +39,7 @@ //! ## List Archived Jobs //! //! ```rust -//! use hammerwork_web::api::archive::ArchivedJobInfo; +//! use hammerwork_web::__private::api::archive::ArchivedJobInfo; //! use hammerwork::archive::{ArchivalReason, ArchivedJob}; //! use hammerwork::{JobId, JobStatus}; //! use chrono::Utc; diff --git a/hammerwork-web/src/api/jobs.rs b/hammerwork-web/src/api/jobs.rs index 7d7e7fe..9431049 100644 --- a/hammerwork-web/src/api/jobs.rs +++ b/hammerwork-web/src/api/jobs.rs @@ -17,7 +17,7 @@ //! ## Creating a Job //! //! ```rust -//! use hammerwork_web::api::jobs::CreateJobRequest; +//! use hammerwork_web::__private::api::jobs::CreateJobRequest; //! use serde_json::json; //! //! let create_request = CreateJobRequest { @@ -44,7 +44,7 @@ //! ## Job Actions //! //! ```rust -//! use hammerwork_web::api::jobs::JobActionRequest; +//! use hammerwork_web::__private::api::jobs::JobActionRequest; //! //! let retry_request = JobActionRequest { //! action: "retry".to_string(), @@ -63,7 +63,7 @@ //! ## Bulk Operations //! //! ```rust -//! use hammerwork_web::api::jobs::BulkJobActionRequest; +//! use hammerwork_web::__private::api::jobs::BulkJobActionRequest; //! //! let bulk_delete = BulkJobActionRequest { //! job_ids: vec![ diff --git a/hammerwork-web/src/api/mod.rs b/hammerwork-web/src/api/mod.rs index 55082b9..8c73c06 100644 --- a/hammerwork-web/src/api/mod.rs +++ b/hammerwork-web/src/api/mod.rs @@ -9,7 +9,7 @@ //! All API endpoints return responses in a consistent format: //! //! ```rust -//! use hammerwork_web::api::ApiResponse; +//! use hammerwork_web::__private::api::ApiResponse; //! use serde_json::json; //! //! // Success response @@ -30,7 +30,7 @@ //! Many endpoints support pagination using query parameters: //! //! ```rust -//! use hammerwork_web::api::{PaginationParams, PaginationMeta}; +//! use hammerwork_web::__private::api::{PaginationParams, PaginationMeta}; //! //! let params = PaginationParams { //! page: Some(2), diff --git a/hammerwork-web/src/api/queues.rs b/hammerwork-web/src/api/queues.rs index 496a6d7..0264f5f 100644 --- a/hammerwork-web/src/api/queues.rs +++ b/hammerwork-web/src/api/queues.rs @@ -15,7 +15,7 @@ //! ## Queue Information Structure //! //! ```rust -//! use hammerwork_web::api::queues::QueueInfo; +//! use hammerwork_web::__private::api::queues::QueueInfo; //! use chrono::Utc; //! //! let queue_info = QueueInfo { @@ -43,7 +43,7 @@ //! ## Queue Actions //! //! ```rust -//! use hammerwork_web::api::queues::QueueActionRequest; +//! use hammerwork_web::__private::api::queues::QueueActionRequest; //! //! let clear_dead_request = QueueActionRequest { //! action: "clear_dead".to_string(), @@ -62,7 +62,7 @@ //! ## Detailed Queue Statistics //! //! ```rust -//! use hammerwork_web::api::queues::{DetailedQueueStats, QueueInfo, HourlyThroughput, RecentError}; +//! use hammerwork_web::__private::api::queues::{DetailedQueueStats, QueueInfo, HourlyThroughput, RecentError}; //! use std::collections::HashMap; //! use chrono::Utc; //! diff --git a/hammerwork-web/src/api/stats.rs b/hammerwork-web/src/api/stats.rs index b082e83..3334455 100644 --- a/hammerwork-web/src/api/stats.rs +++ b/hammerwork-web/src/api/stats.rs @@ -15,7 +15,7 @@ //! ## System Overview //! //! ```rust -//! use hammerwork_web::api::stats::{SystemOverview, SystemHealth, SystemAlert}; +//! use hammerwork_web::__private::api::stats::{SystemOverview, SystemHealth, SystemAlert}; //! use chrono::Utc; //! //! let overview = SystemOverview { @@ -49,7 +49,7 @@ //! ## Statistics Queries //! //! ```rust -//! use hammerwork_web::api::stats::{StatsQuery, TimeRange}; +//! use hammerwork_web::__private::api::stats::{StatsQuery, TimeRange}; //! use chrono::{Utc, Duration}; //! //! let time_range = TimeRange { @@ -72,7 +72,7 @@ //! ## System Alerts //! //! ```rust -//! use hammerwork_web::api::stats::SystemAlert; +//! use hammerwork_web::__private::api::stats::SystemAlert; //! use chrono::Utc; //! //! let alert = SystemAlert { @@ -93,7 +93,7 @@ //! ## Performance Metrics //! //! ```rust -//! use hammerwork_web::api::stats::PerformanceMetrics; +//! use hammerwork_web::__private::api::stats::PerformanceMetrics; //! //! let metrics = PerformanceMetrics { //! database_response_time_ms: Some(5.2), diff --git a/hammerwork-web/src/api/system.rs b/hammerwork-web/src/api/system.rs index d90cfb6..3d95adb 100644 --- a/hammerwork-web/src/api/system.rs +++ b/hammerwork-web/src/api/system.rs @@ -16,7 +16,7 @@ //! ## System Information //! //! ```rust -//! use hammerwork_web::api::system::{SystemInfo, BuildInfo, RuntimeInfo, DatabaseInfo}; +//! use hammerwork_web::__private::api::system::{SystemInfo, BuildInfo, RuntimeInfo, DatabaseInfo}; //! use chrono::Utc; //! //! let build_info = BuildInfo { @@ -62,7 +62,7 @@ //! ## Maintenance Operations //! //! ```rust -//! use hammerwork_web::api::system::MaintenanceRequest; +//! use hammerwork_web::__private::api::system::MaintenanceRequest; //! use serde_json::json; //! //! let cleanup_request = MaintenanceRequest { @@ -85,7 +85,7 @@ //! ## Server Configuration //! //! ```rust -//! use hammerwork_web::api::system::ServerConfig; +//! use hammerwork_web::__private::api::system::ServerConfig; //! //! let config = ServerConfig { //! bind_address: "0.0.0.0".to_string(), @@ -105,7 +105,7 @@ //! ## Metrics Information //! //! ```rust -//! use hammerwork_web::api::system::MetricsInfo; +//! use hammerwork_web::__private::api::system::MetricsInfo; //! use chrono::Utc; //! //! let metrics_info = MetricsInfo { diff --git a/hammerwork-web/src/auth.rs b/hammerwork-web/src/auth.rs index b3723e3..fa348f8 100644 --- a/hammerwork-web/src/auth.rs +++ b/hammerwork-web/src/auth.rs @@ -25,8 +25,8 @@ //! ## Basic Authentication Setup //! //! ```rust -//! use hammerwork_web::auth::AuthState; -//! use hammerwork_web::config::AuthConfig; +//! use hammerwork_web::__private::auth::AuthState; +//! use hammerwork_web::__private::config::AuthConfig; //! //! let auth_config = AuthConfig { //! enabled: true, @@ -43,7 +43,7 @@ //! ## Extracting Basic Auth Credentials //! //! ```rust -//! use hammerwork_web::auth::extract_basic_auth; +//! use hammerwork_web::__private::auth::extract_basic_auth; //! //! // "admin:password" in base64 is "YWRtaW46cGFzc3dvcmQ=" //! let auth_header = "Basic YWRtaW46cGFzc3dvcmQ="; @@ -387,7 +387,7 @@ impl AuthState { /// # Examples /// /// ```rust -/// use hammerwork_web::auth::extract_basic_auth; +/// use hammerwork_web::__private::auth::extract_basic_auth; /// /// // Valid basic auth header /// let auth_header = "Basic YWRtaW46cGFzc3dvcmQ="; // admin:password diff --git a/hammerwork-web/src/config.rs b/hammerwork-web/src/config.rs index 23cb8a3..ab68328 100644 --- a/hammerwork-web/src/config.rs +++ b/hammerwork-web/src/config.rs @@ -8,7 +8,7 @@ //! ## Basic Configuration //! //! ```rust -//! use hammerwork_web::config::DashboardConfig; +//! use hammerwork_web::__private::config::DashboardConfig; //! //! let config = DashboardConfig::new() //! .with_bind_address("127.0.0.1", 8080) @@ -20,7 +20,7 @@ //! ## Configuration with Authentication //! //! ```rust -//! use hammerwork_web::config::{DashboardConfig, AuthConfig}; +//! use hammerwork_web::__private::config::{DashboardConfig, AuthConfig}; //! use std::time::Duration; //! //! let config = DashboardConfig::new() @@ -35,7 +35,7 @@ //! ## Loading from File //! //! ```rust,no_run -//! use hammerwork_web::config::DashboardConfig; +//! use hammerwork_web::__private::config::DashboardConfig; //! //! // Create a configuration file (dashboard.toml) //! let config_content = r#" @@ -73,7 +73,7 @@ use std::time::Duration; /// # Examples /// /// ```rust -/// use hammerwork_web::config::DashboardConfig; +/// use hammerwork_web::__private::config::DashboardConfig; /// use std::path::PathBuf; /// /// // Create with defaults @@ -124,7 +124,7 @@ pub struct DashboardConfig { /// Origins other than the dashboard's own (`scheme://host[:port]`) that may send /// state-changing requests and WebSocket connections, and, with /// [`enable_cors`](Self::enable_cors), read API responses. Browsers on any other origin - /// are refused (see [`crate::security`]). + /// are refused (the `security` module). #[serde(default)] pub allowed_origins: Vec, } @@ -151,7 +151,7 @@ impl DashboardConfig { /// # Examples /// /// ```rust - /// use hammerwork_web::config::DashboardConfig; + /// use hammerwork_web::__private::config::DashboardConfig; /// /// let config = DashboardConfig::new(); /// assert_eq!(config.bind_address, "127.0.0.1"); @@ -167,7 +167,7 @@ impl DashboardConfig { /// # Examples /// /// ```rust - /// use hammerwork_web::config::DashboardConfig; + /// use hammerwork_web::__private::config::DashboardConfig; /// /// let config = DashboardConfig::new() /// .with_bind_address("0.0.0.0", 9090); @@ -189,7 +189,7 @@ impl DashboardConfig { /// # Examples /// /// ```rust - /// use hammerwork_web::config::DashboardConfig; + /// use hammerwork_web::__private::config::DashboardConfig; /// /// // PostgreSQL /// let pg_config = DashboardConfig::new() @@ -211,7 +211,7 @@ impl DashboardConfig { /// # Examples /// /// ```rust - /// use hammerwork_web::config::DashboardConfig; + /// use hammerwork_web::__private::config::DashboardConfig; /// use std::path::PathBuf; /// /// let config = DashboardConfig::new() @@ -232,7 +232,7 @@ impl DashboardConfig { /// # Examples /// /// ```rust - /// use hammerwork_web::config::DashboardConfig; + /// use hammerwork_web::__private::config::DashboardConfig; /// /// let config = DashboardConfig::new() /// .with_auth("admin", "$2b$12$hash..."); @@ -256,7 +256,7 @@ impl DashboardConfig { /// # Examples /// /// ```rust - /// use hammerwork_web::config::DashboardConfig; + /// use hammerwork_web::__private::config::DashboardConfig; /// /// let config = DashboardConfig::new() /// .with_cors(true) @@ -354,7 +354,7 @@ impl DashboardConfig { /// # Examples /// /// ```rust -/// use hammerwork_web::config::AuthConfig; +/// use hammerwork_web::__private::config::AuthConfig; /// use std::time::Duration; /// /// // Default configuration (authentication enabled) diff --git a/hammerwork-web/src/lib.rs b/hammerwork-web/src/lib.rs index 1a50aa1..2fcbb25 100644 --- a/hammerwork-web/src/lib.rs +++ b/hammerwork-web/src/lib.rs @@ -101,17 +101,46 @@ compile_error!( "hammerwork-web needs a database backend: enable the `postgres` (default) and/or `mysql` feature" ); -pub mod api; -pub mod auth; -pub mod config; -pub mod live; -pub mod security; -pub mod server; -pub mod websocket; - -pub use config::{AuthConfig, DashboardConfig}; +mod api; +mod auth; +mod config; +mod live; +mod security; +mod server; +mod websocket; + +pub use config::{AuthConfig, DashboardConfig, WebSocketConfig}; pub use server::WebDashboard; +/// Internals re-exported for this crate's own integration tests and doc examples. +/// +/// Not part of the public API: it is hidden from the documentation and may change in any +/// release, including patch releases. +#[doc(hidden)] +pub mod __private { + pub mod api { + pub use crate::api::*; + } + pub mod auth { + pub use crate::auth::*; + } + pub mod config { + pub use crate::config::*; + } + pub mod live { + pub use crate::live::*; + } + pub mod security { + pub use crate::security::*; + } + pub mod server { + pub use crate::server::*; + } + pub mod websocket { + pub use crate::websocket::*; + } +} + /// Result type alias for consistent error handling pub type Result = std::result::Result; diff --git a/hammerwork-web/src/security.rs b/hammerwork-web/src/security.rs index 90e4647..77f045b 100644 --- a/hammerwork-web/src/security.rs +++ b/hammerwork-web/src/security.rs @@ -15,7 +15,7 @@ //! [`MAX_JSON_BODY_BYTES`] of them. //! //! ```rust -//! use hammerwork_web::security::{AllowedOrigins, normalize_origin}; +//! use hammerwork_web::__private::security::{AllowedOrigins, normalize_origin}; //! //! assert_eq!( //! normalize_origin("https://Ops.Example.com/").as_deref(), diff --git a/hammerwork-web/src/server.rs b/hammerwork-web/src/server.rs index ef266c8..9461bdc 100644 --- a/hammerwork-web/src/server.rs +++ b/hammerwork-web/src/server.rs @@ -68,7 +68,7 @@ use warp::{Filter, Reply}; /// /// State-changing API requests and WebSocket handshakes that a browser sends from a page on /// another origin than the dashboard's own or one of `allowed_origins` are refused with 403 -/// (see [`crate::security`]). +/// (the `security` module). pub fn app_routes( queue: Arc, auth_state: AuthState, @@ -176,7 +176,7 @@ impl WebDashboard { /// For a dashboard embedded in the same process as the workers that publish to /// `event_manager`. Without it (and in the standalone binary) the dashboard still /// pushes job and queue changes, by polling the database every - /// `websocket.live_update_interval`; see [`crate::live`]. + /// `websocket.live_update_interval`; see the `live` module. pub fn with_event_manager( mut self, event_manager: Arc, diff --git a/hammerwork-web/src/websocket.rs b/hammerwork-web/src/websocket.rs index 2f9d06b..79e484c 100644 --- a/hammerwork-web/src/websocket.rs +++ b/hammerwork-web/src/websocket.rs @@ -9,7 +9,7 @@ //! The WebSocket API supports several message types for different events: //! //! ```rust -//! use hammerwork_web::websocket::{ClientMessage, ServerMessage, AlertSeverity}; +//! use hammerwork_web::__private::websocket::{ClientMessage, ServerMessage, AlertSeverity}; //! use serde_json::json; //! //! // Client messages (sent from browser to server) @@ -31,8 +31,8 @@ //! # Connection Management //! //! ```rust -//! use hammerwork_web::websocket::WebSocketState; -//! use hammerwork_web::config::WebSocketConfig; +//! use hammerwork_web::__private::websocket::WebSocketState; +//! use hammerwork_web::__private::config::WebSocketConfig; //! //! let config = WebSocketConfig::default(); //! let ws_state = WebSocketState::new(config); diff --git a/hammerwork-web/tests/api_tests.rs b/hammerwork-web/tests/api_tests.rs index 601e34c..681ccd4 100644 --- a/hammerwork-web/tests/api_tests.rs +++ b/hammerwork-web/tests/api_tests.rs @@ -5,13 +5,13 @@ use hammerwork::archive::{ArchivalConfig, ArchivalPolicy, ArchivalReason}; use hammerwork::queue::DatabaseQueue; use hammerwork::{Job, JobPriority, JobQueue}; -use hammerwork_web::api::history::JobHistory; -use hammerwork_web::api::system::SystemState; -use hammerwork_web::auth::AuthState; -use hammerwork_web::config::{AuthConfig, DashboardConfig}; -use hammerwork_web::security::AllowedOrigins; -use hammerwork_web::server::app_routes; -use hammerwork_web::websocket::WebSocketState; +use hammerwork_web::__private::api::history::JobHistory; +use hammerwork_web::__private::api::system::SystemState; +use hammerwork_web::__private::auth::AuthState; +use hammerwork_web::__private::config::{AuthConfig, DashboardConfig}; +use hammerwork_web::__private::security::AllowedOrigins; +use hammerwork_web::__private::server::app_routes; +use hammerwork_web::__private::websocket::WebSocketState; use serde_json::{Value, json}; use std::sync::Arc; use tokio::sync::RwLock; @@ -1491,7 +1491,7 @@ async fn request_hardening(app: &App) { ) .await; assert_eq!(status, 413); - let ids: Vec = (0..=hammerwork_web::api::jobs::MAX_BULK_JOB_IDS) + let ids: Vec = (0..=hammerwork_web::__private::api::jobs::MAX_BULK_JOB_IDS) .map(|_| uuid::Uuid::new_v4().to_string()) .collect(); let (status, body) = app diff --git a/hammerwork-web/tests/integration_tests.rs b/hammerwork-web/tests/integration_tests.rs index c8818ce..06a3909 100644 --- a/hammerwork-web/tests/integration_tests.rs +++ b/hammerwork-web/tests/integration_tests.rs @@ -229,7 +229,7 @@ mod tests { #[tokio::test] async fn test_websocket_config_validation() { - use hammerwork_web::config::WebSocketConfig; + use hammerwork_web::__private::config::WebSocketConfig; let ws_config = WebSocketConfig { ping_interval: Duration::from_secs(10), @@ -291,8 +291,8 @@ mod websocket_archive_tests { use super::*; use chrono::Utc; use hammerwork::archive::{ArchivalReason, ArchivalStats, ArchiveEvent}; - use hammerwork_web::config::WebSocketConfig; - use hammerwork_web::websocket::{BroadcastMessage, WebSocketState}; + use hammerwork_web::__private::config::WebSocketConfig; + use hammerwork_web::__private::websocket::{BroadcastMessage, WebSocketState}; use uuid::Uuid; #[tokio::test] diff --git a/scripts/dev.sh b/scripts/dev.sh index 33d486c..152d013 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -45,12 +45,12 @@ test() { # CLI unit tests print_status "Running CLI unit tests..." - cargo test -p cargo-hammerwork --lib + cargo test -p cargo-hammerwork --bins # CLI integration tests (non-database) print_status "Running CLI integration tests..." cargo test -p cargo-hammerwork --test integration_tests - cargo test -p cargo-hammerwork --test sql_query_tests unit_tests error_handling_tests + cargo test -p cargo-hammerwork --bins -- sql_query::unit_tests sql_query::error_handling_tests print_success "All tests completed" } diff --git a/scripts/setup-test-databases.sh b/scripts/setup-test-databases.sh index a659f1a..2e6030c 100755 --- a/scripts/setup-test-databases.sh +++ b/scripts/setup-test-databases.sh @@ -217,11 +217,11 @@ run_tests() { export MYSQL_DATABASE_URL="mysql://root:${MYSQL_PASSWORD}@localhost:${MYSQL_PORT}/${DB_NAME}" print_status "Running PostgreSQL integration tests..." - cargo test -p cargo-hammerwork --test sql_query_tests postgres_tests -- --ignored + cargo test -p cargo-hammerwork --bins -- sql_query::postgres_tests --ignored if docker ps --format '{{.Names}}' | grep -q "^${MYSQL_CONTAINER}$"; then print_status "Running MySQL integration tests..." - cargo test -p cargo-hammerwork --test sql_query_tests mysql_tests -- --ignored + cargo test -p cargo-hammerwork --bins -- sql_query::mysql_tests --ignored else print_warning "MySQL container not running, skipping MySQL tests" fi diff --git a/src/queue/lifecycle.rs b/src/queue/lifecycle.rs index a86c643..8dbc613 100644 --- a/src/queue/lifecycle.rs +++ b/src/queue/lifecycle.rs @@ -16,7 +16,7 @@ //! //! Both kinds apply their side effects in the same transaction as the status change: //! on completion, dependents whose dependencies have all completed become runnable; -//! on a terminal failure, the workflow's [`FailurePolicy`](crate::workflow::FailurePolicy) +//! on a terminal failure, the workflow's [`FailurePolicy`] //! and the batch's [`PartialFailureMode`](crate::batch::PartialFailureMode) are applied; //! and workflow and batch counters are updated. A worker run of a recurring job that //! ends (successfully or not) is rescheduled for its next cron occurrence instead. diff --git a/src/worker.rs b/src/worker.rs index 7b4e7f0..b13dc42 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -466,7 +466,7 @@ impl Default for JobResult { pub enum JobHandlerType { /// Original handler type that returns Result<()> Legacy(JobHandler), - /// Enhanced handler type that returns Result with optional data + /// Enhanced handler type that returns `Result` with optional data WithResult(JobHandlerWithResult), } @@ -1131,7 +1131,7 @@ where /// Applies `worker` (see [`with_config`](Self::with_config)), the throttle from /// `rate_limiting` that matches this worker's queue, and `alerting` (when the /// `alerting` feature is enabled). Events are not applied here because the - /// [`EventManager`](crate::events::EventManager) must be shared with any webhook or + /// [`EventManager`] must be shared with any webhook or /// stream managers: build it once with `EventManager::new(config.events.clone())` /// and pass it to [`with_event_manager`](Self::with_event_manager). ///