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}