use crate::ast::AstContext;
use chrono::Utc;
use clap::ValueEnum;
use serde::Serialize;
use std::fmt;
const SCHEMA_VERSION: &str = "1.0.0";
#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum OutputFormat {
Human,
Json,
Pretty,
}
impl fmt::Display for OutputFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
OutputFormat::Human => "human",
OutputFormat::Json => "json",
OutputFormat::Pretty => "pretty",
};
write!(f, "{}", value)
}
}
#[derive(Serialize, Clone, Debug, Default)]
pub struct PerformanceMetrics {
pub backend_detection_ms: u64,
pub query_execution_ms: u64,
pub output_formatting_ms: u64,
pub total_ms: u64,
}
impl PerformanceMetrics {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Serialize)]
pub struct JsonResponse<T> {
pub schema_version: &'static str,
pub execution_id: String,
pub tool: &'static str,
pub timestamp: String,
pub partial: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub performance: Option<PerformanceMetrics>,
pub data: T,
}
#[derive(Serialize)]
pub struct ErrorResponse {
pub code: String,
pub error: String,
pub message: String,
pub span: Option<Span>,
pub remediation: Option<String>,
}
#[derive(Serialize)]
pub struct Span {
pub span_id: String,
pub file_path: String,
pub byte_start: u64,
pub byte_end: u64,
pub start_line: u64,
pub start_col: u64,
pub end_line: u64,
pub end_col: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<SpanContext>,
}
#[derive(Serialize)]
pub struct SpanContext {
pub before: Vec<String>,
pub selected: Vec<String>,
pub after: Vec<String>,
pub truncated: bool,
}
#[derive(Serialize)]
pub struct SymbolMatch {
pub match_id: String,
pub span: Span,
pub name: String,
pub kind: String,
pub parent: Option<String>,
pub symbol_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fqn: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub canonical_fqn: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_fqn: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol_kind_from_chunk: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet_truncated: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind_normalized: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub complexity_score: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fan_in: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fan_out: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cyclomatic_complexity: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ast_context: Option<AstContext>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supernode_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub coverage: Option<CoverageInfo>,
}
#[derive(Serialize, Clone, Debug)]
pub struct CoverageInfo {
pub total_blocks: u64,
pub covered_blocks: u64,
pub block_percentage: f64,
pub total_edges: u64,
pub covered_edges: u64,
pub edge_percentage: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub recorded_at: Option<String>,
}
#[derive(Serialize)]
pub struct ReferenceMatch {
pub match_id: String,
pub span: Span,
pub referenced_symbol: String,
pub reference_kind: Option<String>,
pub target_symbol_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol_kind_from_chunk: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet_truncated: Option<bool>,
}
#[derive(Serialize)]
pub struct CallMatch {
pub match_id: String,
pub span: Span,
pub caller: String,
pub callee: String,
pub caller_symbol_id: Option<String>,
pub callee_symbol_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol_kind_from_chunk: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet_truncated: Option<bool>,
}
#[derive(Serialize)]
pub struct SearchResponse {
pub results: Vec<SymbolMatch>,
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_filter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind_filter: Option<String>,
pub total_count: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub notice: Option<String>,
}
#[derive(Serialize)]
pub struct ReferenceSearchResponse {
pub results: Vec<ReferenceMatch>,
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_filter: Option<String>,
pub total_count: u64,
}
#[derive(Serialize)]
pub struct CallSearchResponse {
pub results: Vec<CallMatch>,
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_filter: Option<String>,
pub total_count: u64,
}
#[derive(Serialize)]
pub struct ImplementsMatch {
pub match_id: String,
pub span: Span,
pub type_name: String,
pub trait_name: String,
pub type_symbol_id: Option<String>,
pub trait_symbol_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol_kind_from_chunk: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet_truncated: Option<bool>,
}
#[derive(Serialize)]
pub struct ImplementsSearchResponse {
pub results: Vec<ImplementsMatch>,
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_filter: Option<String>,
pub total_count: u64,
}
#[derive(Serialize, Debug)]
pub struct DocsMatch {
pub id: i64,
pub path_or_uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub observed_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_timestamp: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wikilinks: Option<String>,
}
#[derive(Serialize, Debug)]
pub struct DocsSearchResponse {
pub results: Vec<DocsMatch>,
pub total_count: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_filter: Option<String>,
}
#[derive(Serialize, Debug)]
pub struct FactMatch {
pub id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub candidate_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_document_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub predicate: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub object_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub object_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties_json: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rejection_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reviewed_at: Option<i64>,
}
#[derive(Serialize, Debug)]
pub struct FactsSearchResponse {
pub results: Vec<FactMatch>,
pub total_count: u64,
}
#[derive(Serialize)]
pub struct CombinedSearchResponse {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_filter: Option<String>,
pub symbols: SearchResponse,
pub references: ReferenceSearchResponse,
pub calls: CallSearchResponse,
pub total_count: u64,
pub limit_mode: String,
}
pub fn json_response<T>(data: T) -> JsonResponse<T> {
json_response_with_partial(data, false)
}
pub fn json_response_with_partial<T>(data: T, partial: bool) -> JsonResponse<T> {
json_response_with_partial_and_performance(data, partial, None)
}
pub fn json_response_with_partial_and_performance<T>(
data: T,
partial: bool,
performance: Option<PerformanceMetrics>,
) -> JsonResponse<T> {
JsonResponse {
schema_version: SCHEMA_VERSION,
execution_id: execution_id(),
tool: "llmgrep",
timestamp: Utc::now().to_rfc3339(),
partial,
performance,
data,
}
}
pub fn execution_id() -> String {
let timestamp = Utc::now().timestamp();
let pid = std::process::id();
format!("{:x}-{:x}", timestamp, pid)
}