use std::sync::Arc;
use std::time::Duration;
use frp_plexus::{EdgeId, PortId, Value};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
pub enum EdgeTransform {
PassThrough,
Named(String),
Inline(Arc<dyn Fn(&[Value]) -> Value + Send + Sync>),
Script(String),
}
impl std::fmt::Debug for EdgeTransform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EdgeTransform::PassThrough => write!(f, "EdgeTransform::PassThrough"),
EdgeTransform::Named(name) => write!(f, "EdgeTransform::Named({:?})", name),
EdgeTransform::Inline(_) => write!(f, "EdgeTransform::Inline(<fn>)"),
EdgeTransform::Script(code) => {
write!(f, "EdgeTransform::Script(<{} bytes>)", code.len())
}
}
}
}
impl Serialize for EdgeTransform {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
#[serde(tag = "type", content = "value")]
enum Repr<'a> {
PassThrough,
Named(&'a str),
Inline, Script(&'a str),
}
match self {
EdgeTransform::PassThrough => Repr::PassThrough,
EdgeTransform::Named(n) => Repr::Named(n.as_str()),
EdgeTransform::Inline(_) => Repr::Inline,
EdgeTransform::Script(code) => Repr::Script(code.as_str()),
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for EdgeTransform {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(tag = "type", content = "value")]
enum Repr {
PassThrough,
Named(String),
Inline,
Script(String),
}
Ok(match Repr::deserialize(deserializer)? {
Repr::PassThrough | Repr::Inline => EdgeTransform::PassThrough,
Repr::Named(n) => EdgeTransform::Named(n),
Repr::Script(code) => EdgeTransform::Script(code),
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum EdgeSchedule {
OnChange,
OnTick(Duration),
OnEvent(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperEdge {
pub id: EdgeId,
pub sources: Vec<PortId>,
pub targets: Vec<PortId>,
pub transform: EdgeTransform,
pub schedule: EdgeSchedule,
#[serde(default)]
pub delay: bool,
}
impl HyperEdge {
pub fn new(
id: EdgeId,
sources: Vec<PortId>,
targets: Vec<PortId>,
transform: EdgeTransform,
schedule: EdgeSchedule,
) -> Self {
Self { id, sources, targets, transform, schedule, delay: false }
}
pub fn with_delay(mut self) -> Self {
self.delay = true;
self
}
}
impl frp_loom::memory::HasEdgeId for HyperEdge {
fn edge_id(&self) -> EdgeId {
self.id
}
}
#[cfg(test)]
mod tests {
use frp_plexus::IdGen;
use super::*;
#[test]
fn passthrough_debug() {
let t = EdgeTransform::PassThrough;
assert!(format!("{:?}", t).contains("PassThrough"));
}
#[test]
fn inline_debug_shows_fn() {
let t = EdgeTransform::Inline(Arc::new(|_| Value::Null));
assert!(format!("{:?}", t).contains("<fn>"));
}
#[test]
fn named_debug() {
let t = EdgeTransform::Named("double".into());
assert!(format!("{:?}", t).contains("double"));
}
#[test]
fn hyperedge_construction() {
let ids = IdGen::new();
let e = HyperEdge::new(
ids.next_edge_id(),
vec![ids.next_port_id()],
vec![ids.next_port_id()],
EdgeTransform::PassThrough,
EdgeSchedule::OnChange,
);
assert_eq!(e.sources.len(), 1);
assert_eq!(e.targets.len(), 1);
}
#[test]
fn on_tick_schedule() {
let s = EdgeSchedule::OnTick(Duration::from_millis(100));
assert!(matches!(s, EdgeSchedule::OnTick(_)));
}
#[test]
fn script_debug() {
let t = EdgeTransform::Script("x + 1".to_string());
let s = format!("{:?}", t);
assert!(s.contains("Script"));
assert!(s.contains("bytes"));
}
#[test]
fn script_serde_round_trip() {
let original = EdgeTransform::Script("inputs[0] + 1".to_string());
let json = serde_json::to_string(&original).unwrap();
let restored: EdgeTransform = serde_json::from_str(&json).unwrap();
assert!(
matches!(restored, EdgeTransform::Script(ref s) if s == "inputs[0] + 1"),
"expected Script variant, got {:?}",
restored
);
}
#[test]
fn inline_degrades_to_passthrough_on_serde() {
let original = EdgeTransform::Inline(Arc::new(|_| Value::Null));
let json = serde_json::to_string(&original).unwrap();
let restored: EdgeTransform = serde_json::from_str(&json).unwrap();
assert!(matches!(restored, EdgeTransform::PassThrough));
}
}