feat: add as_meta parameter to content endpoint
Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) <aider@aider.chat>
This commit is contained in:
@@ -97,6 +97,120 @@ pub async fn handle_list_items(
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
/// Handle as_meta=true response by returning JSON with metadata and content
|
||||
async fn handle_as_meta_response(
|
||||
item_service: &AsyncItemService,
|
||||
item_id: i64,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
) -> Result<Response, StatusCode> {
|
||||
// Get the item with metadata
|
||||
let item_with_meta = item_service.get_item(item_id).await.map_err(|e| {
|
||||
warn!("Failed to get item {} for as_meta content: {}", item_id, e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
let metadata = item_with_meta.meta_as_map();
|
||||
handle_as_meta_response_with_metadata(item_service, item_id, &metadata, offset, length).await
|
||||
}
|
||||
|
||||
/// Handle as_meta=true response with pre-fetched metadata
|
||||
async fn handle_as_meta_response_with_metadata(
|
||||
item_service: &AsyncItemService,
|
||||
item_id: i64,
|
||||
metadata: &HashMap<String, String>,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
) -> Result<Response, StatusCode> {
|
||||
// Check if content is binary
|
||||
let is_binary = if let Some(text_val) = metadata.get("text") {
|
||||
text_val == "false"
|
||||
} else {
|
||||
// If text metadata isn't set, we need to check the content using streaming approach
|
||||
match item_service.get_item_content_info_streaming(item_id).await {
|
||||
Ok((_, _, is_binary)) => is_binary,
|
||||
Err(e) => {
|
||||
warn!("Failed to get content info for binary check for item {}: {}", item_id, e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Get the content if it's not binary
|
||||
if is_binary {
|
||||
// Return JSON with content as None and error message
|
||||
let response_body = serde_json::json!({
|
||||
"metadata": metadata,
|
||||
"content": serde_json::Value::Null,
|
||||
"error": "Content is binary"
|
||||
});
|
||||
|
||||
let response = Response::builder()
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.status(StatusCode::UNPROCESSABLE_ENTITY)
|
||||
.body(axum::body::Body::from(response_body.to_string()))
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
Ok(response)
|
||||
} else {
|
||||
// Get the content as text
|
||||
match item_service.get_item_content_info(item_id).await {
|
||||
Ok((content, _, _)) => {
|
||||
// Apply offset and length
|
||||
let content_len = content.len() as u64;
|
||||
let start = std::cmp::min(offset, content_len);
|
||||
let end = if length > 0 {
|
||||
std::cmp::min(start + length, content_len)
|
||||
} else {
|
||||
content_len
|
||||
};
|
||||
|
||||
let response_content = if start < content_len {
|
||||
&content[start as usize..end as usize]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
// Convert to UTF-8 string
|
||||
let content_str = match String::from_utf8(response_content.to_vec()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
// This shouldn't happen since we checked is_binary, but handle it just in case
|
||||
let response_body = serde_json::json!({
|
||||
"metadata": metadata,
|
||||
"content": serde_json::Value::Null,
|
||||
"error": "Content is not valid UTF-8"
|
||||
});
|
||||
|
||||
let response = Response::builder()
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.status(StatusCode::UNPROCESSABLE_ENTITY)
|
||||
.body(axum::body::Body::from(response_body.to_string()))
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
return Ok(response);
|
||||
}
|
||||
};
|
||||
|
||||
// Return JSON with metadata and content
|
||||
let response_body = serde_json::json!({
|
||||
"metadata": metadata,
|
||||
"content": content_str,
|
||||
"error": serde_json::Value::Null
|
||||
});
|
||||
|
||||
let response = Response::builder()
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(axum::body::Body::from(response_body.to_string()))
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
Ok(response)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to get content for item {}: {}", item_id, e);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
@@ -190,8 +304,14 @@ pub async fn handle_get_item_latest_content(
|
||||
Ok(item) => {
|
||||
let item_id = item.item.id.unwrap();
|
||||
let metadata = item.meta_as_map();
|
||||
// Handle as_meta parameter
|
||||
if params.as_meta {
|
||||
// Force stream=false and allow_binary=false for as_meta=true
|
||||
handle_as_meta_response_with_metadata(&item_service, item_id, &metadata, params.offset, params.length).await
|
||||
} else {
|
||||
stream_item_content_response_with_metadata(&item_service, item_id, &metadata, params.allow_binary, params.offset, params.length, params.stream).await
|
||||
}
|
||||
}
|
||||
Err(CoreError::ItemNotFoundGeneric) => Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
warn!("Failed to find latest item for content: {}", e);
|
||||
@@ -246,12 +366,21 @@ pub async fn handle_get_item_content(
|
||||
state.cmd.clone(),
|
||||
state.settings.clone()
|
||||
);
|
||||
let result = stream_item_content_response(&item_service, item_id, params.allow_binary, params.offset, params.length, params.stream).await;
|
||||
|
||||
// Handle as_meta parameter
|
||||
if params.as_meta {
|
||||
// Force stream=false and allow_binary=false for as_meta=true
|
||||
let result = handle_as_meta_response(&item_service, item_id, params.offset, params.length).await;
|
||||
if let Ok(response) = &result {
|
||||
debug!("ITEM_API: Response content-length: {:?}", response.headers().get("content-length"));
|
||||
}
|
||||
result
|
||||
} else {
|
||||
let result = stream_item_content_response(&item_service, item_id, params.allow_binary, params.offset, params.length, params.stream).await;
|
||||
if let Ok(response) = &result {
|
||||
debug!("ITEM_API: Response content-length: {:?}", response.headers().get("content-length"));
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_item_content_response(
|
||||
|
||||
@@ -130,6 +130,8 @@ pub struct ItemQuery {
|
||||
pub length: u64,
|
||||
#[serde(default = "default_stream")]
|
||||
pub stream: bool,
|
||||
#[serde(default = "default_as_meta")]
|
||||
pub as_meta: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
@@ -143,6 +145,8 @@ pub struct ItemContentQuery {
|
||||
pub length: u64,
|
||||
#[serde(default = "default_stream")]
|
||||
pub stream: bool,
|
||||
#[serde(default = "default_as_meta")]
|
||||
pub as_meta: bool,
|
||||
}
|
||||
|
||||
fn check_bearer_auth(auth_str: &str, expected_password: &str, expected_hash: &Option<String>) -> bool {
|
||||
@@ -169,6 +173,10 @@ fn default_stream() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn default_as_meta() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn check_basic_auth(auth_str: &str, expected_password: &str, expected_hash: &Option<String>) -> bool {
|
||||
if !auth_str.starts_with("Basic ") {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user