commit aa80ac21ee64445a9563462cb35b83dd7e62160f Author: 0xcathiefish <72328723+0xcathiefish@users.noreply.github.com> Date: Sat May 30 08:38:18 2026 +0000 feat: initialize project structure, modules and configurations diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2b85f8b --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Application Environment Variables Template + +# Name of the application (e.g. nosync-app) +APP_NAME= +# Logging configuration level (e.g. info, debug, trace) +RUST_LOG=info diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa575b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/target +.env +.env.local +.env.*.local +*.log +.DS_Store +.idea/ +.vscode/ +*.swp +.antigravitycli/ + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d4362f2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +本文件记录项目所有值得注意的变更。 +格式遵循 Keep a Changelog,版本号遵循 SemVer。 + +## [Unreleased] + +## [0.1.0] - 2026-05-30 +### Added +- 初始化 Rust 工程项目结构与核心配置 +- 实现 `ModuleA` 与 `ModuleB` 业务类及接口设计 +- 配置 `tokio` 异步运行时、`thiserror` 自定义错误与 `anyhow` 二进制入口错误处理 +- 集成 `tracing` 结构化日志与 `dotenvy` 环境配置读取 +- 新增集成测试与示例代码 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2094ea7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,408 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nosync" +version = "0.1.0" +dependencies = [ + "anyhow", + "dotenvy", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8dd1b3d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nosync" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.102" +dotenvy = "0.15.7" +thiserror = "2.0.18" +tokio = { version = "1.52.3", features = ["full"] } +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb95aca --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# nosync + +一款高效、解耦的异步消息处理与任务编排框架。 + +## 1. 项目简介 +`nosync` 是一个高性能的 Rust 异步消息处理和组件协作框架,专为解决多模块间松耦合通信、结构化日志追踪以及鲁棒的错误恢复机制而设计。它主要面向需要高并发、低延迟以及强模块化设计的服务端开发人员,为构建复杂微服务或本地计算引擎提供核心底座。 + +## 2. 核心功能 +* **模块化封装架构**:遵循面向对象设计原则,将组件封装为高内聚的 `pub struct` 并持有独立状态,杜绝全局可变状态与裸函数。 +* **异步事件处理**:全面支持基于 `tokio` 运行时的多任务处理,提升高负载场景下的吞吐量。 +* **结构化日志监控**:集成 `tracing` 系统,提供细粒度的业务追踪与故障还原能力。 +* **强类型错误管理**:利用 `thiserror` 定义清晰的组件级错误,拒绝吞掉异常,确保系统的健壮性。 +* **多环境适配能力**:天然支持通过 `.env` 文件和环境变量在运行时动态配置系统属性。 + +## 3. 架构与模块 +本项目的目录结构与模块划分如下: + +``` +nosync/ +├── src/ +│ ├── lib.rs # 库入口,统一导出公共接口与类型 +│ ├── structs.rs # 存放跨模块共享的纯数据结构 +│ ├── module_a.rs # 业务模块 A (消息接收与底层处理) +│ └── module_b.rs # 业务模块 B (工作流编排与核心控制) +├── examples/ # 使用示例 +│ └── demo.rs # 核心运行演示 +├── tests/ # 集成测试 +│ ├── module_a_test.rs +│ └── module_b_test.rs +├── references/ # Git 子模块与外部参考仓库目录 +├── .env # 实际运行环境变量配置文件 (本地开发,不提交) +├── .env.example # 环境变量配置模板 +├── Cargo.toml # Cargo 配置文件 +└── CHANGELOG.md # 变更日志 +``` + +* **ModuleA** (`ModuleA`):负责消息接收、数据有效性校验与核心的异步解析处理。 +* **ModuleB** (`ModuleB`):负责编排 `ModuleA` 的执行流,充当协调器(Orchestrator)角色。 +* **Structs** (`structs`):定义了消息体 `SharedMessage` 等公共数据契约。 + +## 4. 环境要求 +* **Rust**: `1.85.0` 或更高版本(支持最新 edition 2024) +* **OS**: Linux, macOS, Windows +* **运行时**: `tokio` (Full features) + +## 5. 安装与启动 +```bash +# 克隆仓库 +git clone https://github.com/cathiefish/nosync.git +cd nosync + +# 复制并配置环境变量 +cp .env.example .env +# 可以根据需要修改 .env 中的内容 + +# 构建项目 +cargo build --release + +# 运行默认二进制应用 +cargo run + +# 运行使用示例 +cargo run --example demo +``` + +## 6. 使用示例 +最小可运行示例位于 [examples/demo.rs](file:///home/cathiefish/App/nosync/examples/demo.rs): +```rust +use anyhow::Result; +use nosync::{ModuleA, ModuleB}; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +#[tokio::main] +async fn main() -> Result<()> { + let subscriber = FmtSubscriber::builder() + .with_env_filter(EnvFilter::new("info")) + .finish(); + tracing::subscriber::set_global_default(subscriber)?; + + // 初始化核心组件 + let processor = ModuleA::new("example-processor".to_string())?; + let orchestrator = ModuleB::new(processor)?; + + // 运行工作流 + orchestrator.run().await?; + Ok(()) +} +``` + +## 7. 环境变量说明 +| 环境变量名 | 用途 | 是否必填 | 默认值 / 示例值 | +| :--- | :--- | :--- | :--- | +| `APP_NAME` | 应用程序或当前节点的名称标识,用于日志和初始化 | 否 | `nosync-default` | +| `RUST_LOG` | 设定日志输出级别 (e.g. error, warn, info, debug, trace) | 否 | `info` | + +## 8. 测试与开发 +### 开发分支约定 +* `dev`:主开发分支,新功能与修复首发合并至此。 +* `main`:生产稳定分支,当且仅当测试、Clippy 与格式化全部通过后才合并。 + +### 本地验证命令 +在提交代码前,**必须**运行以下命令进行本地验证: +```bash +# 自动格式化代码 +cargo fmt --all + +# 运行代码规范检查(不能有 warnings) +cargo clippy --all-targets --all-features -- -D warnings + +# 执行单元测试与集成测试 +cargo test +``` + +## 9. 变更日志指引 +关于项目的历史演进和每个版本的详细改动,请参阅 [CHANGELOG.md](file:///home/cathiefish/App/nosync/CHANGELOG.md)。 diff --git a/examples/demo.rs b/examples/demo.rs new file mode 100644 index 0000000..4f65233 --- /dev/null +++ b/examples/demo.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use nosync::{ModuleA, ModuleB}; +use tracing::info; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +#[tokio::main] +async fn main() -> Result<()> { + // Configure simple standard output logging + let subscriber = FmtSubscriber::builder() + .with_env_filter(EnvFilter::new("info")) + .finish(); + tracing::subscriber::set_global_default(subscriber) + .map_err(|e| anyhow::anyhow!("failed to set global tracing subscriber: {e}"))?; + + info!("--- Running nosync library example ---"); + + // Construct the structs and execute the logic + let processor = ModuleA::new("example-processor".to_string())?; + let orchestrator = ModuleB::new(processor)?; + + orchestrator.run().await?; + + info!("--- Example execution completed successfully ---"); + Ok(()) +} diff --git a/references/.gitkeep b/references/.gitkeep new file mode 100644 index 0000000..7b20408 --- /dev/null +++ b/references/.gitkeep @@ -0,0 +1 @@ +# Placeholder file to ensure references/ directory is tracked in Git. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2e667b9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +pub mod module_a; +pub mod module_b; +pub mod structs; + +pub use module_a::{ModuleA, ModuleAError}; +pub use module_b::{ModuleB, ModuleBError}; +pub use structs::SharedMessage; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f960f63 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,38 @@ +use anyhow::{Context, Result}; +use dotenvy::dotenv; +use nosync::{ModuleA, ModuleB}; +use std::env; +use tracing::info; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +#[tokio::main] +async fn main() -> Result<()> { + // Load environment variables from .env if present + let _ = dotenv(); + + // Initialize tracing subscriber with settings from environment (RUST_LOG) + let subscriber = FmtSubscriber::builder() + .with_env_filter(EnvFilter::from_default_env()) + .finish(); + tracing::subscriber::set_global_default(subscriber) + .map_err(|e| anyhow::anyhow!("failed to set global tracing subscriber: {e}"))?; + + info!("Starting nosync application..."); + + // Retrieve APP_NAME from environment variables, defaulting if not found + let app_name = env::var("APP_NAME").unwrap_or_else(|_| "nosync-default".to_string()); + info!(app_name = %app_name, "Environment configured"); + + // Initialize ModuleA and ModuleB + let module_a = ModuleA::new(app_name).context("Failed to initialize ModuleA")?; + let module_b = ModuleB::new(module_a).context("Failed to initialize ModuleB")?; + + // Run ModuleB logic + module_b + .run() + .await + .context("Error occurred during execution")?; + + info!("nosync application finished successfully!"); + Ok(()) +} diff --git a/src/module_a.rs b/src/module_a.rs new file mode 100644 index 0000000..11194fb --- /dev/null +++ b/src/module_a.rs @@ -0,0 +1,61 @@ +use crate::structs::SharedMessage; +use thiserror::Error; +use tracing::{debug, info}; + +/// Errors specific to ModuleA. +#[derive(Error, Debug)] +pub enum ModuleAError { + #[error("Failed to process message: {0}")] + ProcessError(String), +} + +/// ModuleA processes messages and manages a name field. +pub struct ModuleA { + name: String, +} + +impl ModuleA { + /// Creates a new ModuleA instance. + pub fn new(name: String) -> Result { + info!(name = %name, "Initializing ModuleA"); + Ok(Self { name }) + } + + /// Processes a shared message asynchronously. + pub async fn process_message(&self, msg: SharedMessage) -> Result { + debug!(msg_id = msg.id, "Processing message in ModuleA"); + if msg.content.is_empty() { + return Err(ModuleAError::ProcessError( + "Empty message content".to_string(), + )); + } + Ok(format!("ModuleA[{}] processed: {}", self.name, msg.content)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_process_message_success() { + let module_a = ModuleA::new("test-a".to_string()).unwrap(); + let msg = SharedMessage { + id: 42, + content: "Hello".to_string(), + }; + let result = module_a.process_message(msg).await.unwrap(); + assert_eq!(result, "ModuleA[test-a] processed: Hello"); + } + + #[tokio::test] + async fn test_process_message_empty_content() { + let module_a = ModuleA::new("test-a".to_string()).unwrap(); + let msg = SharedMessage { + id: 42, + content: String::new(), + }; + let result = module_a.process_message(msg).await; + assert!(result.is_err()); + } +} diff --git a/src/module_b.rs b/src/module_b.rs new file mode 100644 index 0000000..1c8e5c6 --- /dev/null +++ b/src/module_b.rs @@ -0,0 +1,48 @@ +use crate::module_a::ModuleA; +use crate::structs::SharedMessage; +use thiserror::Error; +use tracing::info; + +/// Errors specific to ModuleB. +#[derive(Error, Debug)] +pub enum ModuleBError { + #[error("ModuleA error occurred: {0}")] + AError(#[from] crate::module_a::ModuleAError), +} + +/// ModuleB holds a reference to ModuleA and interacts with it. +pub struct ModuleB { + processor: ModuleA, +} + +impl ModuleB { + /// Creates a new ModuleB instance by taking ownership of a ModuleA processor. + pub fn new(processor: ModuleA) -> Result { + info!("Initializing ModuleB"); + Ok(Self { processor }) + } + + /// Runs the business logic of ModuleB. + pub async fn run(&self) -> Result<(), ModuleBError> { + let msg = SharedMessage { + id: 100, + content: "Hello from ModuleB".to_string(), + }; + let result = self.processor.process_message(msg).await?; + info!(result = %result, "ModuleB execution succeeded"); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_module_b_run() { + let module_a = ModuleA::new("test-a".to_string()).unwrap(); + let module_b = ModuleB::new(module_a).unwrap(); + let result = module_b.run().await; + assert!(result.is_ok()); + } +} diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 0000000..c9f3683 --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,6 @@ +/// Shared message structure across modules. +#[derive(Debug, Clone)] +pub struct SharedMessage { + pub id: u64, + pub content: String, +} diff --git a/tests/module_a_test.rs b/tests/module_a_test.rs new file mode 100644 index 0000000..a954af6 --- /dev/null +++ b/tests/module_a_test.rs @@ -0,0 +1,16 @@ +use nosync::{ModuleA, SharedMessage}; + +#[tokio::test] +async fn test_integration_module_a() { + let module_a = ModuleA::new("integration-a".to_string()).expect("Failed to create ModuleA"); + let msg = SharedMessage { + id: 999, + content: "Integration content".to_string(), + }; + let res = module_a + .process_message(msg) + .await + .expect("Failed to process"); + assert!(res.contains("integration-a")); + assert!(res.contains("Integration content")); +} diff --git a/tests/module_b_test.rs b/tests/module_b_test.rs new file mode 100644 index 0000000..362a673 --- /dev/null +++ b/tests/module_b_test.rs @@ -0,0 +1,10 @@ +use nosync::{ModuleA, ModuleB}; + +#[tokio::test] +async fn test_integration_module_b() { + let module_a = + ModuleA::new("integration-b-processor".to_string()).expect("Failed to create ModuleA"); + let module_b = ModuleB::new(module_a).expect("Failed to create ModuleB"); + let res = module_b.run().await; + assert!(res.is_ok()); +}