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 frameworktower- Middleware and service abstractiontower-http- HTTP-specific middleware (CORS, static files, tracing)hyper- Low-level HTTP implementation
Async Runtime
tokio- Async runtime with full feature settokio-stream- Stream utilitiesasync-trait- Async traitsasync-stream- Async stream macrosasync-lock- Async locking primitives
Database
diesel- ORM for PostgreSQLdiesel_migrations- Database migration managementr2d2- Connection poolingredis- Cache client (Valkey/Redis-compatible)
Storage
aws-config- AWS SDK configurationaws-sdk-s3- S3-compatible storage (drive component)qdrant-client- Vector database (optional)
Security
argon2- Password hashingaes-gcm- Encryptionhmac- HMAC authenticationsha2- SHA-256 hashing
Scripting
rhai- BASIC interpreter engine
Data Formats
serde- Serialization/deserializationserde_json- JSON supportcsv- CSV parsingbase64- Base64 encoding
Document Processing
pdf-extract- PDF text extractionmailparse- Email parsingzip- ZIP archive handling
Communication
reqwest- HTTP clientlettre- SMTP email sendingimap- IMAP email reading (optional)livekit- Video conferencing
Desktop (Optional)
tauri- Desktop application frameworktauri-plugin-dialog- Native file dialogstauri-plugin-opener- Open files/URLs
Utilities
anyhow- Error handlinglog- Logging facadeenv_logger- Environment-based loggingtracing- Structured loggingchrono- Date/time handlinguuid- UUID generationregex- Regular expressionsrand- Random number generation
Testing
mockito- HTTP mockingtempfile- 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
- Add optional dependency:
[dependencies]
elasticsearch = { version = "8.5", optional = true }
- Create feature:
[features]
search = ["elasticsearch"]
- Use conditional compilation:
#[cfg(feature = "search")]
pub mod search {
use elasticsearch::Elasticsearch;
pub fn create_client() -> Elasticsearch {
// Implementation
}
}
- 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:
- Add comment in
Cargo.toml:
[dependencies]
# JWT token generation and validation
jsonwebtoken = "9.2"
- 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
- Review Module Structure to understand where to use new dependencies
- Check Service Layer to see how dependencies integrate
- Read Creating Custom Keywords to extend BASIC with new functionality