1use super::catalogue_search::retain_matching_predicates;
2use super::*;
3use ahri_tre_protocol::{ProtocolVersion, PublicUuid, refs::*};
4fn app_variable_search_request(
5 request: &ahri_tre_protocol::dictionary::SearchVariablesRequest,
6 protocol_version: &ProtocolVersion,
7) -> Result<SearchVariablesRequest, AppError> {
8 let after = request
9 .decoded_cursor(protocol_version)
10 .map_err(search_validation_error)?
11 .map(|cursor| {
12 Ok::<_, AppError>(ahri_tre_core::VariableSearchCursorAnchor {
13 name: cursor.name,
14 domain_id: ahri_tre_types::DomainId(protocol_int_ref_id(
15 "domain",
16 cursor.domain.id,
17 )?),
18 variable_id: ahri_tre_types::VariableId(protocol_int_ref_id(
19 "variable",
20 cursor.variable.id,
21 )?),
22 })
23 })
24 .transpose()?;
25 Ok(SearchVariablesRequest {
26 kind: match &request.predicates {
27 ahri_tre_protocol::search::PredicateSet::AllOf(_) => {
28 ahri_tre_core::PredicateSetKind::AllOf
29 }
30 ahri_tre_protocol::search::PredicateSet::AnyOf(_) => {
31 ahri_tre_core::PredicateSetKind::AnyOf
32 }
33 },
34 predicates: request
35 .predicates
36 .predicates()
37 .iter()
38 .cloned()
39 .map(app_variable_search_predicate)
40 .collect::<Result<Vec<_>, _>>()?,
41 limit: request.page.limit,
42 after,
43 })
44}
45
46fn app_variable_search_predicate(
47 predicate: ahri_tre_protocol::dictionary::VariablePredicate,
48) -> Result<ahri_tre_core::VariableSearchPredicate, AppError> {
49 match predicate {
50 ahri_tre_protocol::dictionary::VariablePredicate::Text(text) => {
51 Ok(ahri_tre_core::VariableSearchPredicate::Text {
52 field: match text.field {
53 ahri_tre_protocol::dictionary::VariableTextField::Name => {
54 ahri_tre_core::VariableSearchTextField::Name
55 }
56 ahri_tre_protocol::dictionary::VariableTextField::Description => {
57 ahri_tre_core::VariableSearchTextField::Description
58 }
59 ahri_tre_protocol::dictionary::VariableTextField::ValueType => {
60 ahri_tre_core::VariableSearchTextField::ValueType
61 }
62 ahri_tre_protocol::dictionary::VariableTextField::VocabularyName => {
63 ahri_tre_core::VariableSearchTextField::VocabularyName
64 }
65 ahri_tre_protocol::dictionary::VariableTextField::OntologyClass => {
66 ahri_tre_core::VariableSearchTextField::OntologyClass
67 }
68 },
69 mode: match text.mode {
70 ahri_tre_protocol::search::TextMode::Exact => {
71 ahri_tre_core::SearchTextMode::Exact
72 }
73 ahri_tre_protocol::search::TextMode::Prefix => {
74 ahri_tre_core::SearchTextMode::Prefix
75 }
76 ahri_tre_protocol::search::TextMode::Contains => {
77 ahri_tre_core::SearchTextMode::Contains
78 }
79 },
80 value: text.value,
81 })
82 }
83 ahri_tre_protocol::dictionary::VariablePredicate::Domain { domain } => {
84 Ok(ahri_tre_core::VariableSearchPredicate::Domain(
85 app_variable_search_domain_selector(domain)?,
86 ))
87 }
88 ahri_tre_protocol::dictionary::VariablePredicate::ValueType { value_type } => Ok(
89 ahri_tre_core::VariableSearchPredicate::ValueType(value_type),
90 ),
91 ahri_tre_protocol::dictionary::VariablePredicate::Vocabulary { vocabulary } => {
92 Ok(ahri_tre_core::VariableSearchPredicate::Vocabulary(
93 ahri_tre_core::VariableSearchVocabularySelector::Id(ahri_tre_types::VocabularyId(
94 protocol_int_ref_id("vocabulary", vocabulary.id)?,
95 )),
96 ))
97 }
98 ahri_tre_protocol::dictionary::VariablePredicate::KeyRole { key_role } => {
99 Ok(ahri_tre_core::VariableSearchPredicate::KeyRole(
100 app_variable_search_key_role(&key_role)?,
101 ))
102 }
103 ahri_tre_protocol::dictionary::VariablePredicate::Tag(tag) => Ok(
104 ahri_tre_core::VariableSearchPredicate::Tag(match tag.selector {
105 ahri_tre_protocol::refs::TagSelector::Id { tag } => {
106 ahri_tre_core::VariableSearchTagSelector::Id(ahri_tre_types::TagId(
107 protocol_int_ref_id("tag", tag.id)?,
108 ))
109 }
110 ahri_tre_protocol::refs::TagSelector::Label { label, .. } => {
111 ahri_tre_core::VariableSearchTagSelector::Name(
112 ahri_tre_core::normalize_tags([label.as_str().to_string()])
113 .into_iter()
114 .next()
115 .unwrap_or_else(|| label.as_str().to_string()),
116 )
117 }
118 }),
119 ),
120 }
121}
122
123fn app_variable_search_domain_selector(
124 selector: ahri_tre_protocol::domain::DomainSelector,
125) -> Result<ahri_tre_core::VariableSearchDomainSelector, AppError> {
126 match selector {
127 ahri_tre_protocol::domain::DomainSelector::Id { domain } => {
128 Ok(ahri_tre_core::VariableSearchDomainSelector::Id(
129 ahri_tre_types::DomainId(protocol_int_ref_id("domain", domain.id)?),
130 ))
131 }
132 ahri_tre_protocol::domain::DomainSelector::Name { name } => Ok(
133 ahri_tre_core::VariableSearchDomainSelector::Name(name.into()),
134 ),
135 }
136}
137
138fn app_variable_search_key_role(value: &str) -> Result<ahri_tre_types::KeyRole, AppError> {
139 match value.trim().to_ascii_lowercase().as_str() {
140 "none" => Ok(ahri_tre_types::KeyRole::None),
141 "record" => Ok(ahri_tre_types::KeyRole::Record),
142 "external" => Ok(ahri_tre_types::KeyRole::External),
143 _ => Err(protocol_validation_error(
144 "variable key role is invalid",
145 "key_role must be one of none, record, or external",
146 )),
147 }
148}
149
150fn protocol_variable_search_cursor(
151 datastore_id: PublicUuid,
152 request: &ahri_tre_protocol::dictionary::SearchVariablesRequest,
153 protocol_version: &ProtocolVersion,
154 cursor: &ahri_tre_core::VariableSearchCursorAnchor,
155) -> Result<String, AppError> {
156 request
157 .encode_cursor(
158 protocol_version,
159 ahri_tre_protocol::dictionary::VariableSearchCursorAnchor {
160 domain: protocol_domain_ref(datastore_id, cursor.domain_id.0),
161 variable: VariableRef {
162 datastore_id,
163 kind: ahri_tre_protocol::refs::ObjectKind::Variable,
164 id: protocol_int_ref("variable", cursor.variable_id.0),
165 },
166 name: cursor.name.clone(),
167 },
168 )
169 .map_err(search_validation_error)
170}
171
172fn app_entity_search_request(
173 request: &ahri_tre_protocol::model::SearchEntitiesRequest,
174 protocol_version: &ProtocolVersion,
175) -> Result<SearchEntitiesRequest, AppError> {
176 let after = request
177 .decoded_cursor(protocol_version)
178 .map_err(search_validation_error)?
179 .map(|cursor| {
180 Ok::<_, AppError>(ahri_tre_core::EntitySearchCursorAnchor {
181 name: cursor.name,
182 domain_id: ahri_tre_types::DomainId(protocol_int_ref_id(
183 "domain",
184 cursor.domain.id,
185 )?),
186 entity_id: ahri_tre_types::EntityId(protocol_int_ref_id(
187 "entity",
188 cursor.entity.id,
189 )?),
190 })
191 })
192 .transpose()?;
193 Ok(SearchEntitiesRequest {
194 kind: match &request.predicates {
195 ahri_tre_protocol::search::PredicateSet::AllOf(_) => {
196 ahri_tre_core::PredicateSetKind::AllOf
197 }
198 ahri_tre_protocol::search::PredicateSet::AnyOf(_) => {
199 ahri_tre_core::PredicateSetKind::AnyOf
200 }
201 },
202 predicates: request
203 .predicates
204 .predicates()
205 .iter()
206 .cloned()
207 .map(app_entity_search_predicate)
208 .collect::<Result<Vec<_>, _>>()?,
209 limit: request.page.limit,
210 after,
211 })
212}
213
214fn app_entity_search_predicate(
215 predicate: ahri_tre_protocol::model::EntityPredicate,
216) -> Result<ahri_tre_core::EntitySearchPredicate, AppError> {
217 match predicate {
218 ahri_tre_protocol::model::EntityPredicate::Text(text) => {
219 Ok(ahri_tre_core::EntitySearchPredicate::Text {
220 field: match text.field {
221 ahri_tre_protocol::model::EntityTextField::Name => {
222 ahri_tre_core::EntitySearchTextField::Name
223 }
224 ahri_tre_protocol::model::EntityTextField::Description => {
225 ahri_tre_core::EntitySearchTextField::Description
226 }
227 ahri_tre_protocol::model::EntityTextField::OntologyClass => {
228 ahri_tre_core::EntitySearchTextField::OntologyClass
229 }
230 },
231 mode: match text.mode {
232 ahri_tre_protocol::search::TextMode::Exact => {
233 ahri_tre_core::SearchTextMode::Exact
234 }
235 ahri_tre_protocol::search::TextMode::Prefix => {
236 ahri_tre_core::SearchTextMode::Prefix
237 }
238 ahri_tre_protocol::search::TextMode::Contains => {
239 ahri_tre_core::SearchTextMode::Contains
240 }
241 },
242 value: text.value,
243 })
244 }
245 ahri_tre_protocol::model::EntityPredicate::Domain { domain } => {
246 Ok(ahri_tre_core::EntitySearchPredicate::Domain(
247 app_entity_search_domain_selector(domain)?,
248 ))
249 }
250 ahri_tre_protocol::model::EntityPredicate::Tag(tag) => Ok(
251 ahri_tre_core::EntitySearchPredicate::Tag(match tag.selector {
252 ahri_tre_protocol::refs::TagSelector::Id { tag } => {
253 ahri_tre_core::EntitySearchTagSelector::Id(ahri_tre_types::TagId(
254 protocol_int_ref_id("tag", tag.id)?,
255 ))
256 }
257 ahri_tre_protocol::refs::TagSelector::Label { label, .. } => {
258 ahri_tre_core::EntitySearchTagSelector::Name(
259 ahri_tre_core::normalize_tags([label.as_str().to_string()])
260 .into_iter()
261 .next()
262 .unwrap_or_else(|| label.as_str().to_string()),
263 )
264 }
265 }),
266 ),
267 }
268}
269
270fn app_entity_search_domain_selector(
271 selector: ahri_tre_protocol::domain::DomainSelector,
272) -> Result<ahri_tre_core::EntitySearchDomainSelector, AppError> {
273 match selector {
274 ahri_tre_protocol::domain::DomainSelector::Id { domain } => {
275 Ok(ahri_tre_core::EntitySearchDomainSelector::Id(
276 ahri_tre_types::DomainId(protocol_int_ref_id("domain", domain.id)?),
277 ))
278 }
279 ahri_tre_protocol::domain::DomainSelector::Name { name } => {
280 Ok(ahri_tre_core::EntitySearchDomainSelector::Name(name.into()))
281 }
282 }
283}
284
285fn protocol_entity_search_cursor(
286 datastore_id: PublicUuid,
287 request: &ahri_tre_protocol::model::SearchEntitiesRequest,
288 protocol_version: &ProtocolVersion,
289 cursor: &ahri_tre_core::EntitySearchCursorAnchor,
290) -> Result<String, AppError> {
291 request
292 .encode_cursor(
293 protocol_version,
294 ahri_tre_protocol::model::EntitySearchCursorAnchor {
295 domain: protocol_domain_ref(datastore_id, cursor.domain_id.0),
296 entity: protocol_entity_ref(datastore_id, cursor.entity_id.0),
297 name: cursor.name.clone(),
298 },
299 )
300 .map_err(search_validation_error)
301}
302
303fn app_relation_search_request(
304 request: &ahri_tre_protocol::model::SearchRelationsRequest,
305 protocol_version: &ProtocolVersion,
306) -> Result<SearchRelationsRequest, AppError> {
307 let after = request
308 .decoded_cursor(protocol_version)
309 .map_err(search_validation_error)?
310 .map(|cursor| {
311 Ok::<_, AppError>(ahri_tre_core::RelationSearchCursorAnchor {
312 name: cursor.name,
313 domain_id: ahri_tre_types::DomainId(protocol_int_ref_id(
314 "domain",
315 cursor.domain.id,
316 )?),
317 relation_id: ahri_tre_types::EntityRelationId(protocol_int_ref_id(
318 "relation",
319 cursor.relation.id,
320 )?),
321 })
322 })
323 .transpose()?;
324 Ok(SearchRelationsRequest {
325 kind: match &request.predicates {
326 ahri_tre_protocol::search::PredicateSet::AllOf(_) => {
327 ahri_tre_core::PredicateSetKind::AllOf
328 }
329 ahri_tre_protocol::search::PredicateSet::AnyOf(_) => {
330 ahri_tre_core::PredicateSetKind::AnyOf
331 }
332 },
333 predicates: request
334 .predicates
335 .predicates()
336 .iter()
337 .cloned()
338 .map(app_relation_search_predicate)
339 .collect::<Result<Vec<_>, _>>()?,
340 limit: request.page.limit,
341 after,
342 })
343}
344
345fn app_relation_search_predicate(
346 predicate: ahri_tre_protocol::model::RelationPredicate,
347) -> Result<ahri_tre_core::RelationSearchPredicate, AppError> {
348 match predicate {
349 ahri_tre_protocol::model::RelationPredicate::Text(text) => {
350 Ok(ahri_tre_core::RelationSearchPredicate::Text {
351 field: match text.field {
352 ahri_tre_protocol::model::RelationTextField::Name => {
353 ahri_tre_core::RelationSearchTextField::Name
354 }
355 ahri_tre_protocol::model::RelationTextField::Description => {
356 ahri_tre_core::RelationSearchTextField::Description
357 }
358 ahri_tre_protocol::model::RelationTextField::SourceEntityName => {
359 ahri_tre_core::RelationSearchTextField::SourceEntityName
360 }
361 ahri_tre_protocol::model::RelationTextField::TargetEntityName => {
362 ahri_tre_core::RelationSearchTextField::TargetEntityName
363 }
364 ahri_tre_protocol::model::RelationTextField::OntologyClass => {
365 ahri_tre_core::RelationSearchTextField::OntologyClass
366 }
367 },
368 mode: match text.mode {
369 ahri_tre_protocol::search::TextMode::Exact => {
370 ahri_tre_core::SearchTextMode::Exact
371 }
372 ahri_tre_protocol::search::TextMode::Prefix => {
373 ahri_tre_core::SearchTextMode::Prefix
374 }
375 ahri_tre_protocol::search::TextMode::Contains => {
376 ahri_tre_core::SearchTextMode::Contains
377 }
378 },
379 value: text.value,
380 })
381 }
382 ahri_tre_protocol::model::RelationPredicate::Domain { domain } => {
383 Ok(ahri_tre_core::RelationSearchPredicate::Domain(
384 app_relation_search_domain_selector(domain)?,
385 ))
386 }
387 ahri_tre_protocol::model::RelationPredicate::SourceEntity { entity } => {
388 Ok(ahri_tre_core::RelationSearchPredicate::SourceEntity(
389 app_relation_search_entity_selector(entity)?,
390 ))
391 }
392 ahri_tre_protocol::model::RelationPredicate::TargetEntity { entity } => {
393 Ok(ahri_tre_core::RelationSearchPredicate::TargetEntity(
394 app_relation_search_entity_selector(entity)?,
395 ))
396 }
397 ahri_tre_protocol::model::RelationPredicate::Tag(tag) => Ok(
398 ahri_tre_core::RelationSearchPredicate::Tag(match tag.selector {
399 ahri_tre_protocol::refs::TagSelector::Id { tag } => {
400 ahri_tre_core::RelationSearchTagSelector::Id(ahri_tre_types::TagId(
401 protocol_int_ref_id("tag", tag.id)?,
402 ))
403 }
404 ahri_tre_protocol::refs::TagSelector::Label { label, .. } => {
405 ahri_tre_core::RelationSearchTagSelector::Name(
406 ahri_tre_core::normalize_tags([label.as_str().to_string()])
407 .into_iter()
408 .next()
409 .unwrap_or_else(|| label.as_str().to_string()),
410 )
411 }
412 }),
413 ),
414 }
415}
416
417fn app_relation_search_domain_selector(
418 selector: ahri_tre_protocol::domain::DomainSelector,
419) -> Result<ahri_tre_core::RelationSearchDomainSelector, AppError> {
420 match selector {
421 ahri_tre_protocol::domain::DomainSelector::Id { domain } => {
422 Ok(ahri_tre_core::RelationSearchDomainSelector::Id(
423 ahri_tre_types::DomainId(protocol_int_ref_id("domain", domain.id)?),
424 ))
425 }
426 ahri_tre_protocol::domain::DomainSelector::Name { name } => Ok(
427 ahri_tre_core::RelationSearchDomainSelector::Name(name.into()),
428 ),
429 }
430}
431
432fn app_relation_search_entity_selector(
433 selector: ahri_tre_protocol::model::EntitySelector,
434) -> Result<ahri_tre_core::RelationSearchEntitySelector, AppError> {
435 match selector {
436 ahri_tre_protocol::model::EntitySelector::Id { entity, .. } => {
437 Ok(ahri_tre_core::RelationSearchEntitySelector::Id(
438 ahri_tre_types::EntityId(protocol_int_ref_id("entity", entity.id)?),
439 ))
440 }
441 ahri_tre_protocol::model::EntitySelector::Name { domain, name } => {
442 Ok(ahri_tre_core::RelationSearchEntitySelector::Name {
443 domain: Some(app_relation_search_domain_selector(domain)?),
444 name: name.into(),
445 })
446 }
447 }
448}
449
450fn protocol_relation_search_cursor(
451 datastore_id: PublicUuid,
452 request: &ahri_tre_protocol::model::SearchRelationsRequest,
453 protocol_version: &ProtocolVersion,
454 cursor: &ahri_tre_core::RelationSearchCursorAnchor,
455) -> Result<String, AppError> {
456 request
457 .encode_cursor(
458 protocol_version,
459 ahri_tre_protocol::model::RelationSearchCursorAnchor {
460 domain: protocol_domain_ref(datastore_id, cursor.domain_id.0),
461 relation: protocol_relation_ref(datastore_id, cursor.relation_id.0),
462 name: cursor.name.clone(),
463 },
464 )
465 .map_err(search_validation_error)
466}
467
468fn protocol_int_ref_id(scope: &str, reference: PublicUuid) -> Result<i64, AppError> {
469 decode_scoped_integer_ref(scope, reference).ok_or_else(|| {
470 protocol_validation_error("selector ref is invalid", format!("expected {scope} ref"))
471 })
472}
473
474fn search_validation_error(error: ahri_tre_protocol::ProtocolError) -> AppError {
475 AppError::Validation(error.message)
476}
477fn protocol_validation_error(message: &str, detail: impl std::fmt::Display) -> AppError {
478 AppError::Validation(format!("{message}: {detail}"))
479}
480fn protocol_int_ref(scope: &str, id: i64) -> PublicUuid {
481 encode_scoped_integer_ref(scope, id)
482}
483fn protocol_domain_ref(datastore_id: PublicUuid, id: i64) -> ObjectRef {
484 ObjectRef {
485 datastore_id,
486 kind: ObjectKind::Domain,
487 id: protocol_int_ref("domain", id),
488 }
489}
490fn protocol_entity_ref(datastore_id: PublicUuid, id: i64) -> ObjectRef {
491 ObjectRef {
492 datastore_id,
493 kind: ObjectKind::Entity,
494 id: protocol_int_ref("entity", id),
495 }
496}
497fn protocol_relation_ref(datastore_id: PublicUuid, id: i64) -> ObjectRef {
498 ObjectRef {
499 datastore_id,
500 kind: ObjectKind::Relation,
501 id: protocol_int_ref("relation", id),
502 }
503}
504impl AppService {
505 pub async fn search_catalogue_variables(
506 &self,
507 mut request: ahri_tre_protocol::dictionary::SearchVariablesRequest,
508 ) -> Result<
509 (
510 Vec<ahri_tre_protocol::dictionary::VariableSummary>,
511 Option<String>,
512 ),
513 AppError,
514 > {
515 let datastore_id = self.catalogue_datastore_id()?;
516 let version = ProtocolVersion::current();
517 request
518 .validate(&version)
519 .map_err(search_validation_error)?;
520 if let Some(cursor) = request
521 .decoded_cursor(&version)
522 .map_err(search_validation_error)?
523 {
524 self.catalogue_reference_id(cursor.domain, ObjectKind::Domain)?;
525 self.catalogue_reference_id(cursor.variable, ObjectKind::Variable)?;
526 }
527 for predicate in request.predicates.predicates() {
529 match predicate {
530 ahri_tre_protocol::dictionary::VariablePredicate::Domain { domain } => {
531 self.catalogue_domain_selector(domain.clone())?;
532 }
533 ahri_tre_protocol::dictionary::VariablePredicate::Tag(tag) => {
534 if let TagSelector::Id { tag } = tag.selector {
535 self.catalogue_reference_id(tag, ObjectKind::Tag)?;
536 }
537 }
538 ahri_tre_protocol::dictionary::VariablePredicate::Vocabulary { vocabulary } => {
539 self.catalogue_reference_id(*vocabulary, ObjectKind::Vocabulary)?;
540 }
541 _ => {}
542 }
543 }
544 let original = app_variable_search_request(&request, &version)?;
545 let mut resolved = request.clone();
546 resolved.page.cursor = None;
547 let mut unmatched = Vec::new();
548 for (index, predicate) in resolved.predicates.predicates_mut().iter_mut().enumerate() {
549 if let ahri_tre_protocol::dictionary::VariablePredicate::Domain { domain } = predicate {
550 if let Some(value) = self.canonical_catalogue_domain(domain.clone()).await? {
551 *domain = value;
552 } else {
553 unmatched.push(index);
554 }
555 }
556 }
557 if !retain_matching_predicates(&mut resolved.predicates, &unmatched) {
558 return Ok((Vec::new(), None));
559 }
560 let mut query = app_variable_search_request(&resolved, &version)?;
561 query.after = original.after;
562 let result = self.search_variables(query).await?;
563 let next_cursor = result
564 .next_cursor
565 .as_ref()
566 .map(|cursor| protocol_variable_search_cursor(datastore_id, &request, &version, cursor))
567 .transpose()?;
568 let values = result
569 .variables
570 .into_iter()
571 .map(|entry| {
572 let mut summary = crate::projections::semantic_variable(
573 datastore_id,
574 RegisteredVariable {
575 variable: entry.variable,
576 value_type: entry.value_type,
577 vocabulary: entry.vocabulary,
578 vocabulary_items: entry.vocabulary_items,
579 },
580 )
581 .summary;
582 summary.tags = entry
583 .tags
584 .into_iter()
585 .map(|tag| crate::projections::tag_summary(datastore_id, tag))
586 .collect();
587 summary
588 })
589 .collect();
590 Ok((values, next_cursor))
591 }
592 pub async fn search_catalogue_entities(
593 &self,
594 mut request: ahri_tre_protocol::model::SearchEntitiesRequest,
595 ) -> Result<(Vec<ahri_tre_protocol::model::EntitySummary>, Option<String>), AppError> {
596 let datastore_id = self.catalogue_datastore_id()?;
597 let version = ProtocolVersion::current();
598 request
599 .validate(&version)
600 .map_err(search_validation_error)?;
601 if let Some(cursor) = request
602 .decoded_cursor(&version)
603 .map_err(search_validation_error)?
604 {
605 self.catalogue_reference_id(cursor.domain, ObjectKind::Domain)?;
606 self.catalogue_reference_id(cursor.entity, ObjectKind::Entity)?;
607 }
608 for predicate in request.predicates.predicates() {
610 match predicate {
611 ahri_tre_protocol::model::EntityPredicate::Domain { domain } => {
612 self.catalogue_domain_selector(domain.clone())?;
613 }
614 ahri_tre_protocol::model::EntityPredicate::Tag(tag) => {
615 if let TagSelector::Id { tag } = tag.selector {
616 self.catalogue_reference_id(tag, ObjectKind::Tag)?;
617 }
618 }
619 _ => {}
620 }
621 }
622 let original = app_entity_search_request(&request, &version)?;
623 let mut resolved = request.clone();
624 resolved.page.cursor = None;
625 let mut unmatched = Vec::new();
626 for (index, predicate) in resolved.predicates.predicates_mut().iter_mut().enumerate() {
627 if let ahri_tre_protocol::model::EntityPredicate::Domain { domain } = predicate {
628 if let Some(value) = self.canonical_catalogue_domain(domain.clone()).await? {
629 *domain = value;
630 } else {
631 unmatched.push(index);
632 }
633 }
634 }
635 if !retain_matching_predicates(&mut resolved.predicates, &unmatched) {
636 return Ok((Vec::new(), None));
637 }
638 let mut query = app_entity_search_request(&resolved, &version)?;
639 query.after = original.after;
640 let result = self.search_entities(query).await?;
641 let next_cursor = result
642 .next_cursor
643 .as_ref()
644 .map(|cursor| protocol_entity_search_cursor(datastore_id, &request, &version, cursor))
645 .transpose()?;
646 let values = result
647 .entities
648 .into_iter()
649 .map(|entry| {
650 let mut summary =
651 crate::projections::semantic_entity(datastore_id, entry.entity).summary;
652 summary.tags = entry
653 .tags
654 .into_iter()
655 .map(|tag| crate::projections::tag_summary(datastore_id, tag))
656 .collect();
657 summary
658 })
659 .collect();
660 Ok((values, next_cursor))
661 }
662 pub async fn search_catalogue_relations(
663 &self,
664 mut request: ahri_tre_protocol::model::SearchRelationsRequest,
665 ) -> Result<
666 (
667 Vec<ahri_tre_protocol::model::RelationDefinitionSummary>,
668 Option<String>,
669 ),
670 AppError,
671 > {
672 let datastore_id = self.catalogue_datastore_id()?;
673 let version = ProtocolVersion::current();
674 request
675 .validate(&version)
676 .map_err(search_validation_error)?;
677 if let Some(cursor) = request
678 .decoded_cursor(&version)
679 .map_err(search_validation_error)?
680 {
681 self.catalogue_reference_id(cursor.domain, ObjectKind::Domain)?;
682 self.catalogue_reference_id(cursor.relation, ObjectKind::Relation)?;
683 }
684 for predicate in request.predicates.predicates() {
686 match predicate {
687 ahri_tre_protocol::model::RelationPredicate::Domain { domain } => {
688 self.catalogue_domain_selector(domain.clone())?;
689 }
690 ahri_tre_protocol::model::RelationPredicate::Tag(tag) => {
691 if let TagSelector::Id { tag } = tag.selector {
692 self.catalogue_reference_id(tag, ObjectKind::Tag)?;
693 }
694 }
695 ahri_tre_protocol::model::RelationPredicate::SourceEntity { entity }
696 | ahri_tre_protocol::model::RelationPredicate::TargetEntity { entity } => {
697 if let ahri_tre_protocol::model::EntitySelector::Id { entity, domain } = entity
698 {
699 self.catalogue_reference_id(*entity, ObjectKind::Entity)?;
700 if let Some(domain) = domain {
701 self.catalogue_domain_selector(domain.clone())?;
702 }
703 }
704 }
705 _ => {}
706 }
707 }
708 let original = app_relation_search_request(&request, &version)?;
709 let mut resolved = request.clone();
710 resolved.page.cursor = None;
711 let mut unmatched = Vec::new();
712 for (index, predicate) in resolved.predicates.predicates_mut().iter_mut().enumerate() {
713 match predicate {
714 ahri_tre_protocol::model::RelationPredicate::Domain { domain } => {
715 if let Some(value) = self.canonical_catalogue_domain(domain.clone()).await? {
716 *domain = value;
717 } else {
718 unmatched.push(index);
719 }
720 }
721 ahri_tre_protocol::model::RelationPredicate::SourceEntity { entity }
722 | ahri_tre_protocol::model::RelationPredicate::TargetEntity { entity } => {
723 let selector = self.catalogue_entity_selector(entity.clone()).await?;
724 if let Some(value) = self.resolve_entity_selector_optional(selector).await? {
725 *entity = ahri_tre_protocol::model::EntitySelector::Id {
726 entity: protocol_entity_ref(datastore_id, value.entity_id.0),
727 domain: None,
728 };
729 } else {
730 unmatched.push(index);
731 }
732 }
733 _ => {}
734 }
735 }
736 if !retain_matching_predicates(&mut resolved.predicates, &unmatched) {
737 return Ok((Vec::new(), None));
738 }
739 let mut query = app_relation_search_request(&resolved, &version)?;
740 query.after = original.after;
741 let result = self.search_entity_relations(query).await?;
742 let next_cursor = result
743 .next_cursor
744 .as_ref()
745 .map(|cursor| protocol_relation_search_cursor(datastore_id, &request, &version, cursor))
746 .transpose()?;
747 let values = result
748 .relations
749 .into_iter()
750 .map(|entry| {
751 let names = BTreeMap::from([
752 (
753 entry.source_entity.entity_id.0,
754 entry.source_entity.name.as_str().to_owned(),
755 ),
756 (
757 entry.target_entity.entity_id.0,
758 entry.target_entity.name.as_str().to_owned(),
759 ),
760 ]);
761 let mut summary =
762 crate::projections::semantic_relation(datastore_id, entry.relation, &names)
763 .summary;
764 summary.tags = entry
765 .tags
766 .into_iter()
767 .map(|tag| crate::projections::tag_summary(datastore_id, tag))
768 .collect();
769 summary
770 })
771 .collect();
772 Ok((values, next_cursor))
773 }
774}