use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
pub struct Document {
pub id: String,
pub collection_id: String,
pub filename: String,
pub file_type: String,
pub file_size: u64,
pub chunk_count: u32,
pub status: String,
pub error_message: Option<String>,
pub metadata: serde_json::Value,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DocumentListResponse {
pub documents: Vec<Document>,
pub total: u32,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct DocumentListOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DocumentChunk {
pub id: String,
pub document_id: String,
pub chunk_index: u32,
pub text: String,
pub metadata: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DocumentChunkListResponse {
pub chunks: Vec<DocumentChunk>,
pub total: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchStatusItem {
pub id: String,
pub status: String,
pub error_message: Option<String>,
pub chunk_count: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchStatusResponse {
pub documents: Vec<BatchStatusItem>,
pub total: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchGetDocumentsResponse {
pub documents: Vec<Document>,
pub total: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchDeleteDocumentsResponse {
pub status: String,
pub deleted: u32,
pub errors: Vec<BatchDeleteError>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchDeleteError {
pub document_id: String,
pub error: String,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct S3IngestBody {
pub bucket: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoint_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secret_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub no_sign_request: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_types: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct S3IngestResponse {
pub status: String,
pub message: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct S3Job {
pub id: String,
pub collection_name: String,
pub bucket: String,
pub prefix: String,
pub region: String,
pub status: String,
pub total_found: u32,
pub total_ingested: u32,
pub total_skipped: u32,
pub error_message: Option<String>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct S3JobListResponse {
pub jobs: Vec<S3Job>,
pub total: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_document() {
let json = r#"{"id":"doc-1","collection_id":"col-1","filename":"report.pdf","file_type":"pdf","file_size":1024,"chunk_count":10,"status":"ready","error_message":null,"metadata":{},"created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z"}"#;
let doc: Document = serde_json::from_str(json).unwrap();
assert_eq!(doc.filename, "report.pdf");
assert_eq!(doc.status, "ready");
assert_eq!(doc.error_message, None);
}
#[test]
fn test_deserialize_batch_delete_response() {
let json = r#"{"status":"ok","deleted":3,"errors":[{"document_id":"x","error":"not found"}]}"#;
let resp: BatchDeleteDocumentsResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.deleted, 3);
assert_eq!(resp.errors.len(), 1);
assert_eq!(resp.errors[0].document_id, "x");
}
#[test]
fn test_serialize_s3_ingest_body_skips_none() {
let body = S3IngestBody {
bucket: "my-bucket".into(),
..Default::default()
};
let json = serde_json::to_value(&body).unwrap();
assert_eq!(json["bucket"], "my-bucket");
assert!(json.get("prefix").is_none());
assert!(json.get("access_key").is_none());
}
#[test]
fn test_deserialize_s3_job() {
let json = r#"{"id":"job-1","collection_name":"docs","bucket":"b","prefix":"p/","region":"us-east-1","status":"running","total_found":100,"total_ingested":50,"total_skipped":5,"error_message":null,"created_at":"","updated_at":""}"#;
let job: S3Job = serde_json::from_str(json).unwrap();
assert_eq!(job.status, "running");
assert_eq!(job.total_found, 100);
}
}