blob: 2b0fa3f7f373991c9cea274f4e864b3c7a177296 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Scoped JavaScript execution for the Host runtime. Original Host contexts
//! retain all authorization and custody; the adapter exposes only their ABI.
use futures_util::future::BoxFuture;
use maka_host_runtime::application::{
Context, Handler, Result,
abi::{HostActivation, HostObservation, HostRequest},
backend,
};
use maka_js_runtime::plugin::{Captured, Limits, Vm};
use maka_plugin_core::{contract::Interface, package::EntryDeclaration, runtime::Published};
use serde_json::Value;
use std::{collections::BTreeMap, sync::Arc};
pub fn factory(limits: Limits) -> Arc<dyn backend::Factory> {
Arc::new(Factory(limits))
}
struct Factory(Limits);
impl backend::Factory for Factory {
fn create(&self) -> Result<Arc<dyn backend::Realm>> {
Vm::scoped(self.0.clone())
.map(|vm| Arc::new(Realm(vm)) as Arc<dyn backend::Realm>)
.map_err(message)
}
}
struct Realm(Vm);
impl backend::Realm for Realm {
fn load(&self, name: String, source: String) -> Result<Arc<dyn backend::Module>> {
self.0
.load(name, source)
.map(|module| Arc::new(Module(module)) as Arc<dyn backend::Module>)
.map_err(message)
}
fn is_terminated(&self) -> bool {
self.0.is_terminated()
}
fn failed(&self) -> BoxFuture<'static, String> {
let vm = self.0.clone();
Box::pin(async move { vm.failed().await.to_string() })
}
fn shutdown(&self) -> BoxFuture<'static, ()> {
let vm = self.0.clone();
Box::pin(async move { vm.shutdown().await })
}
}
struct Module(maka_js_runtime::plugin::Module);
impl backend::Module for Module {
fn activate(
&self,
entry: Arc<EntryDeclaration>,
configuration: Value,
) -> BoxFuture<'static, Result<backend::Exports>> {
let module = self.0.clone();
Box::pin(async move {
let actual: BTreeMap<String, HostActivation> = serde_json::from_value(
module
.call(vec!["activate".into()], vec![configuration])
.await
.map_err(message)?,
)
.map_err(message)?;
entry
.check_exports(&actual, |actual| &actual.contract)
.map_err(message)?;
let mut exports = BTreeMap::new();
for (name, actual) in actual {
let Interface::Service { methods } = &actual.contract.interface else {
return Err("Host export must declare a service".into());
};
if actual.service.is_empty() {
return Err("service namespace is missing".into());
}
let callbacks = module
.capture(actual.service, methods.keys().cloned().collect())
.await
.map_err(message)?;
exports.insert(
name,
Published {
contract: actual.contract,
value: Arc::new(Service(callbacks)) as Arc<dyn Handler>,
},
);
}
Ok(exports)
})
}
fn close(&self) -> BoxFuture<'static, Result<()>> {
let module = self.0.clone();
Box::pin(async move { module.close().await.map_err(message) })
}
}
struct Service(Captured);
impl Handler for Service {
fn call(
&self,
method: String,
input: Value,
context: Context,
) -> BoxFuture<'static, Result<Value>> {
let callbacks = self.0.clone();
Box::pin(async move {
let observation = serde_json::to_value(HostObservation {
source: context.scope().identity.clone(),
operation: context.operation().cloned(),
})
.map_err(message)?;
callbacks
.call_with_context(
&method,
vec![input, observation],
Arc::new(Request(context)),
)
.await
.map_err(message)
})
}
}
struct Request(Context);
impl maka_js_runtime::plugin::RequestBridge for Request {
fn invoke(
&self,
input: Value,
) -> BoxFuture<'static, std::result::Result<Value, maka_js_runtime::plugin::Error>> {
let request =
serde_json::from_value::<HostRequest>(input).map(|request| self.0.request(request));
Box::pin(async move {
request
.map_err(|error| maka_js_runtime::plugin::Error(error.to_string()))?
.await
.map_err(maka_js_runtime::plugin::Error)
})
}
}
fn message(error: impl ToString) -> String {
error.to_string()
}