Architecture¶
Understanding Feluda’s internal architecture for contributors.
Error Handling¶
Feluda uses a custom error type for consistent error handling. When adding new code, use the FeludaError and FeludaResult types:
// Return a Result with a specific error type
fn my_function() -> FeludaResult<MyType> {
match some_operation() {
Ok(result) => Ok(result),
Err(err) => Err(FeludaError::Parser(format!("Operation failed: {}", err)))
}
}
Available Error Types¶
The FeludaError enum provides specific error variants for different error scenarios. Use the most specific error type that matches your situation:
Error Variant |
Use Case |
Example |
|---|---|---|
|
File system operations, I/O errors |
File read/write failures (auto-converted via |
|
Network requests, API calls |
HTTP client errors (auto-converted via |
|
Configuration loading/validation |
Invalid config values, missing required settings |
|
License analysis, compatibility checks |
Invalid license format, compatibility violations |
|
Dependency file parsing |
Malformed package.json, invalid Cargo.toml |
|
Git repository cloning |
Clone failures, authentication issues |
|
Temporary directory operations |
Failed to create or access temp directories |
|
TUI initialization |
Terminal setup failures, color_eyre errors |
|
TUI runtime operations |
Runtime errors during TUI execution |
|
JSON/YAML serialization |
Failed to serialize SBOM documents |
|
File write operations |
Failed to write SBOM or license files |
|
Data validation |
Malformed SPDX data, invalid characters |
|
Fallback for uncategorized errors |
Use only when no specific type fits |
Guidelines:
Prefer specific error types over
UnknownInclude context in error messages:
FeludaError::Parser(format!("Failed to parse {}: {}", file, err))Use
map_err()to convert errors:.map_err(|e| FeludaError::Serialization(format!("Failed to serialize: {e}")))?IoandHttperrors are auto-converted via theFromtrait, no manual conversion needed
Cache Architecture¶
Feluda implements a multi-tier caching strategy to improve performance on repeated analyses. Currently, GitHub License Cache is implemented.
GitHub License Cache¶
This caches the GitHub Licenses API data to avoid repeated network requests.
Implementation Details:
Storage:
~/.feluda/cache/github_licenses.jsonTTL: 30 days (configurable via
CACHE_TTL_SECSconstant)Size: ~5-10 KB for typical GitHub license database
Files:
src/cache.rs: Core caching modulesrc/licenses.rs: Integration point (load/save)src/main.rs: CLI command handler
Key Functions (src/cache.rs):
pub fn load_github_licenses_from_cache() -> FeludaResult<Option<HashMap<String, License>>>
pub fn save_github_licenses_to_cache(licenses: &HashMap<String, License>) -> FeludaResult<()>
pub fn get_cache_status() -> FeludaResult<CacheStatus>
pub fn clear_github_licenses_cache() -> FeludaResult<()>
CacheStatus Structure:
pub struct CacheStatus {
pub exists: bool, // Whether cache file exists
pub path: PathBuf, // Full cache file path
pub size_bytes: u64, // Cache file size in bytes
pub is_fresh: bool, // Whether cache is within TTL
pub age_secs: u64, // Cache age in seconds
pub license_count: usize, // Number of licenses cached
}
CLI Integration:
# View cache status
feluda cache
# Clear cache
feluda cache --clear
Performance Impact:
First run: Full GitHub API call (~30-60 seconds depending on network)
Subsequent runs (cache hit): Instant license loading
Cache miss/stale: Falls back to GitHub API automatically
Typical speedup: 50-100x faster for analyses within 30 days
Future Considerations (TODO)¶
Per-Package License Cache
Cache individual package license lookups
Key:
{language}:{package_name}:{version}Useful for monorepos with repeated dependencies
Storage:
~/.feluda/cache/packages.jsonExample: npm/Rust packages with same versions
Dependency Manifest Cache
Cache parsed dependency lists with mtime tracking
Skips re-parsing unchanged manifest files
Requires careful invalidation logic
Useful for frequent CI/CD runs on same project
Testing Cache Implementation¶
When working with cache functionality:
# Test cache status display
cargo run -- cache
# Clear cache before testing
cargo run -- cache --clear
# Verify cache is created after analysis
cargo run -- --path examples/rust-example
cargo run -- cache
# Verify cache is used on second run
cargo run -- --path examples/rust-example --debug 2>&1 | grep -i cache
Cache Debugging¶
Enable debug mode to see cache operations:
feluda --debug 2>&1 | grep -i cache
# Output will show:
# - Cache hit/miss
# - Cache age and freshness
# - Save/load operations
OSI Integration¶
Feluda integrates with the Open Source Initiative (OSI) to provide license approval status information. The OSI integration consists of several components that work together to fetch, cache, and display OSI approval status for licenses.
OSI Integration Components¶
OSI API Integration (
src/licenses.rs):fetch_osi_licenses(): Fetches approved licenses from OSI APIOsiLicenseInfostruct: Represents OSI license data structureConcurrent HTTP requests with tokio for performance
Handles API failures gracefully with fallback mechanisms
OSI Status Management:
OsiStatusenum:Approved,NotApproved,Unknownget_osi_status(): Maps SPDX license IDs to OSI approval statusStatic fallback mappings for common licenses when API is unavailable
Integration in all language parsers to include OSI status in
LicenseInfo
Display Integration:
OSI status column in verbose table mode (
src/table.rs)OSI status in JSON/YAML output formats (
src/reporter.rs)Color-coded OSI status display in TUI mode
CLI filtering with
--osiflag (src/cli.rs)
Modifying OSI Integration¶
When working with OSI integration:
Adding New Static Mappings: Update get_osi_status() in src/licenses.rs:
pub fn get_osi_status(license_id: &str, osi_licenses: &[OsiLicenseInfo]) -> OsiStatus {
// Add new static mappings here for licenses not in OSI API
match license_id {
"NEW-LICENSE-ID" => OsiStatus::Approved, // If you know it's OSI approved
// ... existing mappings
}
}
Testing OSI Integration:
# Test OSI API connectivity and filtering
cargo run -- --osi approved --verbose
# Test fallback behavior (when API fails)
# Temporarily break API URL in code and test
# Test JSON output includes osi_status field
cargo run -- --json | jq '.[0].osi_status'