botserver/docs/src/chapter-07-gbapp/dependencies.md

9.6 KiB

Adding Dependencies

BotServer is a single-crate Rust application, so all dependencies are managed through the root Cargo.toml file. This guide covers how to add, update, and manage dependencies.

Adding a Dependency

Basic Dependency

To add a new crate, edit Cargo.toml and add it to the [dependencies] section:

[dependencies]
serde = "1.0"

Then update your dependencies:

cargo build

Dependency with Features

Many crates offer optional features. Enable them like this:

[dependencies]
tokio = { version = "1.41", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

Version-Specific Dependencies

Use specific version constraints:

[dependencies]
# Exact version
diesel = "=2.1.0"

# Minimum version
anyhow = ">=1.0.0"

# Compatible version (caret)
regex = "^1.11"

# Wildcard
uuid = "1.*"

Git Dependencies

Add dependencies directly from Git repositories:

[dependencies]
rhai = { git = "https://github.com/therealprof/rhai.git", branch = "features/use-web-time" }

Or use a specific commit:

[dependencies]
my-crate = { git = "https://github.com/user/repo", rev = "abc123" }

Or a tag:

[dependencies]
my-crate = { git = "https://github.com/user/repo", tag = "v1.0.0" }

Optional Dependencies

For features that aren't always needed:

[dependencies]
qdrant-client = { version = "1.12", optional = true }
imap = { version = "3.0.0-alpha.15", optional = true }

Then define features that enable them:

[features]
vectordb = ["qdrant-client"]
email = ["imap"]

Platform-Specific Dependencies

Add dependencies only for specific platforms:

[target.'cfg(unix)'.dependencies]
libc = "0.2"

[target.'cfg(windows)'.dependencies]
winapi = "0.3"

[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"

Existing Dependencies

BotServer currently uses these major dependencies:

Web Framework

  • axum - HTTP web framework
  • tower - Middleware and service abstraction
  • tower-http - HTTP-specific middleware (CORS, static files, tracing)
  • hyper - Low-level HTTP implementation

Async Runtime

  • tokio - Async runtime with full feature set
  • tokio-stream - Stream utilities
  • async-trait - Async traits
  • async-stream - Async stream macros
  • async-lock - Async locking primitives

Database

  • diesel - ORM for PostgreSQL
  • diesel_migrations - Database migration management
  • r2d2 - Connection pooling
  • redis - Cache client (Valkey/Redis-compatible)

Storage

  • aws-config - AWS SDK configuration
  • aws-sdk-s3 - S3-compatible storage (drive component)
  • qdrant-client - Vector database (optional)

Security

  • argon2 - Password hashing
  • aes-gcm - Encryption
  • hmac - HMAC authentication
  • sha2 - SHA-256 hashing

Scripting

  • rhai - BASIC interpreter engine

Data Formats

  • serde - Serialization/deserialization
  • serde_json - JSON support
  • csv - CSV parsing
  • base64 - Base64 encoding

Document Processing

  • pdf-extract - PDF text extraction
  • mailparse - Email parsing
  • zip - ZIP archive handling

Communication

  • reqwest - HTTP client
  • lettre - SMTP email sending
  • imap - IMAP email reading (optional)
  • livekit - Video conferencing

Desktop (Optional)

  • tauri - Desktop application framework
  • tauri-plugin-dialog - Native file dialogs
  • tauri-plugin-opener - Open files/URLs

Utilities

  • anyhow - Error handling
  • log - Logging facade
  • env_logger - Environment-based logging
  • tracing - Structured logging
  • chrono - Date/time handling
  • uuid - UUID generation
  • regex - Regular expressions
  • rand - Random number generation

Testing

  • mockito - HTTP mocking
  • tempfile - Temporary file handling

Adding a New Dependency: Example

Let's walk through adding a new dependency for JSON Web Tokens (JWT):

1. Choose a Crate

Search on crates.io:

cargo search jsonwebtoken

2. Add to Cargo.toml

[dependencies]
jsonwebtoken = "9.2"

3. Update Dependencies

cargo build

4. Import in Code

In your Rust file (e.g., src/auth/mod.rs):

use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};

5. Use the Dependency

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    exp: usize,
}

