use anyhow::anyhow;
use borderless_id_types::{AgentId, BorderlessId, ContractId};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{fmt::Display, str::FromStr};
use crate::{debug, error, NamedSink};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MethodOrId {
ByName { method: String },
ById { method_id: u32 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallAction {
#[serde(flatten)]
pub method: MethodOrId,
pub params: Value,
}
impl FromStr for CallAction {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
impl CallAction {
pub fn new(method: MethodOrId, params: Value) -> Self {
Self { method, params }
}
pub fn by_method(method_name: impl AsRef<str>, params: Value) -> Self {
Self {
method: MethodOrId::ByName {
method: method_name.as_ref().to_string(),
},
params,
}
}
pub fn by_method_id(method_id: u32, params: Value) -> Self {
Self {
method: MethodOrId::ById { method_id },
params,
}
}
pub fn method_name(&self) -> Option<&str> {
match &self.method {
MethodOrId::ByName { method } => Some(method.as_str()),
MethodOrId::ById { .. } => None,
}
}
pub fn method_id(&self) -> Option<u32> {
match self.method {
MethodOrId::ByName { .. } => None,
MethodOrId::ById { method_id } => Some(method_id),
}
}
pub fn print_method(&self) -> String {
match &self.method {
MethodOrId::ByName { method } => format!("method-name={method}"),
MethodOrId::ById { method_id } => format!("method-id={method_id}"),
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
serde_json::from_slice(bytes)
}
pub fn pretty_print(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&self)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
serde_json::to_vec(&self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContractCall {
pub contract_id: ContractId,
pub action: CallAction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCall {
pub agent_id: AgentId,
pub action: CallAction,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Events {
pub contracts: Vec<ContractCall>,
pub local: Vec<AgentCall>,
}
impl Events {
pub fn is_empty(&self) -> bool {
self.contracts.is_empty() && self.local.is_empty()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, postcard::Error> {
postcard::from_bytes(bytes)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
postcard::to_allocvec(self)
}
}
#[derive(Debug)]
pub enum SinkType {
Named(String),
Agent(AgentId),
Contract(ContractId),
}
impl Display for SinkType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SinkType::Named(s) => write!(f, "{s}"),
SinkType::Agent(s) => write!(f, "{s}"),
SinkType::Contract(s) => write!(f, "{s}"),
}
}
}
#[derive(Default)]
pub struct ActionOutput {
actions: Vec<(SinkType, CallAction)>,
}
impl ActionOutput {
pub fn new() -> Self {
Self::default()
}
pub fn add_event<T: NamedSink>(&mut self, target: T) {
let (sink_name, action) = target.into_action();
self.actions
.push((SinkType::Named(sink_name.to_string()), action));
}
pub fn add_event_dynamic<S, IntoAction>(&mut self, sink_alias: S, action: IntoAction)
where
S: AsRef<str>,
IntoAction: TryInto<CallAction>,
<IntoAction as TryInto<CallAction>>::Error: std::fmt::Display,
{
let alias = sink_alias.as_ref().to_string();
let action = match action.try_into() {
Ok(a) => a,
Err(e) => {
error!("critical error while converting action for dynamic sink '{alias}': {e}");
crate::__private::abort();
}
};
self.actions.push((SinkType::Named(alias), action))
}
pub fn add_event_for_contract<IntoAction>(
&mut self,
contract_id: ContractId,
action: IntoAction,
) where
IntoAction: TryInto<CallAction>,
<IntoAction as TryInto<CallAction>>::Error: std::fmt::Display,
{
let action = match action.try_into() {
Ok(a) => a,
Err(e) => {
error!(
"critical error while converting action for dynamic sink '{contract_id}': {e}"
);
crate::__private::abort();
}
};
self.actions.push((SinkType::Contract(contract_id), action))
}
pub fn add_event_for_process<IntoAction>(&mut self, agent_id: AgentId, action: IntoAction)
where
IntoAction: TryInto<CallAction>,
<IntoAction as TryInto<CallAction>>::Error: std::fmt::Display,
{
let action = match action.try_into() {
Ok(a) => a,
Err(e) => {
error!("critical error while converting action for dynamic sink '{agent_id}': {e}");
crate::__private::abort();
}
};
self.actions.push((SinkType::Agent(agent_id), action))
}
}
pub trait ActionOutEvent: private::Sealed {
fn convert_out_events(self) -> crate::Result<Events>;
}
mod private {
pub trait Sealed {}
}
impl private::Sealed for () {}
impl ActionOutEvent for () {
fn convert_out_events(self) -> crate::Result<Events> {
Ok(Events::default())
}
}
impl<E> private::Sealed for Result<(), E> where E: std::fmt::Display + Send + Sync + 'static {}
impl<E> ActionOutEvent for Result<(), E>
where
E: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static,
{
fn convert_out_events(self) -> crate::Result<Events> {
self.map_err(|e| crate::Error::msg(e))?.convert_out_events()
}
}
impl private::Sealed for ActionOutput {}
impl ActionOutEvent for ActionOutput {
fn convert_out_events(self) -> crate::Result<Events> {
let caller = crate::contracts::env::executor();
let sinks = crate::contracts::env::sinks();
let mut contracts = Vec::new();
let mut local = Vec::new();
for (sink, action) in self.actions {
match sink {
SinkType::Named(alias) => {
if let Some(sink) = sinks.iter().find(|s| s.has_alias(&alias)) {
if !sink.has_access(caller) {
debug!("caller {caller} does not have access to sink {alias}");
continue;
}
match sink {
Sink::Contract { contract_id, .. } => {
contracts.push(ContractCall {
contract_id: *contract_id,
action,
})
}
Sink::Agent { agent_id, .. } => local.push(AgentCall {
agent_id: *agent_id,
action,
}),
}
} else {
return Err(anyhow!("Failed to find sink '{alias}', which is referenced in the action output"));
}
}
SinkType::Agent(agent_id) => local.push(AgentCall { agent_id, action }),
SinkType::Contract(contract_id) => contracts.push(ContractCall {
contract_id,
action,
}),
}
}
Ok(Events { contracts, local })
}
}
impl<E> private::Sealed for Result<ActionOutput, E> where
E: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static
{
}
impl<E> ActionOutEvent for Result<ActionOutput, E>
where
E: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static,
{
fn convert_out_events(self) -> crate::Result<Events> {
let inner = self.map_err(|e| crate::Error::msg(e))?;
inner.convert_out_events()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Sink {
Contract {
contract_id: ContractId,
alias: String,
restrict_to_users: Vec<BorderlessId>,
},
Agent {
agent_id: AgentId,
alias: String,
owner: BorderlessId,
},
}
impl Sink {
pub fn agent(agent_id: AgentId, alias: String, owner: BorderlessId) -> Sink {
Sink::Agent {
agent_id,
alias: alias.to_ascii_uppercase(),
owner,
}
}
pub fn contract(
contract_id: ContractId,
alias: String,
restrict_to_users: Vec<BorderlessId>,
) -> Sink {
Sink::Contract {
contract_id,
alias: alias.to_ascii_uppercase(),
restrict_to_users,
}
}
pub fn has_access(&self, user: BorderlessId) -> bool {
match self {
Sink::Agent { owner, .. } => *owner == user,
Sink::Contract {
restrict_to_users, ..
} => {
restrict_to_users.is_empty() || restrict_to_users.iter().any(|u| *u == user)
}
}
}
pub fn has_alias(&self, alias: impl AsRef<str>) -> bool {
let own_alias = match self {
Sink::Agent { alias, .. } | Sink::Contract { alias, .. } => alias,
};
alias.as_ref().eq_ignore_ascii_case(own_alias)
}
pub fn is_process(&self) -> bool {
match self {
Sink::Agent { .. } => true,
Sink::Contract { .. } => false,
}
}
}