hoist_core/resources/
indexer.rs1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::traits::{Resource, ResourceKind};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct Indexer {
12 pub name: String,
13 pub data_source_name: String,
14 pub target_index_name: String,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub skillset_name: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub description: Option<String>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub schedule: Option<IndexerSchedule>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub parameters: Option<IndexerParameters>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub field_mappings: Option<Vec<FieldMapping>>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub output_field_mappings: Option<Vec<FieldMapping>>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub disabled: Option<bool>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub cache: Option<Value>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub encryption_key: Option<Value>,
33 #[serde(flatten)]
34 pub extra: std::collections::HashMap<String, Value>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct IndexerSchedule {
40 pub interval: String,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub start_time: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct IndexerParameters {
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub batch_size: Option<i32>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub max_failed_items: Option<i32>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub max_failed_items_per_batch: Option<i32>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub configuration: Option<Value>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct FieldMapping {
61 pub source_field_name: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub target_field_name: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub mapping_function: Option<MappingFunction>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct MappingFunction {
71 pub name: String,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub parameters: Option<Value>,
74}
75
76impl Resource for Indexer {
77 fn kind() -> ResourceKind {
78 ResourceKind::Indexer
79 }
80
81 fn name(&self) -> &str {
82 &self.name
83 }
84
85 fn read_only_fields() -> &'static [&'static str] {
86 &["startTime"]
89 }
90
91 fn dependencies(&self) -> Vec<(ResourceKind, String)> {
92 let mut deps = vec![
93 (ResourceKind::DataSource, self.data_source_name.clone()),
94 (ResourceKind::Index, self.target_index_name.clone()),
95 ];
96 if let Some(ref skillset) = self.skillset_name {
97 deps.push((ResourceKind::Skillset, skillset.clone()));
98 }
99 deps
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 fn make_indexer(skillset: Option<&str>) -> Indexer {
108 Indexer {
109 name: "my-indexer".to_string(),
110 data_source_name: "my-ds".to_string(),
111 target_index_name: "my-index".to_string(),
112 skillset_name: skillset.map(String::from),
113 description: None,
114 schedule: None,
115 parameters: None,
116 field_mappings: None,
117 output_field_mappings: None,
118 disabled: None,
119 cache: None,
120 encryption_key: None,
121 extra: Default::default(),
122 }
123 }
124
125 #[test]
126 fn test_indexer_kind() {
127 assert_eq!(Indexer::kind(), ResourceKind::Indexer);
128 }
129
130 #[test]
131 fn test_indexer_dependencies_without_skillset() {
132 let indexer = make_indexer(None);
133 let deps = indexer.dependencies();
134 assert_eq!(deps.len(), 2);
135 assert!(deps.contains(&(ResourceKind::DataSource, "my-ds".to_string())));
136 assert!(deps.contains(&(ResourceKind::Index, "my-index".to_string())));
137 }
138
139 #[test]
140 fn test_indexer_dependencies_with_skillset() {
141 let indexer = make_indexer(Some("my-skillset"));
142 let deps = indexer.dependencies();
143 assert_eq!(deps.len(), 3);
144 assert!(deps.contains(&(ResourceKind::Skillset, "my-skillset".to_string())));
145 }
146
147 #[test]
148 fn test_indexer_deserialize() {
149 let json = r#"{
150 "name": "test-indexer",
151 "dataSourceName": "ds",
152 "targetIndexName": "idx"
153 }"#;
154 let indexer: Indexer = serde_json::from_str(json).unwrap();
155 assert_eq!(indexer.name, "test-indexer");
156 assert_eq!(indexer.data_source_name, "ds");
157 assert_eq!(indexer.target_index_name, "idx");
158 }
159
160 #[test]
161 fn test_indexer_read_only_fields_includes_start_time() {
162 let fields = Indexer::read_only_fields();
163 assert!(
164 fields.contains(&"startTime"),
165 "startTime is server-managed and must be stripped before push"
166 );
167 }
168}