Skip to main content

srcgraph_core/
owned.rs

1use petgraph::Graph;
2use std::collections::HashMap;
3
4use crate::{ClassNode, EdgeType, MethodConnectivity};
5
6/// Concrete directed graph loaded from a GraphML file.
7pub type OwnedGraph = Graph<OwnedClassNode, EdgeType>;
8
9/// A class node with all attributes from the GraphML schema.
10///
11/// Required fields (`id`, `name`, `namespace`, `lineCount`, `methodCount`) are always
12/// present. Halstead and semantic-mode fields are optional — they are `None` when the
13/// GraphML was exported in syntax-only mode.
14#[derive(Debug, Clone)]
15pub struct OwnedClassNode {
16    /// Fully-qualified class name used as the GraphML node id.
17    pub id: String,
18    pub name: String,
19    pub namespace: String,
20    pub line_count: u32,
21    pub method_count: u32,
22    pub halstead_eta1: u32,
23    pub halstead_eta2: u32,
24    pub halstead_n1: u32,
25    pub halstead_n2: u32,
26    pub method_connectivity: Option<MethodConnectivity>,
27    // Semantic-mode JSON blobs — stored as raw serde_json::Value for downstream metrics.
28    pub method_fingerprints: Option<serde_json::Value>,
29    pub method_tokens: Option<serde_json::Value>,
30    pub call_sequences: Option<serde_json::Value>,
31    pub cyclomatic_complexity: Option<serde_json::Value>,
32    pub path_conditions: Option<serde_json::Value>,
33    pub invariants: Option<serde_json::Value>,
34    pub error_messages: Option<serde_json::Value>,
35    pub magic_numbers: Option<serde_json::Value>,
36    pub dead_code: Option<serde_json::Value>,
37    pub tenant_branches: Option<serde_json::Value>,
38    pub state_transitions: Option<serde_json::Value>,
39}
40
41impl OwnedClassNode {
42    pub(crate) fn from_data(
43        id: &str,
44        data: &HashMap<String, String>,
45    ) -> Result<Self, crate::graphml::Error> {
46        use crate::graphml::Error;
47
48        let require = |key: &str| -> Result<&str, Error> {
49            data.get(key)
50                .map(String::as_str)
51                .ok_or_else(|| Error::MissingAttr(key.to_owned()))
52        };
53        let require_u32 = |key: &str| -> Result<u32, Error> {
54            require(key)?
55                .parse::<u32>()
56                .map_err(|e| Error::ParseInt { key: key.to_owned(), source: e })
57        };
58        let opt_u32 = |key: &str| -> u32 {
59            data.get(key).and_then(|s| s.parse().ok()).unwrap_or(0)
60        };
61        let opt_json = |key: &str| -> Result<Option<serde_json::Value>, Error> {
62            data.get(key)
63                .map(|s| {
64                    serde_json::from_str(s)
65                        .map_err(|e| Error::Json { key: key.to_owned(), source: e })
66                })
67                .transpose()
68        };
69
70        Ok(OwnedClassNode {
71            id: id.to_owned(),
72            name: require("name")?.to_owned(),
73            namespace: require("namespace")?.to_owned(),
74            line_count: require_u32("lineCount")?,
75            method_count: require_u32("methodCount")?,
76            halstead_eta1: opt_u32("halstead_eta1"),
77            halstead_eta2: opt_u32("halstead_eta2"),
78            halstead_n1: opt_u32("halstead_N1"),
79            halstead_n2: opt_u32("halstead_N2"),
80            method_connectivity: data
81                .get("methodConnectivity")
82                .map(|s| {
83                    serde_json::from_str(s).map_err(|e| Error::Json {
84                        key: "methodConnectivity".to_owned(),
85                        source: e,
86                    })
87                })
88                .transpose()?,
89            method_fingerprints: opt_json("methodFingerprints")?,
90            method_tokens: opt_json("methodTokens")?,
91            call_sequences: opt_json("callSequences")?,
92            cyclomatic_complexity: opt_json("cyclomaticComplexity")?,
93            path_conditions: opt_json("pathConditions")?,
94            invariants: opt_json("invariants")?,
95            error_messages: opt_json("errorMessages")?,
96            magic_numbers: opt_json("magicNumbers")?,
97            dead_code: opt_json("deadCode")?,
98            tenant_branches: opt_json("tenantBranches")?,
99            state_transitions: opt_json("stateTransitions")?,
100        })
101    }
102}
103
104impl ClassNode for OwnedClassNode {
105    fn id(&self) -> &str {
106        &self.id
107    }
108    fn namespace(&self) -> &str {
109        &self.namespace
110    }
111    fn line_count(&self) -> u32 {
112        self.line_count
113    }
114    fn method_count(&self) -> u32 {
115        self.method_count
116    }
117    fn method_connectivity(&self) -> Option<&MethodConnectivity> {
118        self.method_connectivity.as_ref()
119    }
120    fn cyclomatic_complexity(&self) -> Option<&serde_json::Value> {
121        self.cyclomatic_complexity.as_ref()
122    }
123    fn halstead_eta1(&self) -> u32 {
124        self.halstead_eta1
125    }
126    fn halstead_eta2(&self) -> u32 {
127        self.halstead_eta2
128    }
129    fn halstead_n1(&self) -> u32 {
130        self.halstead_n1
131    }
132    fn halstead_n2(&self) -> u32 {
133        self.halstead_n2
134    }
135    fn method_tokens(&self) -> Option<&serde_json::Value> {
136        self.method_tokens.as_ref()
137    }
138    fn method_fingerprints(&self) -> Option<&serde_json::Value> {
139        self.method_fingerprints.as_ref()
140    }
141    fn call_sequences(&self) -> Option<&serde_json::Value> {
142        self.call_sequences.as_ref()
143    }
144}
145