use extism_convert::{FromBytes, Json, ToBytes};
use lazy_regex::regex;
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::PathBuf;
use std::time::SystemTime;
#[derive(PartialEq, Eq, Hash, Debug, Clone, Deserialize, Serialize)]
pub struct ArtifactMapping {
pub local_file: PathBuf,
pub root_relative_target_dir: PathBuf,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize)]
#[encoding(Json)]
pub struct BoolPayload {
pub value: bool,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize)]
#[encoding(Json)]
pub struct SystemTimePayload {
pub value: SystemTime,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize, Debug)]
#[encoding(Json)]
pub struct DirectoryStructurePayload {
pub entries: Vec<FileEntry>,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize)]
#[encoding(Json)]
pub struct FileWriteOperationPayload {
pub relative_path: String,
pub contents: String,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize)]
#[encoding(Json)]
pub struct FileReadOperationInPayload {
pub relative_path: String,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize)]
#[encoding(Json)]
pub struct FileReadOperationOutPayload {
pub contents: String,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize)]
#[encoding(Json)]
pub struct FileReadBase64OperationInPayload {
pub relative_path: String,
}
#[derive(ToBytes, Serialize, FromBytes, Deserialize)]
#[encoding(Json)]
pub struct FileReadBase64OperationOutPayload {
pub contents: String,
}
#[derive(Serialize, Deserialize, Debug, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct FileEntry {
pub relative_path: String,
pub is_dir: bool,
pub size: u64,
pub permissions: String,
pub modified: Option<String>,
pub created: Option<String>,
}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct NodeProcessingPayload {
pub parameter_values: HashMap<String, serde_yaml::Value>,
pub node: Node,
pub cluster_path: PathBuf,
}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct DummyPayload {}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct ExtensionFieldProcessingPayload {
pub node_processing_payload: NodeProcessingPayload,
pub field_name: String,
pub value: serde_yaml::Value,
}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct ClusterProcessingPayload {
pub parameter_values: HashMap<String, serde_yaml::Value>,
pub cluster_path: PathBuf,
}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct ArchivePayload {
pub parameter_values: HashMap<String, serde_yaml::Value>,
pub cluster_paths: Vec<PathBuf>,
}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct ExtensionFieldProcessingResult {
pub result: anyhow::Result<HashSet<ArtifactMapping>, NodeProcessingError>,
}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct ParamsSchema {
pub schema: HashMap<String, (bool, serde_json::Value)>,
}
#[derive(ToBytes, FromBytes, Serialize, Deserialize, Debug)]
#[encoding(Json)]
pub struct ClusterProcessingResult {
pub hash_set: HashSet<ArtifactMapping>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum NodeProcessingError {
CannotProcessFieldType,
Remarks(Vec<String>),
}
impl NodeProcessingError {
pub fn indicates_inability_to_process_field(&self) -> bool {
match self {
Self::CannotProcessFieldType => true,
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub enum EdgeType {
All,
AtLeastOne,
}
#[derive(Clone, Debug)]
pub struct TypedEdge {
pub start_id: NodeID,
pub end_id: NodeID,
pub kind: EdgeType,
}
#[derive(Debug, Serialize)]
pub struct UnlockingCondition {
pub all_of: HashSet<NodeID>,
pub one_of: HashSet<NodeID>,
}
#[derive(Clone, Debug)]
pub struct UnloadedPlugin {
pub path: String,
pub parameters: HashMap<String, Value>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NodeID {
pub namespace: String,
pub local_id: String,
}
impl std::fmt::Display for NodeID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}__{}", self.namespace, self.local_id)
}
}
impl NodeID {
pub fn from_two_part_string(string: &str) -> Result<NodeID, StructuralError> {
let string = string;
let identifier_regex = regex!("[a-z][a-z_]*");
let parts = string.split("__").collect::<Vec<_>>();
let invalid_part = parts.iter().find(|p| !identifier_regex.is_match(p));
if let Some(part) = invalid_part {
Err(StructuralError::InvalidIdentifierError(part.to_string()).into())
} else if parts.len() == 1 {
Err(StructuralError::NodeMissingNamespace(string.to_string()).into())
} else if parts.len() > 2 {
Err(StructuralError::NodeMultipleNamespace(string.to_string()).into())
} else {
Ok(NodeID {
namespace: parts[0].to_owned(),
local_id: parts[1].to_owned(),
})
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Node {
pub node_id: NodeID,
pub title: String,
pub extension_fields: HashMap<String, Value>,
}
#[derive(Debug)]
pub enum StructuralError {
DoubleNode(NodeID), MissingInternalEndpoint(NodeID, NodeID, NodeID), NodeMissingNamespace(String),
NodeMultipleNamespace(String),
EdgeMultipleNamespace(String, String, String), ClusterBoundary(String, NodeID), InvalidComponentGraph,
Cycle(NodeID),
DependentRootNode(NodeID, NodeID),
UndeclaredRoot(NodeID),
IncomingAnyEdge(NodeID, NodeID),
OutgoingAllEdge(NodeID, NodeID),
InvalidIdentifierError(String),
}
impl fmt::Display for StructuralError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DoubleNode(id) => write!(f, "Node defined multiple times: {id}"),
Self::MissingInternalEndpoint(start_id, end_id, missing_id) => write!(f, "Node {missing_id} mentioned in edge {start_id} → {end_id} does not exist"),
Self::NodeMissingNamespace(id) => write!(f, "Node lacks a namespace: {id}"),
Self::NodeMultipleNamespace(id) => write!(f, "Node has multiple namespaces: {id}"),
Self::EdgeMultipleNamespace(start_id, end_id, namespaced_id) => write!(f, "Node {namespaced_id} mentioned in edge {start_id} → {end_id} is incorrectly namespaced. There should only be one namespace and it should only be explicit if it is not that of the defining cluster."),
Self::ClusterBoundary(cluster,reference) => write!(f, "Cluster {} refers to non-existent external node {}", cluster, reference),
Self::InvalidComponentGraph => write!(f, "At least one component graph is invalid"),
Self::Cycle(id) => write!(f, "Node {} is involved in a cycle", id),
Self::DependentRootNode(id, start_id) => write!(f, "Node {} is declared as a root and has at least one incoming edge (from {}). Roots should not have incoming edges.", id, start_id),
Self::UndeclaredRoot(id) => write!(f, "Root {} is not declared as a node in the cluster.", id),
Self::IncomingAnyEdge(start_id,end_id) => write!(f, "\"At least one\" type edge from {} to {}. These edges can only connect to other clusters in the \"out\" direction.", start_id, end_id),
Self::OutgoingAllEdge(start_id,end_id) => write!(f, "\"All\" type edge from {} to {}. These edges can only connect to other clusters in the \"in\" direction.", start_id, end_id),
Self::InvalidIdentifierError(identifier) => write!(f, "Invalid identifier {}.", identifier)
}
}
}
impl std::error::Error for StructuralError {}
pub type NodeData = (NodeID, String);
pub type EdgeData = EdgeType;
pub type Graph = petgraph::Graph<NodeData, EdgeData>;