Skip to main content

meta_ast/output/shard/
edge.rs

1//! Shard edge serialization, validation, and restoration.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::graph::{CodeGraph, EdgeKind, NodeData};
8use crate::output::shard::error::ShardError;
9use crate::output::shard::name::stable_node_name;
10
11/// Serialized cross-node edge in a shard file.
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct ShardEdge {
14    pub source_name: String,
15    pub target_name: String,
16    pub kind: ShardEdgeKind,
17    pub confidence: f32,
18    pub flow_kind: Option<ShardFlowKind>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum ShardEdgeKind {
24    Ownership,
25    Import,
26    Reference,
27    Flow,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum ShardFlowKind {
33    DefUse,
34    Argument,
35    Return,
36    FieldAccess,
37}
38
39impl From<EdgeKind> for ShardEdgeKind {
40    fn from(kind: EdgeKind) -> Self {
41        match kind {
42            EdgeKind::Ownership => Self::Ownership,
43            EdgeKind::Import => Self::Import,
44            EdgeKind::Reference => Self::Reference,
45            EdgeKind::Flow => Self::Flow,
46        }
47    }
48}
49
50impl From<ShardEdgeKind> for EdgeKind {
51    fn from(kind: ShardEdgeKind) -> Self {
52        match kind {
53            ShardEdgeKind::Ownership => Self::Ownership,
54            ShardEdgeKind::Import => Self::Import,
55            ShardEdgeKind::Reference => Self::Reference,
56            ShardEdgeKind::Flow => Self::Flow,
57        }
58    }
59}
60
61impl From<crate::model::FlowKind> for ShardFlowKind {
62    fn from(kind: crate::model::FlowKind) -> Self {
63        match kind {
64            crate::model::FlowKind::DefUse => Self::DefUse,
65            crate::model::FlowKind::Argument => Self::Argument,
66            crate::model::FlowKind::Return => Self::Return,
67            crate::model::FlowKind::FieldAccess => Self::FieldAccess,
68        }
69    }
70}
71
72impl From<ShardFlowKind> for crate::model::FlowKind {
73    fn from(kind: ShardFlowKind) -> Self {
74        match kind {
75            ShardFlowKind::DefUse => Self::DefUse,
76            ShardFlowKind::Argument => Self::Argument,
77            ShardFlowKind::Return => Self::Return,
78            ShardFlowKind::FieldAccess => Self::FieldAccess,
79        }
80    }
81}
82
83pub(crate) fn validate_edge(
84    edge: &ShardEdge,
85    line: usize,
86    edge_index: usize,
87) -> Result<(), ShardError> {
88    let valid_confidence = edge.confidence.is_finite() && (0.0..=1.0).contains(&edge.confidence);
89    if !valid_confidence {
90        return Err(ShardError::InvalidEdge {
91            line,
92            edge_index,
93            message: "confidence must be finite and in the range 0.0..=1.0".to_string(),
94        });
95    }
96    if edge.kind == ShardEdgeKind::Flow {
97        return Err(ShardError::InvalidEdge {
98            line,
99            edge_index,
100            message: "schema version 2 does not persist dataflow nodes".to_string(),
101        });
102    }
103    if edge.flow_kind.is_some() {
104        return Err(ShardError::InvalidEdge {
105            line,
106            edge_index,
107            message: "non-flow edges forbid flow_kind".to_string(),
108        });
109    }
110    Ok(())
111}
112
113/// Restore persisted edges after `GraphBuilder::from_extractions` regenerates graph nodes.
114pub fn restore_shard_edges(graph: &mut CodeGraph, edges: &[ShardEdge]) -> Result<(), ShardError> {
115    let endpoint_index = graph
116        .graph()
117        .node_indices()
118        .filter(|index| !matches!(graph.graph()[*index], NodeData::Data(_)))
119        .map(|index| stable_node_name(graph, index).map(|name| (name, index)))
120        .collect::<Result<HashMap<_, _>, ShardError>>()?;
121
122    for (edge_index, edge) in edges.iter().enumerate() {
123        validate_edge(edge, 0, edge_index)?;
124        let source = endpoint_index
125            .get(&edge.source_name)
126            .copied()
127            .ok_or_else(|| ShardError::MissingEndpoint {
128                name: edge.source_name.clone(),
129            })?;
130        let target = endpoint_index
131            .get(&edge.target_name)
132            .copied()
133            .ok_or_else(|| ShardError::MissingEndpoint {
134                name: edge.target_name.clone(),
135            })?;
136        graph.add_edge_normalized_with_flow(
137            source,
138            target,
139            edge.kind.into(),
140            edge.confidence,
141            edge.flow_kind.map(Into::into),
142        );
143    }
144    Ok(())
145}