ahri_tre_app/
browser_datastore_discovery.rs1use ahri_tre_observability::{CorrelationContext, FailureCategory, Outcome, Stage};
4use ahri_tre_protocol::{
5 PublicUuid,
6 datastore::{
7 BROWSER_DATASTORE_DISCOVERY_AUTHORITY, BrowserDatastoreDiscoveryEmptyState,
8 BrowserDatastoreDiscoveryRecord, BrowserDatastoreDiscoveryResponse,
9 },
10};
11use ahri_tre_types::DatastoreLifecycleState;
12use thiserror::Error;
13
14use crate::AppError;
15
16pub use ahri_tre_protocol::datastore::BROWSER_DATASTORE_DISCOVERY_AUTHORITY as BROWSER_DISCOVERY_AUTHORITY;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
20#[error("browser Datastore discovery failed")]
21pub struct BrowserDatastoreDiscoveryError;
22
23pub trait BrowserDatastoreDiscoveryCapability {
25 fn discover(
26 &self,
27 context: Option<&CorrelationContext>,
28 ) -> Result<ahri_tre_pgmeta::DatastoreServerDiscovery, BrowserDatastoreDiscoveryError>;
29}
30
31pub fn discover_browser_ready_datastores(
34 capability: &dyn BrowserDatastoreDiscoveryCapability,
35 context: Option<&CorrelationContext>,
36) -> Result<BrowserDatastoreDiscoveryResponse, AppError> {
37 let span = context.map(|context| context.span(Stage::Application));
38 let child_context = span.as_ref().map(|span| span.context());
39 let result = capability.discover(child_context.as_ref());
40 if let Some(span) = span {
41 span.finish(
42 if result.is_ok() {
43 Outcome::Success
44 } else {
45 Outcome::Unavailable
46 },
47 result.as_ref().err().map(|_| FailureCategory::Dependency),
48 );
49 }
50 result
51 .map(project_browser_ready_datastores)
52 .map_err(|_| AppError::Infrastructure("browser Datastore discovery failed".to_string()))
53}
54
55pub fn project_browser_ready_datastores(
58 report: ahri_tre_pgmeta::DatastoreServerDiscovery,
59) -> BrowserDatastoreDiscoveryResponse {
60 let datastores = report
61 .datastores
62 .into_iter()
63 .filter(|candidate| candidate.status == ahri_tre_pgmeta::DatastoreCandidateStatus::Ready)
64 .flat_map(|candidate| {
65 let schema_version = candidate
66 .schema
67 .as_ref()
68 .and_then(|schema| schema.current_version.clone())
69 .unwrap_or_else(|| {
70 candidate
71 .schema
72 .as_ref()
73 .map(|schema| schema.target_version.clone())
74 .unwrap_or_default()
75 });
76 candidate
77 .bindings
78 .into_iter()
79 .filter(|binding| binding.lifecycle_state == DatastoreLifecycleState::Ready)
80 .map(move |binding| BrowserDatastoreDiscoveryRecord {
81 datastore_id: PublicUuid::from_uuid(binding.datastore_id),
82 datastore: binding.datastore_name.clone(),
83 display_name: binding.datastore_name,
84 lifecycle_state: binding.lifecycle_state,
85 schema_version: schema_version.clone(),
86 ready: true,
87 })
88 })
89 .collect::<Vec<_>>();
90
91 let empty_state = datastores
92 .is_empty()
93 .then(|| BrowserDatastoreDiscoveryEmptyState {
94 code: "no_browser_ready_datastores".to_string(),
95 message: "No browser-ready datastores are available.".to_string(),
96 });
97
98 BrowserDatastoreDiscoveryResponse {
99 authority: BROWSER_DATASTORE_DISCOVERY_AUTHORITY.to_string(),
100 datastores,
101 empty_state,
102 }
103}