pub mod init;
pub mod links_utils;
pub mod receive;
pub mod validation_package;
use crate::{
context::Context,
nucleus::CallbackFnCall,
wasm_engine::{
self,
callback::{init::init, receive::receive},
runtime::WasmCallData,
Defn,
},
};
use holochain_core_types::{
entry::Entry, error::RibosomeEncodedValue, validation::ValidationPackageDefinition,
};
use holochain_json_api::{
error::JsonError,
json::{default_to_json, JsonString},
};
use holochain_wasm_utils::{
api_serialization::receive::ReceiveParams, memory::allocation::WasmAllocation,
};
use num_traits::FromPrimitive;
use serde_json;
use std::{convert::TryFrom, str::FromStr, sync::Arc};
#[derive(FromPrimitive, Debug, PartialEq)]
pub enum Callback {
MissingNo = 0,
Init,
Receive,
}
impl FromStr for Callback {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"init" => Ok(Callback::Init),
"receive" => Ok(Callback::Receive),
other if other.is_empty() => Ok(Callback::MissingNo),
_ => Err("Cannot convert string to Callback"),
}
}
}
#[holochain_tracing_macros::newrelic_autotrace(HOLOCHAIN_CORE)]
impl Callback {
#[cfg_attr(tarpaulin, skip)]
pub fn as_fn(
&self,
) -> fn(context: Arc<Context>, zome: &str, params: &CallbackParams) -> CallbackResult {
fn noop(_context: Arc<Context>, _zome: &str, _params: &CallbackParams) -> CallbackResult {
CallbackResult::Pass
}
match *self {
Callback::MissingNo => noop,
Callback::Init => init,
Callback::Receive => receive,
}
}
}
#[holochain_tracing_macros::newrelic_autotrace(HOLOCHAIN_CORE)]
impl Defn for Callback {
fn as_str(&self) -> &'static str {
match *self {
Callback::MissingNo => "",
Callback::Init => "init",
Callback::Receive => "receive",
}
}
fn str_to_index(s: &str) -> usize {
match Callback::from_str(s) {
Ok(i) => i as usize,
Err(_) => Callback::MissingNo as usize,
}
}
fn from_index(i: usize) -> Self {
match FromPrimitive::from_usize(i) {
Some(v) => v,
None => Callback::MissingNo,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, DefaultJson)]
#[allow(clippy::large_enum_variant)]
pub enum CallbackParams {
Init,
ValidateCommit(Entry),
Receive(ReceiveParams),
}
impl ToString for CallbackParams {
fn to_string(&self) -> String {
match self {
CallbackParams::Init => String::new(),
CallbackParams::ValidateCommit(serialized_entry) => {
String::from(JsonString::from(serialized_entry.to_owned()))
}
CallbackParams::Receive(params) => JsonString::from(params).to_string(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum CallbackResult {
Pass,
Fail(String),
NotImplemented(String),
ValidationPackageDefinition(ValidationPackageDefinition),
ReceiveResult(String),
}
impl From<CallbackResult> for JsonString {
fn from(v: CallbackResult) -> Self {
default_to_json(v)
}
}
impl From<JsonString> for CallbackResult {
fn from(json_string: JsonString) -> CallbackResult {
let r#try: Result<CallbackResult, serde_json::Error> =
serde_json::from_str(&String::from(json_string.clone()));
match r#try {
Ok(callback_result) => callback_result,
Err(_) => CallbackResult::Fail(String::from(json_string)),
}
}
}
impl From<RibosomeEncodedValue> for CallbackResult {
fn from(ribosome_return_code: RibosomeEncodedValue) -> CallbackResult {
match ribosome_return_code {
RibosomeEncodedValue::Failure(ribosome_error_code) => {
CallbackResult::Fail(ribosome_error_code.to_string())
}
RibosomeEncodedValue::Allocation(ribosome_allocation) => {
match WasmAllocation::try_from(ribosome_allocation) {
Ok(allocation) => CallbackResult::Fail(allocation.read_to_string()),
Err(allocation_error) => CallbackResult::Fail(String::from(allocation_error)),
}
}
RibosomeEncodedValue::Success => CallbackResult::Pass,
}
}
}
#[holochain_tracing_macros::newrelic_autotrace(HOLOCHAIN_CORE)]
pub(crate) fn run_callback(context: Arc<Context>, call: CallbackFnCall) -> CallbackResult {
match wasm_engine::run_dna(
Some(call.clone().parameters.to_bytes()),
WasmCallData::new_callback_call(context, call),
) {
Ok(call_result) => {
if call_result.is_null() {
CallbackResult::Pass
} else {
CallbackResult::Fail(call_result.to_string())
}
}
Err(_) => CallbackResult::NotImplemented("run_callback".into()),
}
}
#[autotrace]
#[holochain_tracing_macros::newrelic_autotrace(HOLOCHAIN_CORE)]
pub fn call(
context: Arc<Context>,
zome: &str,
function: &Callback,
params: &CallbackParams,
) -> CallbackResult {
let call = CallbackFnCall::new(zome, &function.as_str().to_string(), (*params).clone());
let dna = context.get_dna().expect("Callback called without DNA set!");
match dna.get_wasm_from_zome_name(zome) {
None => CallbackResult::NotImplemented("call/1".into()),
Some(wasm) => {
if wasm.code.is_empty() {
CallbackResult::NotImplemented("call/2".into())
} else {
run_callback(context, call)
}
}
}
}
#[cfg(test)]
pub mod tests {
use self::wabt::Wat2Wasm;
use crate::{
instance::{tests::test_instance, Instance},
wasm_engine::{callback::Callback, Defn},
};
use std::str::FromStr;
use test_utils;
use wabt;
pub fn test_callback_wasm(canonical_name: &str, result: u64) -> Vec<u8> {
Wat2Wasm::new()
.canonicalize_lebs(false)
.write_debug_names(true)
.convert(
format!(
r#"
(module
(memory 1)
(export "memory" (memory 0))
(func
(export "{}")
(param $allocation i64)
(result i64)
(i64.const {})
)
)
"#,
canonical_name, result,
),
)
.expect("string literal should be valid WAT")
.as_ref()
.to_vec()
}
pub fn test_callback_instance(
zome: &str,
canonical_name: &str,
result: u64,
network_name: Option<&str>,
) -> Result<Instance, String> {
let dna =
test_utils::create_test_dna_with_wasm(zome, test_callback_wasm(canonical_name, result));
test_instance(dna, network_name)
}
#[test]
fn test_from_str() {
assert_eq!(
Callback::Init,
Callback::from_str("init").expect("string literal should be valid callback")
);
assert_eq!(
Callback::Receive,
Callback::from_str("receive").expect("string literal should be valid callback")
);
assert_eq!(
"Cannot convert string to Callback",
Callback::from_str("foo").expect_err("string literal shouldn't be valid callback"),
);
}
#[test]
fn defn_test() {
for (input, output) in vec![
(Callback::MissingNo, ""),
(Callback::Init, "init"),
(Callback::Receive, "receive"),
] {
assert_eq!(output, input.as_str());
}
for (input, output) in vec![("", 0), ("init", 1), ("receive", 2)] {
assert_eq!(output, Callback::str_to_index(input));
}
for (input, output) in vec![
(0, Callback::MissingNo),
(1, Callback::Init),
(2, Callback::Receive),
] {
assert_eq!(output, Callback::from_index(input));
}
}
}