pub fn create_jwt(user_id: &str) -> Result<String, jsonwebtoken::errors::Error> {
    let expiration = chrono::Utc::now()
        .checked_add_signed(chrono::Duration::hours(24))
        .unwrap()
        .timestamp() as usize;

    let claims = Claims {
        sub: user_id.to_owned(),
        exp: expiration,
    };

    let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "secret".to_string());
    let token = encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(secret.as_ref()),
    )?;

    Ok(token)
}

Managing Dependencies

Update All Dependencies

cargo update

Update Specific Dependency

cargo update -p serde

Check for Outdated Dependencies

Install and use cargo-outdated:

cargo install cargo-outdated
cargo outdated

Upgrade to Latest Compatible Versions

Install and use cargo-edit:

cargo install cargo-edit
cargo upgrade

Audit for Security Vulnerabilities

cargo install cargo-audit
cargo audit

Check Dependency Tree

cargo tree

View dependencies for specific package:

cargo tree -p diesel

Find Duplicate Dependencies

cargo tree --duplicates

Feature Management

BotServer uses feature flags to enable optional functionality.

Current Features

[features]
default = ["desktop"]
vectordb = ["qdrant-client"]
email = ["imap"]
desktop = ["dep:tauri", "dep:tauri-plugin-dialog", "dep:tauri-plugin-opener"]

Adding a New Feature

  1. Add optional dependency:
[dependencies]
elasticsearch = { version = "8.5", optional = true }
  1. Create feature:
[features]
search = ["elasticsearch"]
  1. Use conditional compilation:
#[cfg(feature = "search")]
pub mod search {
    use elasticsearch::Elasticsearch;
    
    pub fn create_client() -> Elasticsearch {
        // Implementation
    }
}
  1. Build with feature:
cargo build --features search

Build Dependencies

For build-time dependencies (used in build.rs):

[build-dependencies]
tauri-build = { version = "2", features = [] }

Development Dependencies

For dependencies only needed during testing:

[dev-dependencies]
mockito = "1.7.0"
tempfile = "3"

These are not included in release builds.

Dependency Best Practices

1. Version Pinning

Use specific versions for production:

# Good - specific version
serde = "1.0.193"

# Risky - any 1.x version
serde = "1"

2. Minimize Dependencies

Only add dependencies you actually need. Each dependency:

  • Increases build time
  • Increases binary size
  • Adds maintenance burden
  • Introduces security risk

3. Check License Compatibility

Ensure dependency licenses are compatible with AGPL-3.0:

cargo install cargo-license
cargo license

4. Prefer Maintained Crates

Check crate activity:

  • Recent releases
  • Active GitHub repository
  • Responsive maintainers
  • Good documentation

5. Review Security Advisories

Regularly audit dependencies:

cargo audit

6. Use Features to Reduce Size

Don't enable unnecessary features:

# Bad - includes everything
tokio = "1.41"

# Good - only what you need
tokio = { version = "1.41", features = ["rt-multi-thread", "net", "sync"] }

Common Issues

Conflicting Versions

If multiple crates need different versions of the same dependency:

error: failed to select a version for `serde`

Solution: Update dependencies or use cargo tree to identify conflicts.

Missing System Libraries

If a dependency requires system libraries:

error: linking with `cc` failed

Solution: Install required system packages (see Building from Source).

Feature Not Found

If you reference a non-existent feature:

error: feature `invalid-feature` is not found

Solution: Check feature names in Cargo.toml.

Removing Dependencies

1. Remove from Cargo.toml

Delete the dependency line.

2. Remove Imports

Find and remove all use statements:

rg "use dependency_name" src/

3. Clean Build

cargo clean
cargo build

4. Verify

Check the dependency is gone:

cargo tree | grep dependency_name

Alternative Registries

Using a Custom Registry

[dependencies]
my-crate = { version = "1.0", registry = "my-registry" }

[registries.my-registry]
index = "https://my-registry.example.com/index"

Private Crates

For private company crates, use Git dependencies or a private registry like Artifactory or CloudSmith.

Dependency Documentation

After adding a dependency, document its usage:

  1. Add comment in Cargo.toml:
[dependencies]
# JWT token generation and validation
jsonwebtoken = "9.2"
  1. Document in code:
/// Creates a JWT token for user authentication.
/// 
/// Uses the `jsonwebtoken` crate to encode user claims
/// with an expiration time.
pub fn create_jwt(user_id: &str) -> Result<String, Error> {
    // Implementation
}

Next Steps