Skip to main content

typr_core/processes/spg/
model.rs

1#![allow(dead_code)]
2
3use serde::{Deserialize, Serialize};
4
5/// Source location for an SPG node (file + byte offset + 1-based line).
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7pub struct SourceLoc {
8    pub file: String,
9    pub offset: usize,
10    pub line: u32,
11}
12
13impl SourceLoc {
14    /// Compute line number by counting '\n' in the source up to `offset`.
15    pub fn from_offset(file: impl Into<String>, offset: usize, source: &str) -> Self {
16        let line = source[..offset.min(source.len())]
17            .chars()
18            .filter(|&c| c == '\n')
19            .count() as u32
20            + 1;
21        SourceLoc {
22            file: file.into(),
23            offset,
24            line,
25        }
26    }
27}
28
29/// Coarse kind of a documented entity.
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
31#[serde(rename_all = "snake_case")]
32pub enum NodeKind {
33    Function,
34    Alias,
35    TypeDef,
36    Module,
37}
38
39impl NodeKind {
40    pub fn prefix(&self) -> &'static str {
41        match self {
42            NodeKind::Function => "function",
43            NodeKind::Alias => "alias",
44            NodeKind::TypeDef => "type",
45            NodeKind::Module => "module",
46        }
47    }
48}
49
50/// Visibility of a binding.
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
52#[serde(rename_all = "snake_case")]
53pub enum Visibility {
54    Private,
55    Public,
56    Export,
57}
58
59/// Per-kind extra data carried by a node.
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61#[serde(tag = "kind", rename_all = "snake_case")]
62pub enum NodePayload {
63    Function {
64        /// (param_name, type_str) pairs, in declaration order.
65        params: Vec<(String, String)>,
66        returns: String,
67    },
68    Record {
69        /// (field_name, type_str) pairs, sorted alphabetically for snapshot stability.
70        fields: Vec<(String, String)>,
71    },
72    Alias {
73        underlying: String,
74        opaque: bool,
75    },
76    Union {
77        variants: Vec<(String, Option<String>)>,
78    },
79    Module {
80        exports: Vec<String>,
81    },
82    None,
83}
84
85/// A node in the Semantic Package Graph.
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87pub struct Node {
88    /// Stable deterministic id: `<kind>:<module_path>/<name>`
89    pub id: String,
90    pub kind: NodeKind,
91    pub name: String,
92    /// Nesting path of module names (empty for top-level).
93    pub module_path: Vec<String>,
94    pub visibility: Visibility,
95    /// Doc-comment attached by `doc_attach.rs` (Phase 4). `None` until then.
96    pub doc: Option<String>,
97    pub source: Option<SourceLoc>,
98    pub payload: NodePayload,
99}
100
101impl Node {
102    /// Build the canonical `id` from kind, module path, and name.
103    pub fn make_id(kind: &NodeKind, module_path: &[String], name: &str) -> String {
104        if module_path.is_empty() {
105            format!("{}:{}", kind.prefix(), name)
106        } else {
107            format!("{}:{}/{}", kind.prefix(), module_path.join("/"), name)
108        }
109    }
110}
111
112/// Kind of a directed edge in the graph.
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
114#[serde(rename_all = "kebab-case")]
115pub enum EdgeKind {
116    HasField,
117    Returns,
118    ProducesType,
119    ParameterOf,
120    ConsumesType,
121    BelongsToModule,
122    Uses,
123    UsedBy,
124}
125
126/// A directed edge between two nodes.
127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
128pub struct Edge {
129    pub from: String,
130    pub to: String,
131    pub kind: EdgeKind,
132}
133
134/// Root of the Semantic Package Graph (serialises to `spg.json`).
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct Spg {
137    /// JSON-LD `@context` — placeholder until a proper vocabulary is published.
138    #[serde(rename = "@context")]
139    pub context: String,
140    pub package: String,
141    pub version: String,
142    pub nodes: Vec<Node>,
143    pub edges: Vec<Edge>,
144}
145
146impl Spg {
147    pub fn new(package: impl Into<String>, version: impl Into<String>) -> Self {
148        Spg {
149            context: "https://typr-lang.dev/spg/v1/context.jsonld".into(),
150            package: package.into(),
151            version: version.into(),
152            nodes: Vec::new(),
153            edges: Vec::new(),
154        }
155    }
156
157    pub fn add_node(&mut self, node: Node) {
158        self.nodes.push(node);
159    }
160
161    pub fn add_edge(&mut self, edge: Edge) {
162        self.edges.push(edge);
163    }
164}