pulumi_gestalt_rust_adapter_wasm/
lib.rs1pub 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}