use serde::{Deserialize, Serialize};
use crate::{
Engine, EngineError, FtsPropertyPathMode, FtsPropertyPathSpec, FtsPropertySchemaRecord,
};
#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PyPropertyPathMode {
Scalar,
Recursive,
}
impl From<PyPropertyPathMode> for FtsPropertyPathMode {
fn from(value: PyPropertyPathMode) -> Self {
match value {
PyPropertyPathMode::Scalar => Self::Scalar,
PyPropertyPathMode::Recursive => Self::Recursive,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct PyPropertyPathSpec {
pub path: String,
pub mode: PyPropertyPathMode,
}
impl From<PyPropertyPathSpec> for FtsPropertyPathSpec {
fn from(value: PyPropertyPathSpec) -> Self {
Self {
path: value.path,
mode: value.mode.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct PyRegisterFtsPropertySchemaRequest {
pub kind: String,
pub entries: Vec<PyPropertyPathSpec>,
#[serde(default = "default_separator")]
pub separator: String,
#[serde(default)]
pub exclude_paths: Vec<String>,
}
fn default_separator() -> String {
" ".to_owned()
}
#[derive(Debug)]
pub enum AdminFfiError {
Parse(serde_json::Error),
Engine(EngineError),
Serialize(serde_json::Error),
}
impl std::fmt::Display for AdminFfiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Parse(e) => write!(f, "admin request JSON parse error: {e}"),
Self::Engine(e) => write!(f, "admin operation error: {e}"),
Self::Serialize(e) => write!(f, "admin response serialize error: {e}"),
}
}
}
impl std::error::Error for AdminFfiError {}
pub fn register_fts_property_schema_with_entries_json(
engine: &Engine,
request_json: &str,
) -> Result<String, AdminFfiError> {
let request: PyRegisterFtsPropertySchemaRequest =
serde_json::from_str(request_json).map_err(AdminFfiError::Parse)?;
let entries: Vec<FtsPropertyPathSpec> = request.entries.into_iter().map(Into::into).collect();
let record: FtsPropertySchemaRecord = engine
.register_fts_property_schema_with_entries(
&request.kind,
&entries,
Some(request.separator.as_str()),
&request.exclude_paths,
)
.map_err(AdminFfiError::Engine)?;
serde_json::to_string(&record).map_err(AdminFfiError::Serialize)
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::{PyPropertyPathMode, PyPropertyPathSpec, PyRegisterFtsPropertySchemaRequest};
#[test]
fn property_path_mode_snake_case_wire_form() {
let json = serde_json::to_string(&PyPropertyPathMode::Scalar).expect("serialize");
assert_eq!(json, "\"scalar\"");
let json = serde_json::to_string(&PyPropertyPathMode::Recursive).expect("serialize");
assert_eq!(json, "\"recursive\"");
}
#[test]
fn property_path_spec_roundtrip() {
let spec = PyPropertyPathSpec {
path: "$.payload".to_owned(),
mode: PyPropertyPathMode::Recursive,
};
let json = serde_json::to_string(&spec).expect("serialize");
let parsed: PyPropertyPathSpec = serde_json::from_str(&json).expect("deserialize");
assert_eq!(spec, parsed);
}
#[test]
fn register_request_defaults_separator_and_exclude_paths() {
let request: PyRegisterFtsPropertySchemaRequest =
serde_json::from_str(r#"{"kind":"K","entries":[{"path":"$.title","mode":"scalar"}]}"#)
.expect("parse");
assert_eq!(request.kind, "K");
assert_eq!(request.separator, " ");
assert!(request.exclude_paths.is_empty());
assert_eq!(request.entries.len(), 1);
}
#[test]
fn register_request_roundtrip_recursive_entry() {
let request = PyRegisterFtsPropertySchemaRequest {
kind: "KnowledgeItem".to_owned(),
entries: vec![
PyPropertyPathSpec {
path: "$.title".to_owned(),
mode: PyPropertyPathMode::Scalar,
},
PyPropertyPathSpec {
path: "$.payload".to_owned(),
mode: PyPropertyPathMode::Recursive,
},
],
separator: " ".to_owned(),
exclude_paths: vec!["$.payload.ignored".to_owned()],
};
let json = serde_json::to_string(&request).expect("serialize");
let parsed: PyRegisterFtsPropertySchemaRequest =
serde_json::from_str(&json).expect("deserialize");
assert_eq!(request, parsed);
}
}