Skip to main content

impactsense_parser/
ir.rs

1use serde::{Deserialize, Serialize};
2
3use crate::schema;
4
5/// Serialized, language-agnostic graph representation that can be written to
6/// disk and later consumed by a separate "graph loader" runtime.
7///
8/// This sits between the Tree-Sitter based parsing layer (which works on
9/// `ParsedFile` + CST in memory) and the Neo4j integration layer (which
10/// creates nodes and relationships in the database).
11#[derive(Debug, Default, Serialize, Deserialize)]
12pub struct ProjectIr {
13    pub files: Vec<FileIr>,
14    pub modules: Vec<ModuleIr>,
15    pub functions: Vec<FunctionIr>,
16    pub api_endpoints: Vec<ApiEndpointIr>,
17    pub external_apis: Vec<ExternalApiIr>,
18    pub edges: Vec<EdgeIr>,
19    /// C# / Java-style types (CRM-3595).
20    #[serde(default)]
21    pub classes: Vec<ClassIr>,
22    /// C# properties (CRM-3595).
23    #[serde(default)]
24    pub properties: Vec<PropertyIr>,
25    /// OTP/custom behaviour contracts (Erlang).
26    #[serde(default)]
27    pub behaviours: Vec<BehaviourIr>,
28    /// Callback contracts declared by behaviours.
29    #[serde(default)]
30    pub callbacks: Vec<CallbackIr>,
31}
32
33impl ProjectIr {
34    pub fn empty() -> Self {
35        Self::default()
36    }
37}
38
39/// File node IR, mirroring `schema::FileNode`.
40#[derive(Debug, Serialize, Deserialize)]
41pub struct FileIr {
42    pub path: String,
43    pub language: String,
44    pub framework: Option<String>,
45    pub project_name: Option<String>,
46}
47
48/// Module node IR, mirroring `schema::ModuleNode`.
49#[derive(Debug, Serialize, Deserialize)]
50pub struct ModuleIr {
51    pub name: String,
52    pub path: String,
53    pub language: String,
54    pub framework: Option<String>,
55    pub project_name: Option<String>,
56    /// Zstd-compressed source (RedCompressor wire format), when compression is enabled.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub code_bytes: Option<Vec<u8>>,
59}
60
61/// Class node IR for languages that map types to `Class` (CRM-3595).
62#[derive(Debug, Serialize, Deserialize)]
63pub struct ClassIr {
64    pub fqn: String,
65    pub name: String,
66    pub path: String,
67    pub language: String,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub project_name: Option<String>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub kind: Option<String>,
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub code_bytes: Option<Vec<u8>>,
74}
75
76/// Property node IR (C#; CRM-3595).
77#[derive(Debug, Serialize, Deserialize)]
78pub struct PropertyIr {
79    pub fqn: String,
80    pub name: String,
81    pub class_fqn: String,
82    pub path: String,
83    pub language: String,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub project_name: Option<String>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub declared_type: Option<String>,
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub code_bytes: Option<Vec<u8>>,
90}
91
92/// Function node IR, mirroring `schema::FunctionNode`.
93#[derive(Debug, Serialize, Deserialize)]
94pub struct FunctionIr {
95    pub name: String,
96    pub fqn: String,
97    pub path: String,
98    pub language: String,
99    pub framework: Option<String>,
100    pub project_name: Option<String>,
101    pub arity: Option<u32>,
102    pub return_type: Option<String>,
103    pub param_count: Option<u32>,
104    pub param_types: Vec<String>,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub code_bytes: Option<Vec<u8>>,
107}
108
109/// ApiEndpoint node IR, mirroring `schema::ApiEndpointNode`.
110#[derive(Debug, Serialize, Deserialize)]
111pub struct ApiEndpointIr {
112    pub methods: Vec<String>,
113    pub path: String,
114    pub protocol: Option<String>,
115    pub framework: Option<String>,
116    pub project_name: Option<String>,
117}
118
119/// ExternalApi node IR, mirroring `schema::ExternalApiNode`.
120#[derive(Debug, Serialize, Deserialize)]
121pub struct ExternalApiIr {
122    pub name: String,
123    pub base_url: Option<String>,
124    pub protocol: Option<String>,
125    pub provider: Option<String>,
126    pub service: Option<String>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub norm_path: Option<String>,
129}
130
131/// Behaviour node IR, mirroring `schema::BehaviourNode`.
132#[derive(Debug, Serialize, Deserialize)]
133pub struct BehaviourIr {
134    pub name: String,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub path: Option<String>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub language: Option<String>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub project_name: Option<String>,
141}
142
143/// Callback node IR, mirroring `schema::CallbackNode`.
144#[derive(Debug, Serialize, Deserialize)]
145pub struct CallbackIr {
146    pub name: String,
147    pub fqn: String,
148    pub arity: u32,
149    pub optional: bool,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub language: Option<String>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub project_name: Option<String>,
154}
155
156/// Edge kinds in the IR, aligned with `edge::RelType`.
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
158pub enum EdgeKind {
159    DeclaresModule,
160    DeclaresFunction,
161    DeclaresClass,
162    DeclaresProperty,
163    DependsOnFile,
164    CallsFunction,
165    HandlesApi,
166    CallsExternalApi,
167    /// Function → Class type reference.
168    UsesClass,
169    /// Class → Class inheritance / interface (C# `base_list`).
170    ClassUsesClass,
171    SameApi,
172    ImplementsBehaviour,
173    DeclaresCallback,
174    ImplementsCallback,
175    DeclaresBehaviour,
176    ExtendsBehaviour,
177    OverridesCallback,
178}
179
180/// A relationship between two nodes in the IR.
181///
182/// We refer to nodes by a stable "key" string rather than by numeric IDs:
183/// - File: `schema::NodeLabel::File` + `path`
184/// - Module: `Module.name` + `Module.path`
185/// - Function: `Function.fqn`
186/// - Class: `Class.fqn`
187/// - Property: `Property.fqn`
188/// - ApiEndpoint: `"{methods.join(\",\")} {path}"`
189/// - Behaviour: `Behaviour.name`
190/// - Callback: `Callback.fqn`
191/// - ExternalApi: `base_url` + `norm_path` or `ExternalApi.name`
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct EdgeIr {
194    pub kind: EdgeKind,
195    /// Label of the source node (e.g. "File", "Module", "Function").
196    pub from_label: String,
197    /// Stable key identifying the source node (see comment above).
198    pub from_key: String,
199    /// Label of the target node.
200    pub to_label: String,
201    /// Stable key identifying the target node.
202    pub to_key: String,
203}
204
205impl EdgeKind {
206    /// Convert this IR edge kind into the runtime `RelType` used when writing
207    /// to Neo4j. This is a small adapter so the loader can reuse the same
208    /// relationship names as the in-process graph builder.
209    pub fn to_rel_type(&self) -> crate::edge::RelType {
210        use crate::edge::RelType;
211        match self {
212            EdgeKind::DeclaresModule => RelType::DeclaresModule,
213            EdgeKind::DeclaresFunction => RelType::DeclaresFunction,
214            EdgeKind::DeclaresClass => RelType::DeclaresClass,
215            EdgeKind::DeclaresProperty => RelType::DeclaresProperty,
216            EdgeKind::DependsOnFile => RelType::DependsOnFile,
217            EdgeKind::CallsFunction => RelType::CallsFunction,
218            EdgeKind::HandlesApi => RelType::HandlesApi,
219            EdgeKind::CallsExternalApi => RelType::CallsExternalApi,
220            EdgeKind::UsesClass => RelType::UsesClass,
221            EdgeKind::ClassUsesClass => RelType::ClassUsesClass,
222            EdgeKind::SameApi => RelType::SameApi,
223            EdgeKind::ImplementsBehaviour => RelType::ImplementsBehaviour,
224            EdgeKind::DeclaresCallback => RelType::DeclaresCallback,
225            EdgeKind::ImplementsCallback => RelType::ImplementsCallback,
226            EdgeKind::DeclaresBehaviour => RelType::DeclaresBehaviour,
227            EdgeKind::ExtendsBehaviour => RelType::ExtendsBehaviour,
228            EdgeKind::OverridesCallback => RelType::OverridesCallback,
229        }
230    }
231}
232
233/// Stable key for an ExternalApi node in the IR.
234pub fn external_api_key(base_url: &str, norm_path: &str) -> String {
235    format!("{base_url}|{norm_path}")
236}
237
238/// Stable key for an ApiEndpoint node in the IR.
239pub fn api_endpoint_key(methods: &[String], path: &str) -> String {
240    format!("{} {}", methods.join(","), path)
241}
242
243/// Stable key for a Module node in the IR.
244pub fn module_key(name: &str, path: &str) -> String {
245    format!("{name}@{path}")
246}
247
248impl From<schema::NodeLabel> for String {
249    fn from(label: schema::NodeLabel) -> Self {
250        label.to_string()
251    }
252}
253
254