parlov-output
Output formatting layer — terminal table rendering, structured JSON with DTO layer, and SARIF v2.1.0 document generation.
Version: 0.6.0 | Files: 4 | Lines: 1,047 | Dependencies: comfy-table, parlov-core, serde, serde-sarif, serde_json
The output formatting layer. Transforms OracleResult and ScanFinding values into human-readable tables, machine-readable JSON, and SARIF v2.1.0 for security tooling integration. Pure synchronous computation — no I/O.
Public API
Render Functions
Two families of render functions: single-finding (from existence subcommand) and scan-level (from scan subcommand).
Single-finding renderers:
| Function | Signature | Output |
|---|---|---|
render_table | (&OracleResult) -> String | ANSI-colored terminal table |
render_json | (&str, &OracleResult, &str, &str, &str) -> Result<String> | Structured JSON |
render_sarif | (&str, &OracleResult, &str, &str) -> Result<String> | SARIF v2.1.0 document |
render_json parameters: target_url, result, strategy_id, strategy_name, method. render_sarif parameters: target_url, result, strategy_id, method. Context needed for JSON/SARIF that isn't carried on OracleResult itself.
Scan-level renderers:
| Function | Signature | Output |
|---|---|---|
render_scan_table | (&[ScanFinding]) -> String | Multi-row terminal table |
render_scan_json | (&str, &[ScanFinding]) -> Result<String> | Array of findings as JSON |
render_scan_sarif | (&str, &[ScanFinding]) -> Result<String> | Full SARIF document with all findings |
ScanFinding
The intermediate struct that bridges the scan runner and the output layer:
pub struct ScanFinding {
pub target_url: String,
pub strategy_id: String,
pub strategy_name: String,
pub method: String,
pub result: OracleResult,
}Internal Architecture
JSON Output (json.rs)
Internal DTO structs that shape the JSON output:
| DTO | Purpose |
|---|---|
SingleFindingOutput | Top-level wrapper for single-finding JSON |
ScanOutput | Top-level wrapper for scan JSON, contains findings[] |
FindingOutput | Per-finding body (target, method, strategy, result) |
StrategyOutput | Strategy metadata (id, name) |
ResultOutput | Analysis result (oracle_class, verdict, confidence, severity, impact_class) |
TechniqueOutput | Technique metadata (id, vector, normative_strength) |
MatchedPatternOutput | Pattern match context (label, leaks, rfc_basis) |
EvidenceOutput | Structured reasons and signals arrays |
The DTO layer exists to decouple the JSON schema from internal OracleResult field layout. This lets the output format evolve independently.
SARIF Output (sarif.rs)
Produces SARIF v2.1.0 documents compliant with the OASIS standard. Key internal functions:
| Function | Purpose |
|---|---|
verdict_level(OracleVerdict) -> &str | Maps verdict to SARIF level (error/warning/note) |
build_rule(OracleResult) -> serde_json::Value | Builds a SARIF rule from the oracle result |
build_sarif_result(...) | Builds a SARIF result with message, level, fingerprints |
build_sarif_document(...) | Assembles the complete SARIF envelope (schema, version, runs, tool, results) |
SARIF mapping details:
Confirmed-> level"error"Likely-> level"warning"Inconclusive-> level"note"NotPresent-> level"note"fingerprintsuses key"oracleFingerprint/v1"with the deterministicfinding_idfromparlov-corepartialFingerprintsuses key"techniqueTargetHash/v1"for technique+target deduplicationruleIdis derived fromtechnique_id- Tool name:
"parlov", semantic version fromCargo.toml
Table Output (table.rs)
Terminal table rendering using comfy-table. Features:
- ANSI color coding by verdict (green=NotPresent, yellow=Likely, red=Confirmed)
- Severity column with color coding
- Confidence as integer with optional impact class (e.g. "85 (High)")
- Primary evidence extraction
- Scan tables include strategy name and method columns
- Snapshot-tested against golden files
Output Format Examples
JSON (single finding)
{
"schema_version": "0.6.0",
"target_url": "https://api.example.com/users/9999",
"finding": {
"finding_id": "a1b2c3d4e5f6",
"strategy": { "id": "accept-elicit", "name": "Accept header", "method": "GET" },
"result": {
"oracle_class": "Existence",
"verdict": "Confirmed",
"severity": "Medium",
"confidence": 85,
"impact_class": "High"
},
"technique": { "id": "accept", "vector": "StatusCodeDiff", "normative_strength": "Should" },
"matched_pattern": { "label": "OK/Not Found", "leaks": null, "rfc_basis": "RFC 9110 §15.5.5" },
"evidence": {
"reasons": [],
"signals": []
}
}
}SARIF (single finding)
{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": { "driver": { "name": "parlov", "version": "0.6.0", "rules": [] } },
"results": [{
"ruleId": "accept",
"level": "error",
"message": { "text": "Existence Confirmed (confidence 85): 200 vs 404" },
"fingerprints": { "oracleFingerprint/v1": "a1b2c3d4e5f6" },
"partialFingerprints": { "techniqueTargetHash/v1": "f6e5d4c3b2a1" }
}]
}]
}Extension Points
Adding a new output format: Add a new module (e.g. csv.rs), implement a render_* function that takes &OracleResult or &[ScanFinding], and wire it into the CLI's OutputFormat enum in the parlov binary crate.
Evolving JSON schema: Modify the DTO structs in json.rs. The DTO layer insulates the JSON schema from OracleResult field changes. Add snapshot tests for new fields.