Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 0 additions & 66 deletions src/api/bundle.rs

This file was deleted.

57 changes: 1 addition & 56 deletions src/api/cas.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::api::client::ApiClient;
use crate::api::types::{
ApiErrorResponse, CAPromptStoreReadResponse, CasUploadRequest, CasUploadResponse,
};
use crate::api::types::{ApiErrorResponse, CasUploadRequest, CasUploadResponse};
use crate::error::GitAiError;

/// CAS API endpoints
Expand Down Expand Up @@ -56,57 +54,4 @@ impl ApiClient {
))),
}
}

/// Read CAS objects by hash from the server
///
/// # Arguments
/// * `hashes` - Slice of CAS hashes to fetch (max 100 per call)
///
/// # Returns
/// * `Ok(CAPromptStoreReadResponse)` - Response with results for each hash
/// * `Err(GitAiError)` - On network or server errors
pub fn read_ca_prompt_store(
&self,
hashes: &[&str],
) -> Result<CAPromptStoreReadResponse, GitAiError> {
// Validate all hashes are hex-only before building the URL to prevent
// injection via crafted hash values in the query string.
for hash in hashes {
if !hash.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(GitAiError::Generic(format!(
"CAS hash contains non-hex characters: {}",
hash
)));
}
}

let query = hashes.join(",");
let endpoint = format!("/worker/cas/?hashes={}", query);
let response = self.context().get(&endpoint)?;
let status_code = response.status_code;

let body = response
.as_str()
.map_err(|e| GitAiError::Generic(format!("Failed to read response body: {}", e)))?;

match status_code {
200 => {
let cas_response: CAPromptStoreReadResponse =
serde_json::from_str(body).map_err(GitAiError::JsonError)?;
Ok(cas_response)
}
404 => {
// All hashes not found — return empty response gracefully
Ok(CAPromptStoreReadResponse {
results: Vec::new(),
success_count: 0,
failure_count: hashes.len(),
})
}
_ => Err(GitAiError::Generic(format!(
"CAS read failed with status {}: {}",
status_code, body
))),
}
}
}
1 change: 0 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod bundle;
pub mod cas;
pub mod client;
pub mod metrics;
Expand Down
86 changes: 1 addition & 85 deletions src/api/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::authorship::authorship_log::{LineRange, PromptRecord};
use crate::authorship::authorship_log::LineRange;
use crate::commands::diff::FileDiffJson;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -44,34 +44,6 @@ impl From<&FileDiffJson> for ApiFileRecord {
}
}

/// Bundle data containing prompts and optional files
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BundleData {
/// REQUIRED: At least one prompt
pub prompts: HashMap<String, PromptRecord>,
/// OPTIONAL: File diffs and annotations
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub files: HashMap<String, ApiFileRecord>,
}

/// Request body for creating a bundle
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CreateBundleRequest {
/// Bundle title (min 1 character)
pub title: String,
/// Bundle data containing prompts and optional files
pub data: BundleData,
// TODO PR Metadata if linked to PR
}

/// Success response from bundle creation
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateBundleResponse {
pub success: bool,
pub id: String,
pub url: String,
}

/// Error response from API
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApiErrorResponse {
Expand Down Expand Up @@ -112,12 +84,6 @@ pub struct CasUploadResponse {
pub failure_count: usize,
}

/// Wrapper for messages stored in CAS
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CasMessagesObject {
pub messages: Vec<crate::authorship::transcript::Message>,
}

/// A single authorship note entry (commit SHA + content).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NoteEntry {
Expand All @@ -144,25 +110,6 @@ pub struct NotesReadResponse {
pub notes: std::collections::HashMap<String, String>,
}

/// Single result from CA prompt store batch read
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CAPromptStoreReadResult {
pub hash: String,
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}

/// Response from CA prompt store batch read
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CAPromptStoreReadResponse {
pub results: Vec<CAPromptStoreReadResult>,
pub success_count: usize,
pub failure_count: usize,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -280,20 +227,6 @@ mod tests {
assert_eq!(ranges[2], serde_json::Value::Number(20.into()));
}

#[test]
fn test_create_bundle_response_deserialization() {
let json = r#"{
"success": true,
"id": "bundle123",
"url": "https://example.com/bundle123"
}"#;

let response: CreateBundleResponse = serde_json::from_str(json).unwrap();
assert!(response.success);
assert_eq!(response.id, "bundle123");
assert_eq!(response.url, "https://example.com/bundle123");
}

#[test]
fn test_api_error_response_serialization() {
let error = ApiErrorResponse {
Expand Down Expand Up @@ -428,21 +361,4 @@ mod tests {
let cloned = record.clone();
assert_eq!(record, cloned);
}

#[test]
fn test_cas_messages_object() {
use crate::authorship::transcript::Message;

let messages = vec![Message::user("test".to_string(), None)];

let cas_msg = CasMessagesObject {
messages: messages.clone(),
};

let json = serde_json::to_string(&cas_msg).unwrap();
assert!(json.contains("test"));

let deserialized: CasMessagesObject = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.messages.len(), 1);
}
}
Loading