1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6use crate::service::ServiceDomain;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "kebab-case")]
11pub enum ResourceKind {
12 Index,
14 Indexer,
15 DataSource,
16 Skillset,
17 SynonymMap,
18 Alias,
19 KnowledgeBase,
20 KnowledgeSource,
21 Agent,
23}
24
25impl ResourceKind {
26 pub fn domain(&self) -> ServiceDomain {
28 match self {
29 ResourceKind::Agent => ServiceDomain::Foundry,
30 _ => ServiceDomain::Search,
31 }
32 }
33
34 pub fn api_path(&self) -> &'static str {
36 match self {
37 ResourceKind::Index => "indexes",
38 ResourceKind::Indexer => "indexers",
39 ResourceKind::DataSource => "datasources",
40 ResourceKind::Skillset => "skillsets",
41 ResourceKind::SynonymMap => "synonymmaps",
42 ResourceKind::Alias => "aliases",
43 ResourceKind::KnowledgeBase => "knowledgebases",
44 ResourceKind::KnowledgeSource => "knowledgesources",
45 ResourceKind::Agent => "assistants",
46 }
47 }
48
49 pub fn directory_name(&self) -> &'static str {
51 match self {
52 ResourceKind::Index => "search-management/indexes",
53 ResourceKind::Indexer => "search-management/indexers",
54 ResourceKind::DataSource => "search-management/data-sources",
55 ResourceKind::Skillset => "search-management/skillsets",
56 ResourceKind::SynonymMap => "search-management/synonym-maps",
57 ResourceKind::Alias => "search-management/aliases",
58 ResourceKind::KnowledgeBase => "agentic-retrieval/knowledge-bases",
59 ResourceKind::KnowledgeSource => "agentic-retrieval/knowledge-sources",
60 ResourceKind::Agent => "agents",
61 }
62 }
63
64 pub fn is_preview(&self) -> bool {
66 matches!(
67 self,
68 ResourceKind::Alias | ResourceKind::KnowledgeBase | ResourceKind::KnowledgeSource
69 )
70 }
71
72 pub fn display_name(&self) -> &'static str {
74 match self {
75 ResourceKind::Index => "Index",
76 ResourceKind::Indexer => "Indexer",
77 ResourceKind::DataSource => "Data Source",
78 ResourceKind::Skillset => "Skillset",
79 ResourceKind::SynonymMap => "Synonym Map",
80 ResourceKind::Alias => "Alias",
81 ResourceKind::KnowledgeBase => "Knowledge Base",
82 ResourceKind::KnowledgeSource => "Knowledge Source",
83 ResourceKind::Agent => "Agent",
84 }
85 }
86
87 pub fn all() -> &'static [ResourceKind] {
89 &[
90 ResourceKind::Index,
91 ResourceKind::Indexer,
92 ResourceKind::DataSource,
93 ResourceKind::Skillset,
94 ResourceKind::SynonymMap,
95 ResourceKind::Alias,
96 ResourceKind::KnowledgeBase,
97 ResourceKind::KnowledgeSource,
98 ResourceKind::Agent,
99 ]
100 }
101
102 pub fn stable() -> &'static [ResourceKind] {
104 &[
105 ResourceKind::Index,
106 ResourceKind::Indexer,
107 ResourceKind::DataSource,
108 ResourceKind::Skillset,
109 ResourceKind::SynonymMap,
110 ]
111 }
112
113 pub fn search_kinds() -> Vec<ResourceKind> {
115 ResourceKind::all()
116 .iter()
117 .filter(|k| k.domain() == ServiceDomain::Search)
118 .copied()
119 .collect()
120 }
121
122 pub fn foundry_kinds() -> Vec<ResourceKind> {
124 ResourceKind::all()
125 .iter()
126 .filter(|k| k.domain() == ServiceDomain::Foundry)
127 .copied()
128 .collect()
129 }
130}
131
132impl fmt::Display for ResourceKind {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 write!(f, "{}", self.display_name())
135 }
136}
137
138pub trait Resource: Serialize + for<'de> Deserialize<'de> + Clone {
140 fn kind() -> ResourceKind;
142
143 fn name(&self) -> &str;
145
146 fn volatile_fields() -> &'static [&'static str] {
149 &["@odata.etag", "@odata.context"]
150 }
151
152 fn read_only_fields() -> &'static [&'static str] {
156 &[]
157 }
158
159 fn identity_key() -> &'static str {
161 "name"
162 }
163
164 fn dependencies(&self) -> Vec<(ResourceKind, String)> {
166 Vec::new()
167 }
168
169 fn immutable_fields() -> &'static [&'static str] {
171 &[]
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_all_returns_nine_kinds() {
181 assert_eq!(ResourceKind::all().len(), 9);
182 }
183
184 #[test]
185 fn test_stable_excludes_preview() {
186 let stable = ResourceKind::stable();
187 assert_eq!(stable.len(), 5);
188 for kind in stable {
189 assert!(!kind.is_preview());
190 }
191 }
192
193 #[test]
194 fn test_preview_kinds() {
195 assert!(ResourceKind::KnowledgeBase.is_preview());
196 assert!(ResourceKind::KnowledgeSource.is_preview());
197 assert!(!ResourceKind::Index.is_preview());
198 assert!(!ResourceKind::Indexer.is_preview());
199 assert!(!ResourceKind::DataSource.is_preview());
200 assert!(!ResourceKind::Skillset.is_preview());
201 assert!(!ResourceKind::SynonymMap.is_preview());
202 assert!(ResourceKind::Alias.is_preview());
203 assert!(!ResourceKind::Agent.is_preview());
204 }
205
206 #[test]
207 fn test_api_paths() {
208 assert_eq!(ResourceKind::Index.api_path(), "indexes");
209 assert_eq!(ResourceKind::Indexer.api_path(), "indexers");
210 assert_eq!(ResourceKind::DataSource.api_path(), "datasources");
211 assert_eq!(ResourceKind::Skillset.api_path(), "skillsets");
212 assert_eq!(ResourceKind::SynonymMap.api_path(), "synonymmaps");
213 assert_eq!(ResourceKind::Alias.api_path(), "aliases");
214 assert_eq!(ResourceKind::KnowledgeBase.api_path(), "knowledgebases");
215 assert_eq!(ResourceKind::KnowledgeSource.api_path(), "knowledgesources");
216 assert_eq!(ResourceKind::Agent.api_path(), "assistants");
217 }
218
219 #[test]
220 fn test_directory_names() {
221 assert_eq!(
222 ResourceKind::Index.directory_name(),
223 "search-management/indexes"
224 );
225 assert_eq!(
226 ResourceKind::DataSource.directory_name(),
227 "search-management/data-sources"
228 );
229 assert_eq!(
230 ResourceKind::SynonymMap.directory_name(),
231 "search-management/synonym-maps"
232 );
233 assert_eq!(
234 ResourceKind::Alias.directory_name(),
235 "search-management/aliases"
236 );
237 assert_eq!(
238 ResourceKind::KnowledgeBase.directory_name(),
239 "agentic-retrieval/knowledge-bases"
240 );
241 assert_eq!(
242 ResourceKind::KnowledgeSource.directory_name(),
243 "agentic-retrieval/knowledge-sources"
244 );
245 assert_eq!(ResourceKind::Agent.directory_name(), "agents");
246 }
247
248 #[test]
249 fn test_stable_kinds_under_search_management() {
250 for kind in ResourceKind::stable() {
251 assert!(
252 kind.directory_name().starts_with("search-management/"),
253 "{:?} should be under search-management/",
254 kind
255 );
256 }
257 }
258
259 #[test]
260 fn test_agentic_retrieval_kinds_are_preview() {
261 for kind in ResourceKind::all() {
262 if kind.directory_name().starts_with("agentic-retrieval/") {
263 assert!(
264 kind.is_preview(),
265 "{:?} under agentic-retrieval/ should be preview",
266 kind
267 );
268 }
269 }
270 }
271
272 #[test]
273 fn test_display_names() {
274 assert_eq!(ResourceKind::Index.display_name(), "Index");
275 assert_eq!(ResourceKind::DataSource.display_name(), "Data Source");
276 assert_eq!(ResourceKind::KnowledgeBase.display_name(), "Knowledge Base");
277 assert_eq!(ResourceKind::Alias.display_name(), "Alias");
278 assert_eq!(ResourceKind::Agent.display_name(), "Agent");
279 }
280
281 #[test]
282 fn test_display_trait() {
283 assert_eq!(format!("{}", ResourceKind::Index), "Index");
284 assert_eq!(format!("{}", ResourceKind::Skillset), "Skillset");
285 assert_eq!(format!("{}", ResourceKind::Alias), "Alias");
286 assert_eq!(format!("{}", ResourceKind::Agent), "Agent");
287 }
288
289 #[test]
290 fn test_serde_roundtrip() {
291 let kind = ResourceKind::DataSource;
292 let json = serde_json::to_string(&kind).unwrap();
293 assert_eq!(json, "\"data-source\"");
294 let back: ResourceKind = serde_json::from_str(&json).unwrap();
295 assert_eq!(back, kind);
296 }
297
298 #[test]
299 fn test_serde_roundtrip_alias() {
300 let kind = ResourceKind::Alias;
301 let json = serde_json::to_string(&kind).unwrap();
302 assert_eq!(json, "\"alias\"");
303 let back: ResourceKind = serde_json::from_str(&json).unwrap();
304 assert_eq!(back, kind);
305 }
306
307 #[test]
308 fn test_serde_roundtrip_agent() {
309 let kind = ResourceKind::Agent;
310 let json = serde_json::to_string(&kind).unwrap();
311 assert_eq!(json, "\"agent\"");
312 let back: ResourceKind = serde_json::from_str(&json).unwrap();
313 assert_eq!(back, kind);
314 }
315
316 #[test]
317 fn test_all_kinds_in_stable_or_preview() {
318 for kind in ResourceKind::all() {
319 if kind.domain() == ServiceDomain::Search {
320 if kind.is_preview() {
321 assert!(!ResourceKind::stable().contains(kind));
322 } else {
323 assert!(ResourceKind::stable().contains(kind));
324 }
325 }
326 }
327 }
328
329 #[test]
330 fn test_domain_search_kinds() {
331 let search = ResourceKind::search_kinds();
332 assert_eq!(search.len(), 8);
333 for kind in &search {
334 assert_eq!(kind.domain(), ServiceDomain::Search);
335 }
336 }
337
338 #[test]
339 fn test_domain_foundry_kinds() {
340 let foundry = ResourceKind::foundry_kinds();
341 assert_eq!(foundry.len(), 1);
342 assert_eq!(foundry[0], ResourceKind::Agent);
343 for kind in &foundry {
344 assert_eq!(kind.domain(), ServiceDomain::Foundry);
345 }
346 }
347
348 #[test]
349 fn test_agent_domain_is_foundry() {
350 assert_eq!(ResourceKind::Agent.domain(), ServiceDomain::Foundry);
351 }
352
353 #[test]
354 fn test_search_resources_domain_is_search() {
355 for kind in ResourceKind::stable() {
356 assert_eq!(kind.domain(), ServiceDomain::Search);
357 }
358 assert_eq!(ResourceKind::Alias.domain(), ServiceDomain::Search);
359 assert_eq!(ResourceKind::KnowledgeBase.domain(), ServiceDomain::Search);
360 assert_eq!(
361 ResourceKind::KnowledgeSource.domain(),
362 ServiceDomain::Search
363 );
364 }
365
366 #[test]
367 fn test_search_plus_foundry_equals_all() {
368 let mut combined = ResourceKind::search_kinds();
369 combined.extend(ResourceKind::foundry_kinds());
370 assert_eq!(combined.len(), ResourceKind::all().len());
371 for kind in ResourceKind::all() {
372 assert!(combined.contains(kind));
373 }
374 }
375}