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}
57
58/// Class node IR for languages that map types to `Class` (CRM-3595).
59#[derive(Debug, Serialize, Deserialize)]
60pub struct ClassIr {
61    pub fqn: String,
62    pub name: String,
63    pub path: String,
64    pub language: String,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub project_name: Option<String>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub kind: Option<String>,
69}
70
71/// Property node IR (C#; CRM-3595).
72#[derive(Debug, Serialize, Deserialize)]
73pub struct PropertyIr {
74    pub fqn: String,
75    pub name: String,
76    pub class_fqn: String,
77    pub path: String,
78    pub language: String,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub project_name: Option<String>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub declared_type: Option<String>,
83}
84
85/// Function node IR, mirroring `schema::FunctionNode`.
86#[derive(Debug, Serialize, Deserialize)]
87pub struct FunctionIr {
88    pub name: String,
89    pub fqn: String,
90    pub path: String,
91    pub language: String,
92    pub framework: Option<String>,
93    pub project_name: Option<String>,
94    pub arity: Option<u32>,
95    pub return_type: Option<String>,
96    pub param_count: Option<u32>,
97    pub param_types: Vec<String>,
98}
99
100/// ApiEndpoint node IR, mirroring `schema::ApiEndpointNode`.
101#[derive(Debug, Serialize, Deserialize)]
102pub struct ApiEndpointIr {
103    pub methods: Vec<String>,
104    pub path: String,
105    pub protocol: Option<String>,
106    pub framework: Option<String>,
107    pub project_name: Option<String>,
108}
109
110/// ExternalApi node IR, mirroring `schema::ExternalApiNode`.
111#[derive(Debug, Serialize, Deserialize)]
112pub struct ExternalApiIr {
113    pub name: String,
114    pub base_url: Option<String>,
115    pub protocol: Option<String>,
116    pub provider: Option<String>,
117    pub service: Option<String>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub norm_path: Option<String>,
120}
121
122/// Behaviour node IR, mirroring `schema::BehaviourNode`.
123#[derive(Debug, Serialize, Deserialize)]
124pub struct BehaviourIr {
125    pub name: String,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub path: Option<String>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub language: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub project_name: Option<String>,
132}
133
134/// Callback node IR, mirroring `schema::CallbackNode`.
135#[derive(Debug, Serialize, Deserialize)]
136pub struct CallbackIr {
137    pub name: String,
138    pub fqn: String,
139    pub arity: u32,
140    pub optional: bool,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub language: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub project_name: Option<String>,
145}
146
147/// Edge kinds in the IR, aligned with `edge::RelType`.
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
149pub enum EdgeKind {
150    DeclaresModule,
151    DeclaresFunction,
152    DeclaresClass,
153    DeclaresProperty,
154    DependsOnFile,
155    CallsFunction,
156    HandlesApi,
157    CallsExternalApi,
158    /// Function → Class type reference.
159    UsesClass,
160    /// Class → Class inheritance / interface (C# `base_list`).
161    ClassUsesClass,
162    SameApi,
163    ImplementsBehaviour,
164    DeclaresCallback,
165    ImplementsCallback,
166    DeclaresBehaviour,
167    ExtendsBehaviour,
168    OverridesCallback,
169}
170
171/// A relationship between two nodes in the IR.
172///
173/// We refer to nodes by a stable "key" string rather than by numeric IDs:
174/// - File: `schema::NodeLabel::File` + `path`
175/// - Module: `Module.name` + `Module.path`
176/// - Function: `Function.fqn`
177/// - Class: `Class.fqn`
178/// - Property: `Property.fqn`
179/// - ApiEndpoint: `"{methods.join(\",\")} {path}"`
180/// - Behaviour: `Behaviour.name`
181/// - Callback: `Callback.fqn`
182/// - ExternalApi: `base_url` + `norm_path` or `ExternalApi.name`
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct EdgeIr {
185    pub kind: EdgeKind,
186    /// Label of the source node (e.g. "File", "Module", "Function").
187    pub from_label: String,
188    /// Stable key identifying the source node (see comment above).
189    pub from_key: String,
190    /// Label of the target node.
191    pub to_label: String,
192    /// Stable key identifying the target node.
193    pub to_key: String,
194}
195
196impl EdgeKind {
197    /// Convert this IR edge kind into the runtime `RelType` used when writing
198    /// to Neo4j. This is a small adapter so the loader can reuse the same
199    /// relationship names as the in-process graph builder.
200    pub fn to_rel_type(&self) -> crate::edge::RelType {
201        use crate::edge::RelType;
202        match self {
203            EdgeKind::DeclaresModule => RelType::DeclaresModule,
204            EdgeKind::DeclaresFunction => RelType::DeclaresFunction,
205            EdgeKind::DeclaresClass => RelType::DeclaresClass,
206            EdgeKind::DeclaresProperty => RelType::DeclaresProperty,
207            EdgeKind::DependsOnFile => RelType::DependsOnFile,
208            EdgeKind::CallsFunction => RelType::CallsFunction,
209            EdgeKind::HandlesApi => RelType::HandlesApi,
210            EdgeKind::CallsExternalApi => RelType::CallsExternalApi,
211            EdgeKind::UsesClass => RelType::UsesClass,
212            EdgeKind::ClassUsesClass => RelType::ClassUsesClass,
213            EdgeKind::SameApi => RelType::SameApi,
214            EdgeKind::ImplementsBehaviour => RelType::ImplementsBehaviour,
215            EdgeKind::DeclaresCallback => RelType::DeclaresCallback,
216            EdgeKind::ImplementsCallback => RelType::ImplementsCallback,
217            EdgeKind::DeclaresBehaviour => RelType::DeclaresBehaviour,
218            EdgeKind::ExtendsBehaviour => RelType::ExtendsBehaviour,
219            EdgeKind::OverridesCallback => RelType::OverridesCallback,
220        }
221    }
222}
223
224/// Stable key for an ExternalApi node in the IR.
225pub fn external_api_key(base_url: &str, norm_path: &str) -> String {
226    format!("{base_url}|{norm_path}")
227}
228
229/// Stable key for an ApiEndpoint node in the IR.
230pub fn api_endpoint_key(methods: &[String], path: &str) -> String {
231    format!("{} {}", methods.join(","), path)
232}
233
234/// Stable key for a Module node in the IR.
235pub fn module_key(name: &str, path: &str) -> String {
236    format!("{name}@{path}")
237}
238
239impl From<schema::NodeLabel> for String {
240    fn from(label: schema::NodeLabel) -> Self {
241        label.to_string()
242    }
243}
244
245