Skip to main content

axioval_engine/
services.rs

1//! Type-safe host service registration.
2
3use std::{
4    any::{Any, TypeId},
5    collections::HashMap,
6    sync::Arc,
7};
8use thiserror::Error;
9
10/// Service registration failure.
11#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
12pub enum ServiceRegistryError {
13    #[error("a service of this type is already registered")]
14    Duplicate,
15}
16
17/// Immutable type-indexed services supplied by an application or adapter.
18#[derive(Clone, Default)]
19pub struct ServiceRegistry {
20    entries: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
21}
22impl ServiceRegistry {
23    /// Creates an empty registry.
24    #[must_use]
25    pub fn new() -> Self {
26        Self::default()
27    }
28    /// Registers one service without allowing silent replacement.
29    pub fn register<T: Any + Send + Sync>(
30        &mut self,
31        service: T,
32    ) -> Result<(), ServiceRegistryError> {
33        if self.entries.contains_key(&TypeId::of::<T>()) {
34            return Err(ServiceRegistryError::Duplicate);
35        }
36        self.entries.insert(TypeId::of::<T>(), Arc::new(service));
37        Ok(())
38    }
39    /// Registers a service, replacing any existing one of the same type.
40    ///
41    /// Crate-private: only the engine may overwrite, and only for services it
42    /// derives per run and must not accept from a host.
43    pub(crate) fn replace<T: Any + Send + Sync>(&mut self, service: T) {
44        self.entries.insert(TypeId::of::<T>(), Arc::new(service));
45    }
46    /// Number of registered services.
47    pub(crate) fn len(&self) -> usize {
48        self.entries.len()
49    }
50    /// Looks up a service by its concrete interface type.
51    #[must_use]
52    pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
53        self.entries.get(&TypeId::of::<T>())?.downcast_ref()
54    }
55}
56
57/// Evidence an adapter may present as fact.
58///
59/// Exact, and carrying a locator a human can follow back to the source. Both
60/// halves matter: an exact measurement nobody can audit is not reviewable, and
61/// a well-located estimate is not exact. Owned here so every service applies
62/// the same admission test.
63pub(crate) fn reviewable_exact_evidence(evidence: &axioval_ir::Evidence) -> bool {
64    evidence.exact && !evidence.locator.trim().is_empty()
65}