Skip to main content

pulumi_gestalt_rust_adapter_wasm/
lib.rs

1pub mod runner;
2
3use anyhow::{Error, Result, anyhow};
4use pulumi_gestalt_rust_adapter::{
5    ConfigValue, GestaltCompositeOutput, GestaltContext, GestaltOutput, InvokeResourceRequest,
6    RegisterResourceRequest,
7};
8use pulumi_gestalt_wit::client_bindings;
9use pulumi_gestalt_wit::client_bindings::component::pulumi_gestalt::context::Context as WitContext;
10use pulumi_gestalt_wit::client_bindings::component::pulumi_gestalt::output_interface;
11use pulumi_gestalt_wit::client_bindings::component::pulumi_gestalt::types::FunctionInvocationResult;
12use pulumi_gestalt_wit::client_bindings::component::pulumi_gestalt::types::{
13    FunctionInvocationRequest, ObjectField, RegisterResourceRequest as WitRegisterResourceRequest,
14    ResourceInvokeRequest as WitResourceInvokeRequest,
15};
16use serde::Serialize;
17use serde::de::DeserializeOwned;
18use std::collections::HashMap;
19use std::marker::PhantomData;
20use std::rc::Rc;
21use std::sync::RwLock;
22use uuid::Uuid;
23
24type Function = Box<dyn Fn(&String) -> Result<String, Error> + Send>;
25
26pub struct WasmOutput<T> {
27    wasm_output: output_interface::Output,
28    context: Rc<RwLock<InnerWasmContext>>,
29    phantom: PhantomData<T>,
30}
31
32impl<T> Clone for WasmOutput<T> {
33    fn clone(&self) -> Self {
34        WasmOutput {
35            context: self.context.clone(),
36            wasm_output: self.wasm_output.clone(),
37            phantom: PhantomData,
38        }
39    }
40}
41
42pub(crate) struct InnerWasmContext {
43    wit_context: WitContext,
44    functions: HashMap<String, Function>,
45}
46
47pub struct WasmContext {
48    context: Rc<RwLock<InnerWasmContext>>,
49}
50
51impl GestaltContext for WasmContext {
52    type Output<T> = WasmOutput<T>;
53    type CompositeOutput = WasmCompositeOutput;
54
55    fn new_output<T: serde::Serialize>(&self, value: &T) -> WasmOutput<T> {
56        Self::new_output_priv(self, value, false)
57    }
58
59    fn new_secret<T: serde::Serialize>(&self, value: &T) -> WasmOutput<T> {
60        Self::new_output_priv(self, value, true)
61    }
62
63    fn register_resource(
64        &self,
65        request: RegisterResourceRequest<Self::Output<()>>,
66    ) -> Self::CompositeOutput {
67        let mut object_fields = Vec::new();
68        for object in request.object {
69            object_fields.push(ObjectField {
70                name: object.name.clone(),
71                value: &object.value.wasm_output,
72            });
73        }
74        let request = WitRegisterResourceRequest {
75            type_: request.type_,
76            name: request.name,
77            version: request.version,
78            object: object_fields,
79        };
80
81        let context = self.context.clone();
82        let context = context.read().unwrap();
83
84        let result = context.wit_context.register_resource(&request);
85
86        WasmCompositeOutput {
87            context: self.context.clone(),
88            wasm_output: result,
89        }
90    }
91
92    fn invoke_resource(
93        &self,
94        request: InvokeResourceRequest<Self::Output<()>>,
95    ) -> Self::CompositeOutput {
96        let mut object_fields = Vec::new();
97        for object in request.object {
98            object_fields.push(ObjectField {
99                name: object.name.clone(),
100                value: &object.value.wasm_output,
101            });
102        }
103        let request = WitResourceInvokeRequest {
104            token: request.token,
105            version: request.version,
106            object: object_fields,
107        };
108
109        let context = self.context.clone();
110        let context = context.read().unwrap();
111
112        let result = context.wit_context.invoke_resource(&request);
113
114        WasmCompositeOutput {
115            context: self.context.clone(),
116            wasm_output: result,
117        }
118    }
119
120    fn get_config(
121        &self,
122        name: Option<&str>,
123        key: &str,
124    ) -> Option<ConfigValue<Self::Output<String>>> {
125        let context = self.context.clone();
126        let context = context.read().unwrap();
127        let result = context.wit_context.get_config(name, key);
128        result.map(|v| match v {
129            client_bindings::component::pulumi_gestalt::types::ConfigValue::Plaintext(pt) => {
130                ConfigValue::PlainText(pt.to_string())
131            }
132            client_bindings::component::pulumi_gestalt::types::ConfigValue::Secret(s) => {
133                let output = WasmOutput {
134                    context: self.context.clone(),
135                    wasm_output: s,
136                    phantom: PhantomData,
137                };
138                ConfigValue::Secret(output)
139            }
140        })
141    }
142}
143
144impl WasmContext {
145    fn new() -> WasmContext {
146        let wit_context = WitContext::new();
147        let context = InnerWasmContext {
148            wit_context,
149            functions: HashMap::new(),
150        };
151
152        WasmContext {
153            context: Rc::new(RwLock::new(context)),
154        }
155    }
156
157    fn new_output_priv<T: serde::Serialize>(&self, value: &T, secret: bool) -> WasmOutput<T> {
158        let binding = serde_json::to_string(&value).unwrap();
159        let context = self.context.clone();
160        let inner_context = context.read().unwrap();
161        let resource = inner_context
162            .wit_context
163            .create_output(binding.as_str(), secret);
164        WasmOutput {
165            context: self.context.clone(),
166            wasm_output: resource,
167            phantom: PhantomData,
168        }
169    }
170
171    fn invoke_function(&self, function_id: &str, value: &str) -> Result<String, Error> {
172        let context = self.context.clone();
173        let context = context.read().unwrap();
174        let function = context
175            .functions
176            .get(function_id)
177            .ok_or_else(|| anyhow!("Function with id {function_id} not found"))?;
178        let result = function(&value.to_owned())?;
179        Ok(result)
180    }
181
182    fn invoke_finish(
183        &self,
184        results: Vec<FunctionInvocationResult>,
185    ) -> Result<Vec<FunctionInvocationRequest>> {
186        let context = self.context.clone();
187        let context = context.read().unwrap();
188        let functions = context.wit_context.finish(&results);
189        Ok(functions)
190    }
191}
192
193impl InnerWasmContext {
194    fn put_function<T, B, F>(&mut self, f: F) -> String
195    where
196        F: Fn(T) -> B + Send + 'static,
197        T: DeserializeOwned,
198        B: Serialize,
199    {
200        let f = move |arg: &String| {
201            let argument = serde_json::from_str(arg)?;
202            let result = f(argument);
203            let result = serde_json::to_string(&result)?;
204            Ok(result)
205        };
206
207        let uuid = Uuid::now_v7().to_string();
208        self.functions.insert(uuid.clone(), Box::new(f));
209
210        uuid
211    }
212}
213
214impl<T> GestaltOutput<T> for WasmOutput<T> {
215    type Me<A> = WasmOutput<A>;
216
217    fn map<B, F>(&self, f: F) -> Self::Me<B>
218    where
219        F: Fn(T) -> B + Send + 'static,
220        T: DeserializeOwned,
221        B: Serialize,
222    {
223        let context = self.context.clone();
224        let mut context = context.write().unwrap();
225
226        let function_name = context.put_function(f);
227        let new_output = self.wasm_output.map(function_name.as_str());
228
229        WasmOutput {
230            context: self.context.clone(),
231            wasm_output: new_output,
232            phantom: PhantomData,
233        }
234    }
235
236    fn add_to_export(&self, key: &str) {
237        self.wasm_output.add_to_export(key);
238    }
239
240    fn combine<RESULT>(&self, others: &[&Self::Me<()>]) -> Self::Me<RESULT> {
241        let other_outputs = others
242            .iter()
243            .map(|other| &other.wasm_output)
244            .collect::<Vec<_>>();
245        let result = self.wasm_output.combine(&other_outputs);
246        WasmOutput {
247            context: self.context.clone(),
248            wasm_output: result,
249            phantom: PhantomData,
250        }
251    }
252
253    unsafe fn transmute<F>(self) -> Self::Me<F> {
254        WasmOutput {
255            context: self.context.clone(),
256            wasm_output: self.wasm_output,
257            phantom: PhantomData,
258        }
259    }
260}
261
262pub struct WasmCompositeOutput {
263    context: Rc<RwLock<InnerWasmContext>>,
264    wasm_output: output_interface::CompositeOutput,
265}
266
267impl GestaltCompositeOutput for WasmCompositeOutput {
268    type Output<T> = WasmOutput<T>;
269    fn get_field<T>(&self, key: &str) -> Self::Output<T> {
270        let output_id = self.wasm_output.get_field(key);
271
272        WasmOutput {
273            context: self.context.clone(),
274            wasm_output: output_id,
275            phantom: PhantomData,
276        }
277    }
278}