use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::model::{data_flow_step_dependency, DependencyGraph, PipelineContract};
use crate::plan::{plan, PlanResult};
use crate::validation::validate;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InspectView {
pub id: String,
pub name: Option<String>,
pub version: String,
pub dpcs_version: String,
pub step_count: usize,
pub edge_count: usize,
pub input_count: usize,
pub output_count: usize,
pub contract_reference_count: usize,
pub data_flow_count: usize,
pub control_flow_count: usize,
pub scheduling_count: usize,
pub quality_gate_count: usize,
pub failure_semantics_count: usize,
pub has_execution: bool,
pub has_lineage: bool,
pub valid: bool,
pub planning_refused: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step_order: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub step_ids: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GraphView {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contract_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub entry_points: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exit_points: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub step_ids: Vec<String>,
pub edges: Vec<GraphEdgeView>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step_order: Option<Vec<String>>,
pub planning_refused: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GraphEdgeView {
pub from: String,
pub to: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
}
pub fn inspect_view_from_contract(contract: &PipelineContract) -> InspectView {
let valid = validate(contract).is_valid();
let planned = match plan(contract) {
PlanResult::Ok(plan) => Some(plan.step_order.clone()),
PlanResult::Err(_) => None,
};
InspectView {
id: contract.id.clone(),
name: contract.name.clone(),
version: contract.version.clone(),
dpcs_version: contract.dpcs_version.clone(),
step_count: contract.steps.len(),
edge_count: contract.graph.edges.len(),
input_count: contract.interface.inputs.len(),
output_count: contract.interface.outputs.len(),
contract_reference_count: contract.contract_references.len(),
data_flow_count: contract.data_flow.len(),
control_flow_count: contract.control_flow.len(),
scheduling_count: contract.scheduling.len(),
quality_gate_count: contract.quality_gates.len(),
failure_semantics_count: contract.failure_semantics.len(),
has_execution: contract.execution.is_some(),
has_lineage: contract.lineage.is_some(),
valid,
planning_refused: planned.is_none(),
step_order: planned,
step_ids: contract.steps.iter().map(|s| s.id.clone()).collect(),
}
}
pub fn graph_view_from_contract(contract: &PipelineContract) -> GraphView {
let (step_order, planning_refused) = match plan(contract) {
PlanResult::Ok(plan) => (Some(plan.step_order.clone()), false),
PlanResult::Err(_) => (None, true),
};
GraphView {
contract_id: Some(contract.id.clone()),
entry_points: contract.graph.entry_points.clone(),
exit_points: contract.graph.exit_points.clone(),
step_ids: contract.steps.iter().map(|s| s.id.clone()).collect(),
edges: collect_graph_edges(contract),
step_order,
planning_refused,
}
}
fn collect_graph_edges(contract: &PipelineContract) -> Vec<GraphEdgeView> {
let mut edges = Vec::new();
let mut seen: BTreeSet<(String, String, Option<String>)> = BTreeSet::new();
let mut pairs: BTreeSet<(String, String)> = BTreeSet::new();
let push_edge = |edges: &mut Vec<GraphEdgeView>,
seen: &mut BTreeSet<(String, String, Option<String>)>,
pairs: &mut BTreeSet<(String, String)>,
from: String,
to: String,
kind: Option<String>| {
if from.trim().is_empty() || to.trim().is_empty() {
return;
}
let pair = (from.clone(), to.clone());
let key = (from.clone(), to.clone(), kind.clone());
if seen.insert(key) {
pairs.insert(pair);
edges.push(GraphEdgeView { from, to, kind });
}
};
for e in &contract.graph.edges {
push_edge(
&mut edges,
&mut seen,
&mut pairs,
e.from.clone(),
e.to.clone(),
e.kind.clone(),
);
}
for flow in &contract.control_flow {
let kind = flow
.kind
.clone()
.filter(|k| !k.is_empty())
.or_else(|| Some("controlFlow".to_owned()));
push_edge(
&mut edges,
&mut seen,
&mut pairs,
flow.from.clone(),
flow.to.clone(),
kind,
);
}
for flow in &contract.data_flow {
if let Some((from_step, to_step)) =
data_flow_step_dependency(contract, &flow.from, &flow.to)
{
push_edge(
&mut edges,
&mut seen,
&mut pairs,
from_step,
to_step,
Some("dataFlow".to_owned()),
);
}
}
for (from, to) in DependencyGraph::from_contract(contract).edges() {
if pairs.contains(&(from.clone(), to.clone())) {
continue;
}
push_edge(&mut edges, &mut seen, &mut pairs, from, to, None);
}
edges
}