authz_resolver/domain/
service.rs1use std::sync::Arc;
4use std::time::Duration;
5
6use authz_resolver_sdk::{
7 AuthZResolverPluginClient, AuthZResolverPluginSpecV1, EvaluationRequest, EvaluationResponse,
8};
9use modkit::client_hub::{ClientHub, ClientScope};
10use modkit::plugins::{GtsPluginSelector, choose_plugin_instance};
11use modkit::telemetry::ThrottledLog;
12use modkit_macros::domain_model;
13use tracing::info;
14use types_registry_sdk::{ListQuery, TypesRegistryClient};
15
16use super::error::DomainError;
17
18const UNAVAILABLE_LOG_THROTTLE: Duration = Duration::from_secs(10);
20
21#[domain_model]
23pub struct Service {
24 hub: Arc<ClientHub>,
25 vendor: String,
26 selector: GtsPluginSelector,
27 unavailable_log_throttle: ThrottledLog,
28}
29
30impl Service {
31 #[must_use]
32 pub fn new(hub: Arc<ClientHub>, vendor: String) -> Self {
33 Self {
34 hub,
35 vendor,
36 selector: GtsPluginSelector::new(),
37 unavailable_log_throttle: ThrottledLog::new(UNAVAILABLE_LOG_THROTTLE),
38 }
39 }
40
41 async fn get_plugin(&self) -> Result<Arc<dyn AuthZResolverPluginClient>, DomainError> {
42 let instance_id = self.selector.get_or_init(|| self.resolve_plugin()).await?;
43 let scope = ClientScope::gts_id(instance_id.as_ref());
44
45 if let Some(client) = self
46 .hub
47 .try_get_scoped::<dyn AuthZResolverPluginClient>(&scope)
48 {
49 Ok(client)
50 } else {
51 if self.unavailable_log_throttle.should_log() {
52 tracing::warn!(
53 plugin_gts_id = %instance_id,
54 vendor = %self.vendor,
55 "Plugin client not registered yet"
56 );
57 }
58 Err(DomainError::PluginUnavailable {
59 gts_id: instance_id.to_string(),
60 reason: "client not registered yet".into(),
61 })
62 }
63 }
64
65 #[tracing::instrument(skip_all, fields(vendor = %self.vendor))]
66 async fn resolve_plugin(&self) -> Result<String, DomainError> {
67 info!("Resolving authz_resolver plugin");
68
69 let registry = self
70 .hub
71 .get::<dyn TypesRegistryClient>()
72 .map_err(|e| DomainError::TypesRegistryUnavailable(e.to_string()))?;
73
74 let plugin_type_id = AuthZResolverPluginSpecV1::gts_schema_id().clone();
75
76 let instances = registry
77 .list(
78 ListQuery::new()
79 .with_pattern(format!("{plugin_type_id}*"))
80 .with_is_type(false),
81 )
82 .await?;
83
84 let gts_id = choose_plugin_instance::<AuthZResolverPluginSpecV1>(
85 &self.vendor,
86 instances.iter().map(|e| (e.gts_id.as_str(), &e.content)),
87 )?;
88 info!(plugin_gts_id = %gts_id, "Selected authz_resolver plugin instance");
89
90 Ok(gts_id)
91 }
92
93 #[tracing::instrument(skip_all)]
100 pub async fn evaluate(
101 &self,
102 request: EvaluationRequest,
103 ) -> Result<EvaluationResponse, DomainError> {
104 let plugin = self.get_plugin().await?;
105 plugin.evaluate(request).await.map_err(DomainError::from)
106 }
107}