refactor(handlers): extract LearnParams + reusable RBAC helpers
learn_handler refactored: - Extract LearnParams struct with validation + bounds clamping - Extract store_compacted_memory helper - Extract build_learn_response helper - Reuse check_project_write_access for RBAC ingest_handler refactored: - Extract check_project_write_access (reusable) - Extract execute_ingest helper New tests (6 total): - LearnParams validation tests Total tests: 694 (was 688)
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/// Learn Handler Helpers
|
||||
///
|
||||
/// Extracted to reduce learn_handler complexity.
|
||||
|
||||
use actix_web::HttpResponse;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
// ============================================================================
|
||||
// Learn Parameters
|
||||
// ============================================================================
|
||||
|
||||
/// Validated learn request parameters
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LearnParams {
|
||||
pub project: String,
|
||||
pub text: String,
|
||||
pub question: String,
|
||||
pub memory_budget: u32,
|
||||
pub chunk_size: usize,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
impl LearnParams {
|
||||
/// Parse and validate learn request body
|
||||
pub fn from_body(body: &Value) -> Result<Self, LearnParamsError> {
|
||||
let project = body["project"]
|
||||
.as_str()
|
||||
.unwrap_or("knowledge")
|
||||
.to_string();
|
||||
|
||||
let text = body["text"]
|
||||
.as_str()
|
||||
.filter(|t| !t.trim().is_empty())
|
||||
.ok_or(LearnParamsError::MissingText)?
|
||||
.to_string();
|
||||
|
||||
let question = body["query"]
|
||||
.as_str()
|
||||
.unwrap_or("What are the key facts, patterns, and practices in this knowledge?")
|
||||
.to_string();
|
||||
|
||||
let memory_budget = body["memory_budget"]
|
||||
.as_u64()
|
||||
.unwrap_or(4096)
|
||||
.clamp(256, 32768) as u32;
|
||||
|
||||
let chunk_size = body["chunk_size"]
|
||||
.as_u64()
|
||||
.unwrap_or(2000)
|
||||
.clamp(500, 10000) as usize;
|
||||
|
||||
let model = body["model"]
|
||||
.as_str()
|
||||
.unwrap_or("qwen2.5:3b-instruct")
|
||||
.to_string();
|
||||
|
||||
Ok(Self { project, text, question, memory_budget, chunk_size, model })
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn parameter validation errors
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum LearnParamsError {
|
||||
MissingText,
|
||||
}
|
||||
|
||||
impl LearnParamsError {
|
||||
pub fn to_response(&self) -> HttpResponse {
|
||||
let reason = match self {
|
||||
Self::MissingText => "missing required field: text",
|
||||
};
|
||||
HttpResponse::BadRequest().json(json!({
|
||||
"error": "bad_request",
|
||||
"reason": reason
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Learn Response Builder
|
||||
// ============================================================================
|
||||
|
||||
/// Build learn response JSON
|
||||
pub fn build_learn_response(
|
||||
project: &str,
|
||||
model: &str,
|
||||
chunks_seen: u32,
|
||||
chunks_used: u32,
|
||||
memory: &str,
|
||||
stored: bool,
|
||||
) -> HttpResponse {
|
||||
HttpResponse::Ok().json(json!({
|
||||
"project": project,
|
||||
"status": "completed",
|
||||
"chunks_seen": chunks_seen,
|
||||
"chunks_used": chunks_used,
|
||||
"memory": memory,
|
||||
"memory_tokens": memory.len() / 4,
|
||||
"stored": stored,
|
||||
"model": model,
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_learn_params_valid() {
|
||||
let body = json!({
|
||||
"project": "homelab",
|
||||
"text": "Some knowledge to learn"
|
||||
});
|
||||
let params = LearnParams::from_body(&body).unwrap();
|
||||
|
||||
assert_eq!(params.project, "homelab");
|
||||
assert_eq!(params.text, "Some knowledge to learn");
|
||||
assert_eq!(params.memory_budget, 4096);
|
||||
assert_eq!(params.chunk_size, 2000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_learn_params_defaults() {
|
||||
let body = json!({"text": "content"});
|
||||
let params = LearnParams::from_body(&body).unwrap();
|
||||
|
||||
assert_eq!(params.project, "knowledge");
|
||||
assert_eq!(params.model, "qwen2.5:3b-instruct");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_learn_params_custom_values() {
|
||||
let body = json!({
|
||||
"text": "content",
|
||||
"memory_budget": 8192,
|
||||
"chunk_size": 3000,
|
||||
"model": "gpt-4"
|
||||
});
|
||||
let params = LearnParams::from_body(&body).unwrap();
|
||||
|
||||
assert_eq!(params.memory_budget, 8192);
|
||||
assert_eq!(params.chunk_size, 3000);
|
||||
assert_eq!(params.model, "gpt-4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_learn_params_clamped_budget() {
|
||||
let body = json!({"text": "x", "memory_budget": 999999});
|
||||
let params = LearnParams::from_body(&body).unwrap();
|
||||
assert_eq!(params.memory_budget, 32768);
|
||||
|
||||
let body = json!({"text": "x", "memory_budget": 10});
|
||||
let params = LearnParams::from_body(&body).unwrap();
|
||||
assert_eq!(params.memory_budget, 256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_learn_params_missing_text() {
|
||||
let body = json!({"project": "test"});
|
||||
assert_eq!(LearnParams::from_body(&body).unwrap_err(), LearnParamsError::MissingText);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_learn_params_empty_text() {
|
||||
let body = json!({"text": " "});
|
||||
assert_eq!(LearnParams::from_body(&body).unwrap_err(), LearnParamsError::MissingText);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
pub mod query;
|
||||
pub mod ingest;
|
||||
pub mod learn;
|
||||
|
||||
pub use query::*;
|
||||
pub use ingest::*;
|
||||
pub use learn::*;
|
||||
|
||||
Reference in New Issue
Block a user