use serde_json::{Value, json};
use crate::types::{SearchModifier, SearchParameter};
pub fn build_clause(param: &SearchParameter, value: &str) -> Option<Value> {
let name = ¶m.name;
match param.modifier {
Some(SearchModifier::Not) => {
let inner = build_token_condition(name, value, None)?;
return Some(json!({
"bool": {
"must_not": [inner]
}
}));
}
Some(SearchModifier::Text) => {
return build_text_clause(name, value);
}
Some(SearchModifier::TextAdvanced) => {
return build_text_advanced_clause(name, value);
}
Some(SearchModifier::OfType) => {
return build_of_type_clause(name, value);
}
_ => {}
}
build_token_condition(name, value, param.modifier.as_ref())
}
fn build_token_condition(
name: &str,
value: &str,
_modifier: Option<&SearchModifier>,
) -> Option<Value> {
let mut must_conditions = vec![json!({ "term": { "search_params.token.name": name } })];
if let Some((system, code)) = value.split_once('|') {
if system.is_empty() && !code.is_empty() {
must_conditions.push(json!({ "term": { "search_params.token.code": code } }));
must_conditions.push(json!({
"bool": {
"must_not": [
{ "exists": { "field": "search_params.token.system" } }
]
}
}));
} else if !system.is_empty() && code.is_empty() {
must_conditions.push(json!({ "term": { "search_params.token.system": system } }));
} else {
must_conditions.push(json!({ "term": { "search_params.token.system": system } }));
must_conditions.push(json!({ "term": { "search_params.token.code": code } }));
}
} else {
must_conditions.push(json!({ "term": { "search_params.token.code": value } }));
}
Some(json!({
"nested": {
"path": "search_params.token",
"query": {
"bool": {
"must": must_conditions
}
}
}
}))
}
fn build_text_clause(name: &str, value: &str) -> Option<Value> {
Some(json!({
"nested": {
"path": "search_params.token",
"query": {
"bool": {
"must": [
{ "term": { "search_params.token.name": name } },
{
"match": {
"search_params.token.display": {
"query": value,
"operator": "and"
}
}
}
]
}
}
}
}))
}
fn build_text_advanced_clause(name: &str, value: &str) -> Option<Value> {
Some(json!({
"nested": {
"path": "search_params.token",
"query": {
"bool": {
"must": [
{ "term": { "search_params.token.name": name } },
{
"query_string": {
"default_field": "search_params.token.display",
"query": value
}
}
]
}
}
}
}))
}
fn build_of_type_clause(name: &str, value: &str) -> Option<Value> {
let parts: Vec<&str> = value.splitn(3, '|').collect();
if parts.len() < 3 {
return None;
}
let type_system = parts[0];
let type_code = parts[1];
let identifier_value = parts[2];
let mut must_conditions = vec![
json!({ "term": { "search_params.token.name": name } }),
json!({ "term": { "search_params.token.code": identifier_value } }),
];
if !type_system.is_empty() {
must_conditions
.push(json!({ "term": { "search_params.token.identifier_type_system": type_system } }));
}
if !type_code.is_empty() {
must_conditions
.push(json!({ "term": { "search_params.token.identifier_type_code": type_code } }));
}
Some(json!({
"nested": {
"path": "search_params.token",
"query": {
"bool": {
"must": must_conditions
}
}
}
}))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{SearchParamType, SearchValue};
fn make_param(name: &str, modifier: Option<SearchModifier>) -> SearchParameter {
SearchParameter {
name: name.to_string(),
param_type: SearchParamType::Token,
modifier,
values: vec![SearchValue::eq("test")],
chain: vec![],
components: vec![],
}
}
#[test]
fn test_code_only() {
let param = make_param("code", None);
let clause = build_clause(¶m, "8867-4").unwrap();
let s = serde_json::to_string(&clause).unwrap();
assert!(s.contains("8867-4"));
assert!(s.contains("search_params.token.code"));
}
#[test]
fn test_system_code() {
let param = make_param("code", None);
let clause = build_clause(¶m, "http://loinc.org|8867-4").unwrap();
let s = serde_json::to_string(&clause).unwrap();
assert!(s.contains("http://loinc.org"));
assert!(s.contains("8867-4"));
}
#[test]
fn test_not_modifier() {
let param = make_param("gender", Some(SearchModifier::Not));
let clause = build_clause(¶m, "male").unwrap();
let s = serde_json::to_string(&clause).unwrap();
assert!(s.contains("must_not"));
}
#[test]
fn test_text_modifier() {
let param = make_param("code", Some(SearchModifier::Text));
let clause = build_clause(¶m, "headache").unwrap();
let s = serde_json::to_string(&clause).unwrap();
assert!(s.contains("display"));
assert!(s.contains("headache"));
}
}