| Crates.io | forseti_sdk |
| lib.rs | forseti_sdk |
| version | 0.1.3 |
| created_at | 2025-09-09 04:24:46.598374+00 |
| updated_at | 2025-09-13 06:10:20.779373+00 |
| description | Forseti SDK (Rust) — core types, linter+engine helpers, and ruleset API |
| homepage | |
| repository | https://github.com/forseti-linter/forseti-sdk/ |
| max_upload_size | |
| id | 1830260 |
| size | 80,149 |
The Forseti SDK is the foundation for building engines and rulesets for the Forseti linter ecosystem. It provides a minimal, language-agnostic protocol for communication between linters and engines, along with Rust implementations for building robust linting tools.
Forseti uses a protocol-based architecture where:
This design enables memory-efficient processing of large codebases and supports multiple programming languages through separate engines.
serde, anyhow, thiserror, and tomlcore - Protocol envelopes, NDJSON I/O, common types (Position/Range/Diagnostic)engine - Engine server implementation with capabilities and preprocessingruleset - Rule trait and ruleset container for memory-efficient executionlinter - Engine management, lifecycle, and discoveryconfig - Configuration system with git-based dependenciesCommunication uses NDJSON (newline-delimited JSON) with versioned envelopes:
{
"v": 1,
"kind": "req" | "res" | "event",
"type": "initialize" | "getCapabilities" | "analyzeFile" | ...,
"id": "string",
"payload": { ... }
}
initialize - Bootstrap engine with configurationgetDefaultConfig - Get engine's default configurationgetCapabilities - Query engine file patterns and limitspreprocessFiles - Process file list, return lightweight contextanalyzeFile - Analyze individual files (legacy mode)shutdown - Clean engine teardowndiagnostics - Emitted results from analysislog - Optional logging eventsuse forseti_sdk::{engine::*, core::*};
struct MyEngine;
impl EngineOptions for MyEngine {
fn get_default_config(&self) -> EngineConfig {
EngineConfig::default()
}
fn load_ruleset(&self, id: &str) -> anyhow::Result<Ruleset> {
// Load and return your ruleset
todo!()
}
fn get_capabilities(&self) -> EngineCapabilities {
EngineCapabilities {
engine_id: "my-engine".to_string(),
version: "1.0.0".to_string(),
file_patterns: vec!["*.txt".to_string()],
max_file_size: Some(1024 * 1024), // 1MB
}
}
fn preprocess_files(&self, file_uris: &[String]) -> anyhow::Result<PreprocessingContext> {
// Return lightweight file context
todo!()
}
}
fn main() -> anyhow::Result<()> {
let engine = MyEngine;
let mut server = EngineServer::new(Box::new(engine));
server.run_stdio()
}
use forseti_sdk::{ruleset::*, core::*};
struct NoTrailingWhitespace;
impl Rule for NoTrailingWhitespace {
fn id(&self) -> &'static str {
"no-trailing-whitespace"
}
fn check(&self, ctx: &mut RuleContext) {
let index = LineIndex::new(ctx.text);
for (line_num, line) in ctx.text.lines().enumerate() {
if line.ends_with(' ') || line.ends_with('\t') {
let start = Position { line: line_num, character: line.trim_end().len() };
let end = Position { line: line_num, character: line.len() };
ctx.diagnostics.push(Diagnostic {
rule_id: self.id().to_string(),
message: "Trailing whitespace found".to_string(),
severity: "warn".to_string(),
range: Range { start, end },
code: None,
suggest: None,
docs_url: None,
});
}
}
}
}
// Bundle into a ruleset
let ruleset = Ruleset::new("my-rules")
.with_rule(Box::new(NoTrailingWhitespace));
use forseti_sdk::linter::*;
let mut manager = EngineManager::new("/path/to/cache");
let engines = manager.discover_engines()?;
// Start an engine
manager.start_engine("my-engine", Some(config))?;
// Analyze files
let result = manager.analyze_file("my-engine", "file.txt", content)?;
// Cleanup
manager.shutdown_all()?;
Engines accept configuration in this format:
[engines.my-engine]
enabled = true
[engines.my-engine.rulesets.my-rules]
no-trailing-whitespace = "warn"
max-line-length = ["error", { limit = 100 }]
some-rule = "off"
Rules can be configured as:
"off" | "warn" | "error" - Simple severity levels[level, options] - Severity with custom options{ ...options } - Options object (implies enabled)cargo build # Build the SDK
cargo test # Run tests
cargo clippy # Lint code
cargo fmt # Format code
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trailing_whitespace() {
let rule = NoTrailingWhitespace;
let mut ctx = RuleContext {
uri: "test.txt",
text: "hello \nworld",
options: &serde_json::Value::Null,
diagnostics: Vec::new(),
};
rule.check(&mut ctx);
assert_eq!(ctx.diagnostics.len(), 1);
assert_eq!(ctx.diagnostics[0].rule_id, "no-trailing-whitespace");
}
}
The forseti-engine-base provides a complete example of:
The NDJSON protocol is language-agnostic. Engines can be implemented in any language that can:
cargo clippy passes without warningsMIT License - see LICENSE for details.
For detailed protocol specifications and advanced usage, see CLAUDE.md.