Skip to main content

greentic_runner_host/
provider.rs

1use std::str::FromStr;
2use std::sync::Arc;
3
4use anyhow::{Context, Result, anyhow, bail};
5use greentic_types::{
6    EnvId, PackManifest, ProviderRuntimeRef, StateKey as StoreStateKey, TenantCtx, TenantId,
7};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use crate::storage::DynStateStore;
12use crate::storage::state::STATE_PREFIX;
13
14#[derive(Clone, Debug, Serialize)]
15pub struct ProviderBinding {
16    pub provider_id: Option<String>,
17    pub provider_type: String,
18    pub component_ref: String,
19    pub export: String,
20    pub world: String,
21    pub config_json: Option<String>,
22    pub pack_ref: Option<String>,
23}
24
25#[derive(Clone, Debug)]
26pub struct OperatorProviderMetadata {
27    pub provider_id: Option<String>,
28    pub provider_type: String,
29    pub capabilities: Vec<String>,
30    pub ops: Vec<String>,
31    pub config_schema_ref: Option<String>,
32    pub state_schema_ref: Option<String>,
33    pub runtime: ProviderRuntimeRef,
34    pub docs_ref: Option<String>,
35    pub pack_ref: Option<String>,
36}
37
38#[derive(Clone, Debug, Deserialize)]
39struct ProviderInstance {
40    provider_id: String,
41    provider_type: String,
42    pack_ref: Option<String>,
43    component_ref: String,
44    export: String,
45    world: String,
46    #[serde(default)]
47    enabled: bool,
48    #[serde(default)]
49    config: Value,
50}
51
52#[derive(Clone, Debug, Deserialize)]
53struct ProviderExtRuntime {
54    component_ref: String,
55    export: String,
56    world: String,
57}
58
59#[derive(Clone, Debug, Deserialize)]
60#[allow(dead_code)]
61struct ProviderExtDecl {
62    #[serde(default)]
63    provider_id: Option<String>,
64    provider_type: String,
65    #[serde(default)]
66    capabilities: Vec<String>,
67    #[serde(default)]
68    ops: Vec<String>,
69    #[serde(default)]
70    config_schema_ref: Option<String>,
71    #[serde(default)]
72    state_schema_ref: Option<String>,
73    runtime: ProviderExtRuntime,
74    #[serde(default)]
75    docs_ref: Option<String>,
76}
77
78#[derive(Clone)]
79pub struct ProviderRegistry {
80    pack_ref: Option<String>,
81    inline: Vec<ProviderExtDecl>,
82    state_store: Option<DynStateStore>,
83    tenant: TenantCtx,
84}
85
86impl ProviderRegistry {
87    pub fn new(
88        manifest: &PackManifest,
89        state_store: Option<DynStateStore>,
90        tenant: &str,
91        env: &str,
92    ) -> Result<Self> {
93        let inline = extract_inline_providers(manifest)?;
94        let tenant_ctx = TenantCtx::new(
95            EnvId::from_str(env).unwrap_or_else(|_| EnvId::from_str("local").expect("local env")),
96            TenantId::from_str(tenant).with_context(|| format!("invalid tenant id `{tenant}`"))?,
97        );
98        let pack_ref = Some(format!(
99            "{}@{}",
100            manifest.pack_id.as_str(),
101            manifest.version
102        ));
103        Ok(Self {
104            pack_ref,
105            inline,
106            state_store,
107            tenant: tenant_ctx,
108        })
109    }
110
111    pub fn operator_metadata(&self) -> Vec<OperatorProviderMetadata> {
112        self.inline
113            .iter()
114            .map(|decl| OperatorProviderMetadata {
115                provider_id: decl.provider_id.clone(),
116                provider_type: decl.provider_type.clone(),
117                capabilities: decl.capabilities.clone(),
118                ops: decl.ops.clone(),
119                config_schema_ref: decl.config_schema_ref.clone(),
120                state_schema_ref: decl.state_schema_ref.clone(),
121                runtime: ProviderRuntimeRef {
122                    component_ref: decl.runtime.component_ref.clone(),
123                    export: decl.runtime.export.clone(),
124                    world: decl.runtime.world.clone(),
125                },
126                docs_ref: decl.docs_ref.clone(),
127                pack_ref: self.pack_ref.clone(),
128            })
129            .collect()
130    }
131
132    pub fn resolve(
133        &self,
134        provider_id: Option<&str>,
135        provider_type: Option<&str>,
136    ) -> Result<ProviderBinding> {
137        if provider_id.is_none() && provider_type.is_none() {
138            bail!("provider.invoke requires provider_id or provider_type");
139        }
140
141        if let Some(id) = provider_id {
142            if let Some(binding) = self.load_instance(id)? {
143                return Ok(binding);
144            }
145            if let Some(ext) = self
146                .inline
147                .iter()
148                .find(|decl| decl.provider_id.as_deref() == Some(id))
149            {
150                return Ok(binding_from_decl(ext, self.pack_ref.clone(), None));
151            }
152            bail!("provider_id `{id}` not found");
153        }
154
155        let provider_type = provider_type.unwrap();
156        let matches: Vec<_> = self
157            .inline
158            .iter()
159            .filter(|decl| decl.provider_type == provider_type)
160            .collect();
161        match matches.as_slice() {
162            [] => bail!("no provider runtime found for type `{provider_type}`"),
163            [decl] => Ok(binding_from_decl(
164                decl,
165                self.pack_ref.clone(),
166                Some(provider_type.to_string()),
167            )),
168            _ => bail!("multiple providers found for type `{provider_type}`, specify provider_id"),
169        }
170    }
171
172    fn load_instance(&self, provider_id: &str) -> Result<Option<ProviderBinding>> {
173        let store = match &self.state_store {
174            Some(store) => Arc::clone(store),
175            None => return Ok(None),
176        };
177        let key = StoreStateKey::from(format!("providers/instances/{provider_id}.json"));
178        let value = store
179            .get_json(&self.tenant, STATE_PREFIX, &key, None)
180            .map_err(|err| anyhow!(err.to_string()))
181            .with_context(|| format!("failed to load provider instance `{provider_id}`"))?;
182        let Some(doc) = value else {
183            return Ok(None);
184        };
185        let instance: ProviderInstance = serde_json::from_value(doc)
186            .with_context(|| format!("invalid provider instance `{provider_id}`"))?;
187        if !instance.enabled {
188            bail!("provider `{provider_id}` is disabled");
189        }
190        Ok(Some(binding_from_instance(instance)))
191    }
192}
193
194fn extract_inline_providers(manifest: &PackManifest) -> Result<Vec<ProviderExtDecl>> {
195    let Some(inline) = manifest.provider_extension_inline() else {
196        return Ok(Vec::new());
197    };
198
199    let providers = inline
200        .providers
201        .iter()
202        .map(|provider| ProviderExtDecl {
203            provider_id: Some(provider.provider_type.clone()),
204            provider_type: provider.provider_type.clone(),
205            capabilities: provider.capabilities.clone(),
206            ops: provider.ops.clone(),
207            config_schema_ref: Some(provider.config_schema_ref.clone()),
208            state_schema_ref: provider.state_schema_ref.clone(),
209            runtime: ProviderExtRuntime {
210                component_ref: provider.runtime.component_ref.clone(),
211                export: provider.runtime.export.clone(),
212                world: provider.runtime.world.clone(),
213            },
214            docs_ref: provider.docs_ref.clone(),
215        })
216        .collect();
217
218    Ok(providers)
219}
220
221fn binding_from_decl(
222    decl: &ProviderExtDecl,
223    pack_ref: Option<String>,
224    default_provider_id: Option<String>,
225) -> ProviderBinding {
226    ProviderBinding {
227        provider_id: decl.provider_id.clone().or(default_provider_id),
228        provider_type: decl.provider_type.clone(),
229        component_ref: decl.runtime.component_ref.clone(),
230        export: decl.runtime.export.clone(),
231        world: decl.runtime.world.clone(),
232        config_json: None,
233        pack_ref,
234    }
235}
236
237fn binding_from_instance(instance: ProviderInstance) -> ProviderBinding {
238    ProviderBinding {
239        config_json: if instance.config.is_null() {
240            None
241        } else {
242            Some(instance.config.to_string())
243        },
244        provider_id: Some(instance.provider_id),
245        provider_type: instance.provider_type,
246        component_ref: instance.component_ref,
247        export: instance.export,
248        world: instance.world,
249        pack_ref: instance.pack_ref,
250    }
251}