Forge
csharpdeeb25a2
1#nullable enable
2using System.Text.Json;
3
4namespace CascadeIDE.Cockpit.Graph;
5
6/// <summary>Парсинг wire JSON режима <c>subgraph</c> / <c>related</c> в <see cref="GraphDocument"/> (ADR 0067).</summary>
7public static class GraphDocumentJson
8{
9 public static bool TryParse(string json, out GraphDocument? doc, out string? error)
10 {
11 doc = null;
12 error = null;
13 try
14 {
15 using var j = JsonDocument.Parse(json);
16 return TryParseRoot(j.RootElement, out doc, out error);
17 }
18 catch (Exception ex)
19 {
20 error = ex.Message;
21 return false;
22 }
23 }
24
25 /// <summary>Subgraph или related (для уровня file при graph/both).</summary>
26 public static bool TryParseRoot(JsonElement root, out GraphDocument? doc, out string? error)
27 {
28 doc = null;
29 error = null;
30 if (root.TryGetProperty("error", out var errEl))
31 {
32 error = errEl.GetString() ?? "error";
33 return false;
34 }
35
36 if (!root.TryGetProperty("mode", out var modeEl) || modeEl.ValueKind != JsonValueKind.String)
37 {
38 error = "bad_mode";
39 return false;
40 }
41
42 return modeEl.GetString() switch
43 {
44 "subgraph" => TryParseSubgraphRoot(root, out doc, out error),
45 "related" => TryParseRelatedRoot(root, out doc, out error),
46 _ => Fail(out doc, out error, "bad_mode")
47 };
48 }
49
50 private static bool TryParseSubgraphRoot(JsonElement root, out GraphDocument? doc, out string? error)
51 {
52 doc = null;
53 error = null;
54
55 var anchor = root.TryGetProperty("anchor_path", out var ap) && ap.ValueKind == JsonValueKind.String
56 ? ap.GetString() ?? ""
57 : "";
58 if (string.IsNullOrEmpty(anchor))
59 return Fail(out doc, out error, "no_anchor");
60
61 var graphKind = TryParseGraphKind(root);
62 var nodes = ParseSubgraphNodes(root);
63 var edges = ParseSubgraphEdges(root);
64
65 doc = new GraphDocument
66 {
67 AnchorPath = anchor,
68 Kind = graphKind,
69 Nodes = nodes,
70 Edges = edges
71 };
72 return true;
73 }
74
75 private static bool TryParseRelatedRoot(JsonElement root, out GraphDocument? doc, out string? error)
76 {
77 doc = null;
78 error = null;
79
80 var anchor = root.TryGetProperty("anchor_path", out var ap) && ap.ValueKind == JsonValueKind.String
81 ? ap.GetString() ?? ""
82 : "";
83 if (string.IsNullOrEmpty(anchor))
84 return Fail(out doc, out error, "no_anchor");
85
86 if (!root.TryGetProperty("items", out var items) || items.ValueKind != JsonValueKind.Array)
87 return Fail(out doc, out error, "no_items");
88
89 var nodes = new List<GraphNode>
90 {
91 new()
92 {
93 Id = "n0",
94 Path = anchor,
95 Kind = "anchor",
96 Label = Path.GetFileName(anchor)
97 }
98 };
99 var edges = new List<GraphEdge>();
100 var idByPath = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { [anchor] = "n0" };
101 var n = 1;
102
103 foreach (var el in items.EnumerateArray())
104 {
105 var path = el.TryGetProperty("path", out var pEl) ? pEl.GetString() : null;
106 if (string.IsNullOrEmpty(path))
107 continue;
108
109 if (idByPath.ContainsKey(path))
110 continue;
111
112 var id = $"n{n++}";
113 idByPath[path] = id;
114 var relatedKind = el.TryGetProperty("kind", out var kindEl) ? kindEl.GetString() : null;
115 var semanticKind = string.IsNullOrEmpty(relatedKind) ? "related" : relatedKind;
116 var rel = el.TryGetProperty("relative_path", out var rEl) ? rEl.GetString() : null;
117 var rat = el.TryGetProperty("rationale", out var raEl) ? raEl.GetString() : null;
118 nodes.Add(new GraphNode
119 {
120 Id = id,
121 Path = path,
122 Kind = semanticKind,
123 Label = Path.GetFileName(path),
124 RelativePath = rel,
125 Rationale = rat
126 });
127 edges.Add(new GraphEdge
128 {
129 FromId = "n0",
130 ToId = id,
131 Kind = "related_to",
132 RelationKind = relatedKind
133 });
134 }
135
136 doc = new GraphDocument
137 {
138 AnchorPath = anchor,
139 Kind = GraphKind.RelatedFiles,
140 Nodes = nodes,
141 Edges = edges
142 };
143 return true;
144 }
145
146 private static List<GraphNode> ParseSubgraphNodes(JsonElement root)
147 {
148 var nodes = new List<GraphNode>();
149 if (!root.TryGetProperty("nodes", out var nodesEl) || nodesEl.ValueKind != JsonValueKind.Array)
150 return nodes;
151
152 foreach (var el in nodesEl.EnumerateArray())
153 {
154 var id = el.TryGetProperty("id", out var idEl) ? idEl.GetString() ?? "" : "";
155 var path = el.TryGetProperty("path", out var pEl) ? pEl.GetString() ?? "" : "";
156 if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(path))
157 continue;
158 var kind = el.TryGetProperty("kind", out var kEl) ? kEl.GetString() ?? "" : "";
159 var label = el.TryGetProperty("label", out var lEl) ? lEl.GetString() ?? Path.GetFileName(path) : Path.GetFileName(path);
160 var rel = el.TryGetProperty("relative_path", out var rEl) ? rEl.GetString() : null;
161 var rat = el.TryGetProperty("rationale", out var raEl) ? raEl.GetString() : null;
162 int? legendIndex = null;
163 if (el.TryGetProperty("legend_index", out var liEl) && liEl.ValueKind == JsonValueKind.Number
164 && liEl.TryGetInt32(out var li))
165 legendIndex = li;
166 var legendText = el.TryGetProperty("legend_text", out var ltEl) ? ltEl.GetString() : null;
167 int? lineStart = null;
168 int? lineEnd = null;
169 if (el.TryGetProperty("line_start", out var lsEl) && lsEl.ValueKind == JsonValueKind.Number
170 && lsEl.TryGetInt32(out var ls))
171 lineStart = ls;
172 if (el.TryGetProperty("line_end", out var leEl) && leEl.ValueKind == JsonValueKind.Number
173 && leEl.TryGetInt32(out var le))
174 lineEnd = le;
175 int? loopGroupId = null;
176 if (el.TryGetProperty("loop_group", out var lgEl) && lgEl.ValueKind == JsonValueKind.Number
177 && lgEl.TryGetInt32(out var lg))
178 loopGroupId = lg;
179 nodes.Add(new GraphNode
180 {
181 Id = id,
182 Path = path,
183 Kind = kind,
184 Label = string.IsNullOrEmpty(label) ? Path.GetFileName(path) : label,
185 RelativePath = rel,
186 Rationale = rat,
187 LegendIndex = legendIndex,
188 LegendText = legendText,
189 LineStart = lineStart,
190 LineEnd = lineEnd,
191 LoopGroupId = loopGroupId
192 });
193 }
194
195 return nodes;
196 }
197
198 private static List<GraphEdge> ParseSubgraphEdges(JsonElement root)
199 {
200 var edges = new List<GraphEdge>();
201 if (!root.TryGetProperty("edges", out var edgesEl) || edgesEl.ValueKind != JsonValueKind.Array)
202 return edges;
203
204 foreach (var el in edgesEl.EnumerateArray())
205 {
206 var from = el.TryGetProperty("from_id", out var fEl) ? fEl.GetString() ?? "" : "";
207 var to = el.TryGetProperty("to_id", out var tEl) ? tEl.GetString() ?? "" : "";
208 if (string.IsNullOrEmpty(from) || string.IsNullOrEmpty(to))
209 continue;
210 var k = el.TryGetProperty("kind", out var ke) ? ke.GetString() : null;
211 var relationKind = el.TryGetProperty("relation_kind", out var rkeNew) && rkeNew.ValueKind == JsonValueKind.String
212 ? rkeNew.GetString()
213 : el.TryGetProperty("related_kind", out var rke) ? rke.GetString() : null;
214 var provenance = el.TryGetProperty("edge_provenance", out var pe) && pe.ValueKind == JsonValueKind.String
215 ? pe.GetString()
216 : null;
217 edges.Add(new GraphEdge
218 {
219 FromId = from,
220 ToId = to,
221 Kind = k,
222 RelationKind = relationKind,
223 EdgeProvenance = provenance
224 });
225 }
226
227 return edges;
228 }
229
230 private static bool Fail(out GraphDocument? doc, out string? error, string code)
231 {
232 doc = null;
233 error = code;
234 return false;
235 }
236
237 public static GraphKind TryParseGraphKind(JsonElement root)
238 {
239 if (!root.TryGetProperty("graph_kind", out var g) || g.ValueKind != JsonValueKind.String)
240 return GraphKind.Unspecified;
241 var s = g.GetString();
242 if (string.IsNullOrEmpty(s))
243 return GraphKind.Unspecified;
244 if (string.Equals(s, GraphKindWire.CodeIntent, StringComparison.Ordinal)
245 || string.Equals(s, GraphKindWire.CodeIntentLegacy, StringComparison.Ordinal))
246 return GraphKind.CodeIntent;
247 if (string.Equals(s, GraphKindWire.RelatedFiles, StringComparison.Ordinal))
248 return GraphKind.RelatedFiles;
249 if (string.Equals(s, GraphKindWire.RepositoryModuleTree, StringComparison.Ordinal))
250 return GraphKind.RepositoryModuleTree;
251 return GraphKind.Unspecified;
252 }
253}
254
View only · write via MCP/CIDE