use serde::{Deserialize, Serialize};
use crate::types::entity::Entity;
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "api", derive(utoipa::ToSchema))]
pub struct EnrichOptions {
#[serde(default)]
pub keywords: bool,
#[serde(default)]
pub entities: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub labels: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "api", derive(utoipa::ToSchema))]
pub struct EnrichResult {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub keywords: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub entities: Vec<Entity>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub labels: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "api", derive(utoipa::ToSchema))]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum EnrichStatus {
Pending,
Completed {
result: EnrichResult,
},
Failed {
error: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::entity::{Entity, EntityCategory};
#[test]
fn enrich_options_defaults_are_all_off() {
let opts = EnrichOptions::default();
assert!(!opts.keywords, "keywords should default to false");
assert!(!opts.entities, "entities should default to false");
assert!(opts.labels.is_empty(), "labels should default to empty");
}
#[test]
fn enrich_options_roundtrip_all_fields() {
let opts = EnrichOptions {
keywords: true,
entities: true,
labels: vec!["tag-a".to_string(), "tag-b".to_string()],
};
let json = serde_json::to_string(&opts).expect("serialize");
let decoded: EnrichOptions = serde_json::from_str(&json).expect("deserialize");
assert_eq!(opts, decoded);
}
#[test]
fn enrich_options_labels_omitted_when_empty() {
let opts = EnrichOptions {
keywords: true,
entities: false,
labels: vec![],
};
let json = serde_json::to_value(&opts).expect("serialize");
assert!(json.get("labels").is_none(), "empty labels should be omitted from JSON");
}
#[test]
fn enrich_result_defaults_are_empty() {
let r = EnrichResult::default();
assert!(r.keywords.is_empty());
assert!(r.entities.is_empty());
assert!(r.labels.is_empty());
}
#[test]
fn enrich_result_roundtrip_with_entities() {
let result = EnrichResult {
keywords: vec!["rust".to_string(), "serde".to_string()],
entities: vec![Entity {
category: EntityCategory::Person,
text: "Alice".to_string(),
start: 0,
end: 5,
confidence: Some(0.95),
}],
labels: vec!["doc-type:invoice".to_string()],
};
let json = serde_json::to_string(&result).expect("serialize");
let decoded: EnrichResult = serde_json::from_str(&json).expect("deserialize");
assert_eq!(result, decoded);
}
#[test]
fn enrich_result_empty_vecs_omitted_from_json() {
let result = EnrichResult::default();
let json = serde_json::to_value(&result).expect("serialize");
assert!(json.get("keywords").is_none());
assert!(json.get("entities").is_none());
assert!(json.get("labels").is_none());
}
#[test]
fn enrich_status_pending_serialises_tag() {
let json = serde_json::to_value(EnrichStatus::Pending).expect("serialize");
assert_eq!(json["status"], "pending");
assert_eq!(
json.as_object().unwrap().len(),
1,
"Pending should only have the status tag"
);
}
#[test]
fn enrich_status_completed_roundtrip() {
let status = EnrichStatus::Completed {
result: EnrichResult {
keywords: vec!["hello".to_string()],
entities: vec![],
labels: vec![],
},
};
let json = serde_json::to_string(&status).expect("serialize");
let decoded: EnrichStatus = serde_json::from_str(&json).expect("deserialize");
assert_eq!(status, decoded);
}
#[test]
fn enrich_status_completed_tag_value() {
let status = EnrichStatus::Completed {
result: EnrichResult::default(),
};
let json = serde_json::to_value(&status).expect("serialize");
assert_eq!(json["status"], "completed");
}
#[test]
fn enrich_status_failed_roundtrip() {
let status = EnrichStatus::Failed {
error: "text too large".to_string(),
};
let json = serde_json::to_string(&status).expect("serialize");
let decoded: EnrichStatus = serde_json::from_str(&json).expect("deserialize");
assert_eq!(status, decoded);
}
#[test]
fn enrich_status_failed_tag_and_error_field() {
let status = EnrichStatus::Failed {
error: "timeout".to_string(),
};
let json = serde_json::to_value(&status).expect("serialize");
assert_eq!(json["status"], "failed");
assert_eq!(json["error"], "timeout");
}
#[test]
fn enrich_status_pending_deserialises_from_json() {
let json = r#"{"status":"pending"}"#;
let status: EnrichStatus = serde_json::from_str(json).expect("deserialize");
assert_eq!(status, EnrichStatus::Pending);
}
}