pub use dpp_plugin_traits as traits;
pub use dpp_rules as rules;
pub mod validate;
use dpp_plugin_traits::{AbiResult, DppSectorPlugin, PluginError, PluginInput};
pub use dpp_plugin_traits::{
METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT, METRIC_REPAIRABILITY_INDEX,
PluginComplianceStatus,
};
use serde::Serialize;
pub mod abi {
use std::alloc::{Layout, alloc as mem_alloc, dealloc as mem_dealloc};
#[must_use]
pub fn host_alloc(len: u32) -> u32 {
if len == 0 {
return 0;
}
let layout = Layout::from_size_align(len as usize, 1).expect("valid layout");
unsafe { mem_alloc(layout) as u32 }
}
pub fn host_dealloc(ptr: u32, len: u32) {
if ptr == 0 || len == 0 {
return;
}
let layout = Layout::from_size_align(len as usize, 1).expect("valid layout");
unsafe { mem_dealloc(ptr as *mut u8, layout) }
}
#[must_use]
pub unsafe fn read_input<'a>(ptr: u32, len: u32) -> &'a [u8] {
unsafe {
if len == 0 {
return &[];
}
std::slice::from_raw_parts(ptr as *const u8, len as usize)
}
}
#[must_use]
pub fn write_output(bytes: Vec<u8>) -> u64 {
let mut boxed = bytes.into_boxed_slice();
let out_len = boxed.len() as u32;
let out_ptr = boxed.as_mut_ptr() as usize as u32;
std::mem::forget(boxed);
((out_ptr as u64) << 32) | (out_len as u64)
}
}
fn to_bytes<T: Serialize>(value: &T) -> Vec<u8> {
serde_json::to_vec(value).unwrap_or_default()
}
fn parse_input(bytes: &[u8]) -> Result<PluginInput, PluginError> {
serde_json::from_slice(bytes).map_err(|e| PluginError::InvalidInput(e.to_string()))
}
pub fn metadata_bytes<P: DppSectorPlugin>(plugin: &P) -> Vec<u8> {
to_bytes(&plugin.meta())
}
pub fn describe_bytes<P: DppSectorPlugin>(plugin: &P) -> Vec<u8> {
to_bytes(&plugin.capabilities())
}
pub fn validate_bytes<P: DppSectorPlugin>(plugin: &P, input: &[u8]) -> Vec<u8> {
let outcome = match parse_input(input) {
Ok(value) => match plugin.validate_input(&value) {
Ok(()) => AbiResult::Ok(serde_json::Value::Null),
Err(e) => AbiResult::Error(e),
},
Err(e) => AbiResult::Error(e),
};
to_bytes(&outcome)
}
pub fn calculate_metrics_bytes<P: DppSectorPlugin>(plugin: &P, input: &[u8]) -> Vec<u8> {
let outcome = match parse_input(input) {
Ok(value) => match plugin.calculate_metrics(&value) {
Ok(result) => AbiResult::ok(&result),
Err(e) => AbiResult::Error(e),
},
Err(e) => AbiResult::Error(e),
};
to_bytes(&outcome)
}
pub fn generate_passport_bytes<P: DppSectorPlugin>(plugin: &P, input: &[u8]) -> Vec<u8> {
let outcome = match parse_input(input) {
Ok(value) => match plugin.generate_passport(&value) {
Ok(payload) => AbiResult::Ok(payload),
Err(e) => AbiResult::Error(e),
},
Err(e) => AbiResult::Error(e),
};
to_bytes(&outcome)
}
pub fn run_metadata<P: DppSectorPlugin>(plugin: &P) -> u64 {
abi::write_output(metadata_bytes(plugin))
}
pub fn run_describe<P: DppSectorPlugin>(plugin: &P) -> u64 {
abi::write_output(describe_bytes(plugin))
}
pub unsafe fn run_validate<P: DppSectorPlugin>(plugin: &P, ptr: u32, len: u32) -> u64 {
unsafe { abi::write_output(validate_bytes(plugin, abi::read_input(ptr, len))) }
}
pub unsafe fn run_calculate_metrics<P: DppSectorPlugin>(plugin: &P, ptr: u32, len: u32) -> u64 {
unsafe { abi::write_output(calculate_metrics_bytes(plugin, abi::read_input(ptr, len))) }
}
pub unsafe fn run_generate_passport<P: DppSectorPlugin>(plugin: &P, ptr: u32, len: u32) -> u64 {
unsafe { abi::write_output(generate_passport_bytes(plugin, abi::read_input(ptr, len))) }
}
#[macro_export]
macro_rules! export_plugin {
($plugin:ty) => {
#[unsafe(no_mangle)]
pub extern "C" fn alloc(len: u32) -> u32 {
$crate::abi::host_alloc(len)
}
#[unsafe(no_mangle)]
pub extern "C" fn dealloc(ptr: u32, len: u32) {
$crate::abi::host_dealloc(ptr, len)
}
#[unsafe(no_mangle)]
pub extern "C" fn metadata() -> u64 {
$crate::run_metadata(&<$plugin as ::core::default::Default>::default())
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> u64 {
$crate::run_describe(&<$plugin as ::core::default::Default>::default())
}
#[unsafe(no_mangle)]
pub extern "C" fn validate(ptr: u32, len: u32) -> u64 {
unsafe {
$crate::run_validate(&<$plugin as ::core::default::Default>::default(), ptr, len)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn calculate_metrics(ptr: u32, len: u32) -> u64 {
unsafe {
$crate::run_calculate_metrics(
&<$plugin as ::core::default::Default>::default(),
ptr,
len,
)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn generate_passport(ptr: u32, len: u32) -> u64 {
unsafe {
$crate::run_generate_passport(
&<$plugin as ::core::default::Default>::default(),
ptr,
len,
)
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use dpp_plugin_traits::{
AbiVersion, METRIC_CO2E_SCORE, PluginCapabilities, PluginCapability,
PluginComplianceStatus, PluginFieldError, PluginMeta, PluginResult, SchemaVersionRange,
};
use serde_json::{Value, json};
#[derive(Default)]
struct DummyPlugin;
impl DppSectorPlugin for DummyPlugin {
fn meta(&self) -> PluginMeta {
PluginMeta {
sector: "dummy".into(),
name: "Dummy".into(),
version: "0.1.0".into(),
license: "Apache-2.0".into(),
description: None,
author: None,
homepage: None,
}
}
fn capabilities(&self) -> PluginCapabilities {
PluginCapabilities {
abi_version: AbiVersion::current(),
supported_schemas: vec![SchemaVersionRange {
min_version: "1.0.0".into(),
max_version: "1.0.0".into(),
}],
capabilities: vec![PluginCapability::ComputeMetrics],
min_host_version: None,
max_fuel: None,
max_memory_bytes: None,
}
}
fn validate_input(&self, input: &PluginInput) -> Result<(), PluginError> {
if input.get("ok").is_some() {
Ok(())
} else {
Err(PluginError::ValidationErrors(vec![PluginFieldError {
field: "/ok".into(),
code: "missing".into(),
message: "ok is required".into(),
}]))
}
}
fn calculate_metrics(&self, input: &PluginInput) -> Result<PluginResult, PluginError> {
self.validate_input(input)?;
Ok(PluginResult::new(PluginComplianceStatus::NotAssessed)
.maybe_metric(METRIC_CO2E_SCORE, input.get("co2e").and_then(Value::as_f64)))
}
fn generate_passport(&self, input: &PluginInput) -> Result<Value, PluginError> {
self.validate_input(input)?;
Ok(input.clone())
}
}
fn parse(bytes: &[u8]) -> Value {
serde_json::from_slice(bytes).expect("glue emits valid JSON")
}
#[test]
fn describe_emits_capabilities() {
let json = parse(&describe_bytes(&DummyPlugin));
assert_eq!(json["abiVersion"]["major"], 1);
assert!(json["supportedSchemas"].is_array());
let back: PluginCapabilities = serde_json::from_value(json).unwrap();
assert_eq!(back.abi_version, AbiVersion::current());
}
#[test]
fn metadata_emits_meta() {
let json = parse(&metadata_bytes(&DummyPlugin));
assert_eq!(json["sector"], "dummy");
}
#[test]
fn calculate_metrics_ok_envelope() {
let input = json!({ "ok": true, "co2e": 42.0 });
let json = parse(&calculate_metrics_bytes(&DummyPlugin, &to_bytes(&input)));
assert_eq!(json["ok"]["metrics"]["co2e_score"], 42.0);
assert_eq!(json["ok"]["complianceStatus"], "NOT_ASSESSED");
}
#[test]
fn calculate_metrics_validation_error_envelope() {
let input = json!({ "co2e": 42.0 }); let json = parse(&calculate_metrics_bytes(&DummyPlugin, &to_bytes(&input)));
assert!(json.get("error").is_some());
assert!(json.get("ok").is_none());
}
#[test]
fn validate_error_on_malformed_json() {
let json = parse(&validate_bytes(&DummyPlugin, b"not json {{{"));
let back: AbiResult = serde_json::from_value(json).unwrap();
assert!(!back.is_ok());
}
#[test]
fn validate_ok_envelope_is_null() {
let input = json!({ "ok": true });
let json = parse(&validate_bytes(&DummyPlugin, &to_bytes(&input)));
assert!(json["ok"].is_null());
}
#[test]
fn generate_passport_passthrough() {
let input = json!({ "ok": true, "gtin": "12345678901231" });
let json = parse(&generate_passport_bytes(&DummyPlugin, &to_bytes(&input)));
assert_eq!(json["ok"]["gtin"], "12345678901231");
}
#[test]
fn validate_error_when_input_parses_but_is_rejected() {
let input = json!({ "missing": "ok" });
let json = parse(&validate_bytes(&DummyPlugin, &to_bytes(&input)));
assert!(json.get("error").is_some());
assert!(json.get("ok").is_none());
}
#[test]
fn generate_passport_error_when_input_parses_but_is_rejected() {
let input = json!({ "missing": "ok" });
let json = parse(&generate_passport_bytes(&DummyPlugin, &to_bytes(&input)));
assert!(json.get("error").is_some());
assert!(json.get("ok").is_none());
}
export_plugin!(DummyPlugin);
fn out_len(packed: u64) -> usize {
(packed & 0xFFFF_FFFF) as usize
}
#[test]
fn macro_alloc_dealloc_are_callable() {
assert_eq!(alloc(0), 0);
let _ = alloc(8);
dealloc(0, 0);
}
#[test]
fn macro_metadata_and_describe_pack_glue_output() {
assert_eq!(out_len(metadata()), metadata_bytes(&DummyPlugin).len());
assert_eq!(out_len(describe()), describe_bytes(&DummyPlugin).len());
}
#[test]
fn macro_input_exports_pack_error_envelope_for_empty_input() {
assert_eq!(
out_len(validate(0, 0)),
validate_bytes(&DummyPlugin, &[]).len()
);
assert_eq!(
out_len(calculate_metrics(0, 0)),
calculate_metrics_bytes(&DummyPlugin, &[]).len()
);
assert_eq!(
out_len(generate_passport(0, 0)),
generate_passport_bytes(&DummyPlugin, &[]).len()
);
}
}