Skip to main content

ahri_tre_app/
browser_datastore_discovery.rs

1//! Browser-safe ready-Datastore discovery workflow projection.
2
3use 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/// Safe failure exposed by a browser Datastore discovery capability.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
20#[error("browser Datastore discovery failed")]
21pub struct BrowserDatastoreDiscoveryError;
22
23/// Adapter capability accepted by the app-owned discovery workflow.
24pub trait BrowserDatastoreDiscoveryCapability {
25    fn discover(
26        &self,
27        context: Option<&CorrelationContext>,
28    ) -> Result<ahri_tre_pgmeta::DatastoreServerDiscovery, BrowserDatastoreDiscoveryError>;
29}
30
31/// Discovers the configured browser Datastore through an already-resolved
32/// adapter capability and returns only safe stable DTOs.
33pub 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
55/// Projects adapter-owned operational discovery into the stable browser-safe
56/// response without exposing topology, credentials, or maintenance states.
57pub 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}