use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct ItemInfo {
pub id: String,
pub name: String,
pub kind: String,
pub path: Vec<String>,
pub docs: Option<String>,
pub visibility: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct ItemPreview {
pub id: String,
pub name: String,
pub kind: String,
pub path: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct PaginationInfo {
pub total: usize,
pub limit: usize,
pub offset: usize,
pub has_more: bool,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct ListCrateItemsOutput {
pub items: Vec<ItemInfo>,
pub pagination: PaginationInfo,
}
impl ListCrateItemsOutput {
pub fn to_json(&self) -> String {
serde_json::to_string(self)
.unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SearchItemsOutput {
pub items: Vec<ItemInfo>,
pub pagination: PaginationInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub warning: Option<String>,
}
impl SearchItemsOutput {
pub fn to_json(&self) -> String {
serde_json::to_string(self)
.unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SearchItemsPreviewOutput {
pub items: Vec<ItemPreview>,
pub pagination: PaginationInfo,
}
impl SearchItemsPreviewOutput {
pub fn to_json(&self) -> String {
serde_json::to_string(self)
.unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct SourceLocation {
pub filename: String,
pub line_start: usize,
pub column_start: usize,
pub line_end: usize,
pub column_end: usize,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct DetailedItem {
pub info: ItemInfo,
pub signature: Option<String>,
pub generics: Option<serde_json::Value>,
pub fields: Option<Vec<ItemInfo>>,
pub variants: Option<Vec<ItemInfo>>,
pub methods: Option<Vec<ItemInfo>>,
pub source_location: Option<SourceLocation>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum GetItemDetailsOutput {
Success(Box<DetailedItem>),
Error { error: String },
}
impl GetItemDetailsOutput {
pub fn to_json(&self) -> String {
serde_json::to_string(self)
.unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
}
pub fn is_success(&self) -> bool {
matches!(self, GetItemDetailsOutput::Success(_))
}
pub fn is_error(&self) -> bool {
matches!(self, GetItemDetailsOutput::Error { .. })
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct GetItemDocsOutput {
pub documentation: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl GetItemDocsOutput {
pub fn to_json(&self) -> String {
serde_json::to_string(self)
.unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SourceInfo {
pub location: SourceLocation,
pub code: String,
pub context_lines: Option<usize>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum GetItemSourceOutput {
Success(SourceInfo),
Error { error: String },
}
impl GetItemSourceOutput {
pub fn to_json(&self) -> String {
serde_json::to_string(self)
.unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
}
pub fn is_success(&self) -> bool {
matches!(self, GetItemSourceOutput::Success(_))
}
pub fn is_error(&self) -> bool {
matches!(self, GetItemSourceOutput::Error { .. })
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct DocsErrorOutput {
pub error: String,
}
impl DocsErrorOutput {
pub fn new(message: impl Into<String>) -> Self {
Self {
error: message.into(),
}
}
pub fn to_json(&self) -> String {
serde_json::to_string(self)
.unwrap_or_else(|_| r#"{"error":"Failed to serialize error"}"#.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_list_items_output_serialization() {
let output = ListCrateItemsOutput {
items: vec![ItemInfo {
id: "1".to_string(),
name: "test_fn".to_string(),
kind: "function".to_string(),
path: vec!["test".to_string()],
docs: Some("Test function".to_string()),
visibility: "public".to_string(),
}],
pagination: PaginationInfo {
total: 1,
limit: 100,
offset: 0,
has_more: false,
},
};
let json = output.to_json();
let deserialized: ListCrateItemsOutput = serde_json::from_str(&json).unwrap();
assert_eq!(output, deserialized);
}
#[test]
fn test_search_preview_output() {
let output = SearchItemsPreviewOutput {
items: vec![ItemPreview {
id: "42".to_string(),
name: "MyStruct".to_string(),
kind: "struct".to_string(),
path: vec!["my_mod".to_string()],
}],
pagination: PaginationInfo {
total: 1,
limit: 10,
offset: 0,
has_more: false,
},
};
let json = output.to_json();
let deserialized: SearchItemsPreviewOutput = serde_json::from_str(&json).unwrap();
assert_eq!(output, deserialized);
}
#[test]
fn test_item_details_output() {
let success = GetItemDetailsOutput::Success(Box::new(DetailedItem {
info: ItemInfo {
id: "1".to_string(),
name: "test".to_string(),
kind: "function".to_string(),
path: vec![],
docs: None,
visibility: "public".to_string(),
},
signature: Some("fn test()".to_string()),
generics: None,
fields: None,
variants: None,
methods: None,
source_location: None,
}));
assert!(success.is_success());
assert!(!success.is_error());
let error = GetItemDetailsOutput::Error {
error: "Not found".to_string(),
};
assert!(!error.is_success());
assert!(error.is_error());
}
}