1use crate::Result;
18use crate::config::AppServiceConfig;
19use crate::MemoryStats;
20use crate::service_interface::ServiceInterface;
21use super::AppServiceError;
22
23#[cfg(feature = "raw-module-api")]
24use marine_wasm_backend_traits::WasiState;
25use marine_wasm_backend_traits::WasmBackend;
26use marine::generic::Marine;
27use marine::generic::MarineModuleConfig;
28use marine::MarineError;
29use marine::MError;
30use marine::IValue;
31
32use serde_json::Value as JValue;
33
34use std::convert::TryInto;
35use std::collections::HashMap;
36use std::path::Path;
37use std::io::ErrorKind;
38
39const SERVICE_ID_ENV_NAME: &str = "service_id";
40
41pub struct AppService<WB: WasmBackend> {
42 marine: marine::generic::Marine<WB>,
43 facade_module_name: String,
44}
45
46impl<WB: WasmBackend> AppService<WB> {
47 pub async fn new<C, S>(config: C, service_id: S, envs: HashMap<String, String>) -> Result<Self>
49 where
50 C: TryInto<AppServiceConfig<WB>>,
51 S: Into<String>,
52 AppServiceError: From<C::Error>,
53 {
54 let backend = <WB as WasmBackend>::new_async()
55 .map_err(|e| MarineError::EngineError(MError::WasmBackendError(e)))?;
56
57 Self::new_with_backend(backend, config, service_id, envs).await
58 }
59
60 pub async fn new_with_backend<C, S>(
61 backend: WB,
62 config: C,
63 service_id: S,
64 envs: HashMap<String, String>,
65 ) -> Result<Self>
66 where
67 C: TryInto<AppServiceConfig<WB>>,
68 S: Into<String>,
69 AppServiceError: From<C::Error>,
70 {
71 let mut config: AppServiceConfig<WB> = config.try_into()?;
72 let facade_module_name = config
73 .marine_config
74 .modules_config
75 .last()
76 .ok_or_else(|| {
77 AppServiceError::ConfigParseError(String::from(
78 "config should contain at least one module",
79 ))
80 })?
81 .import_name
82 .clone();
83
84 let service_id = service_id.into();
85 Self::set_env_and_dirs(&mut config, service_id, envs)?;
86
87 let marine = Marine::with_raw_config(backend, config.marine_config).await?;
88
89 Ok(Self {
90 marine,
91 facade_module_name,
92 })
93 }
94
95 pub async fn call_async(
97 &mut self,
98 func_name: impl AsRef<str>,
99 arguments: JValue,
100 call_parameters: crate::CallParameters,
101 ) -> Result<JValue> {
102 self.marine
103 .call_with_json_async(
104 &self.facade_module_name,
105 func_name,
106 arguments,
107 call_parameters,
108 )
109 .await
110 .map_err(Into::into)
111 }
112
113 pub async fn call_with_ivalues_async(
115 &mut self,
116 func_name: impl AsRef<str>,
117 arguments: &[IValue],
118 call_parameters: crate::CallParameters,
119 ) -> Result<Vec<IValue>> {
120 self.marine
121 .call_with_ivalues_async(
122 &self.facade_module_name,
123 func_name,
124 arguments,
125 call_parameters,
126 )
127 .await
128 .map_err(Into::into)
129 }
130
131 pub fn get_interface(&self) -> ServiceInterface {
133 use crate::service_interface::into_service_interface;
134
135 let marine_facade_interface = self
136 .marine
137 .get_interface()
138 .modules
139 .remove(self.facade_module_name.as_str())
140 .unwrap();
142
143 into_service_interface(marine_facade_interface)
144 }
145
146 fn set_env_and_dirs(
150 config: &mut AppServiceConfig<WB>,
151 service_id: String,
152 mut envs: HashMap<String, String>,
153 ) -> Result<()> {
154 let working_dir = &config.service_working_dir;
155
156 envs.insert(SERVICE_ID_ENV_NAME.to_string(), service_id);
157
158 for module in &mut config.marine_config.modules_config {
159 module.config.extend_wasi_envs(envs.clone());
160 module.config.root_wasi_files_at(working_dir);
162
163 create_wasi_dirs(&module.config)?;
166 }
167 Ok(())
168 }
169
170 pub fn module_memory_stats(&self) -> MemoryStats<'_> {
173 self.marine.module_memory_stats()
174 }
175}
176
177#[cfg(feature = "raw-module-api")]
179impl<WB: WasmBackend> AppService<WB> {
180 pub async fn new_with_empty_facade<C, S>(
181 backend: WB,
182 config: C,
183 service_id: S,
184 envs: HashMap<String, String>,
185 ) -> Result<Self>
186 where
187 S: Into<String>,
188 C: TryInto<AppServiceConfig<WB>>,
189 AppServiceError: From<C::Error>,
190 {
191 let mut config: AppServiceConfig<WB> = config.try_into()?;
192 let service_id = service_id.into();
193 Self::set_env_and_dirs(&mut config, service_id, envs)?;
194
195 let marine = Marine::with_raw_config(backend, config.marine_config).await?;
196
197 Ok(Self {
198 marine,
199 facade_module_name: String::new(),
200 })
201 }
202
203 pub async fn call_module(
204 &mut self,
205 module_name: impl AsRef<str>,
206 func_name: impl AsRef<str>,
207 arguments: JValue,
208 call_parameters: crate::CallParameters,
209 ) -> Result<JValue> {
210 self.marine
211 .call_with_json_async(module_name, func_name, arguments, call_parameters)
212 .await
213 .map_err(Into::into)
214 }
215
216 pub async fn load_module<C, S>(
217 &mut self,
218 name: S,
219 wasm_bytes: &[u8],
220 config: Option<C>,
221 ) -> Result<()>
222 where
223 S: Into<String>,
224 C: TryInto<marine::generic::MarineModuleConfig<WB>>,
225 marine::MarineError: From<C::Error>,
226 {
227 self.marine
228 .load_module(name, wasm_bytes, config)
229 .await
230 .map_err(Into::into)
231 }
232
233 pub fn unload_module(&mut self, module_name: impl AsRef<str>) -> Result<()> {
234 self.marine.unload_module(module_name).map_err(Into::into)
235 }
236
237 pub fn get_full_interface(&self) -> marine::MarineInterface<'_> {
239 self.marine.get_interface()
240 }
241
242 pub fn get_wasi_state(
244 &mut self,
245 module_name: impl AsRef<str>,
246 ) -> Result<Box<dyn WasiState + '_>> {
247 self.marine
248 .module_wasi_state(module_name)
249 .map_err(Into::into)
250 }
251}
252
253fn create_wasi_dirs<WB: WasmBackend>(config: &MarineModuleConfig<WB>) -> Result<()> {
254 if let Some(wasi_config) = &config.wasi {
255 for dir in wasi_config.mapped_dirs.values() {
256 create(dir)?;
257 }
258 }
259
260 Ok(())
261}
262
263fn create(dir: &Path) -> Result<()> {
264 match std::fs::create_dir_all(dir) {
265 Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()),
266 Err(err) => Err(AppServiceError::CreateDir {
267 err,
268 path: dir.to_owned(),
269 }),
270 _ => Ok(()),
271 }
272}