a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
136 lines
3.9 KiB
Go
136 lines
3.9 KiB
Go
package resolver
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// mediatrVia is the Meta["via"] tag the C# extractor stamps on a MediatR
|
|
// Send/Publish placeholder.
|
|
const mediatrVia = "mediatr-dispatch"
|
|
|
|
// ResolveMediatRCalls binds .NET MediatR dispatches to their handler's
|
|
// Handle method by request type: `_mediator.Send(new CreateOrder())` →
|
|
// `CreateOrderHandler.Handle` (one handler), and `_bus.Publish(new
|
|
// OrderPlaced())` → every `INotificationHandler<OrderPlaced>` (fan-out).
|
|
// Type-keyed via the handler base list, so edges land at the typed
|
|
// framework tier.
|
|
//
|
|
// Returns the number of caller → handler edges synthesized.
|
|
func ResolveMediatRCalls(g graph.Store) int {
|
|
if g == nil {
|
|
return 0
|
|
}
|
|
// index: kind → request type → handler Handle methods.
|
|
index := map[string]map[string][]*graph.Node{"request": {}, "notification": {}}
|
|
for _, n := range nodesByKindsOrAll(g, graph.KindMethod, graph.KindFunction) {
|
|
if n == nil || n.Meta == nil {
|
|
continue
|
|
}
|
|
reqType, _ := n.Meta["mediatr_request_type"].(string)
|
|
kind, _ := n.Meta["mediatr_kind"].(string)
|
|
if reqType == "" || index[kind] == nil {
|
|
continue
|
|
}
|
|
index[kind][reqType] = append(index[kind][reqType], n)
|
|
}
|
|
if len(index["request"]) == 0 && len(index["notification"]) == 0 {
|
|
return 0
|
|
}
|
|
|
|
resolved := 0
|
|
var reindex []graph.EdgeReindex
|
|
var batch []*graph.Edge
|
|
for e := range g.EdgesByKind(graph.EdgeCalls) {
|
|
if e == nil || e.Meta == nil {
|
|
continue
|
|
}
|
|
if v, _ := e.Meta["via"].(string); v != mediatrVia {
|
|
continue
|
|
}
|
|
reqType, _ := e.Meta["mediatr_request_type"].(string)
|
|
kind, _ := e.Meta["mediatr_kind"].(string)
|
|
if reqType == "" || index[kind] == nil {
|
|
continue
|
|
}
|
|
cands := index[kind][reqType]
|
|
|
|
if kind == "request" {
|
|
// A request has a single handler; never guess on ambiguity.
|
|
target := pickStoreAction(g, e, sameBoundaryCandidates(g, e.From, cands))
|
|
resolved += mediatrRebind(e, target, &reindex)
|
|
continue
|
|
}
|
|
|
|
// A notification fans out to every handler.
|
|
handlers := sameBoundaryCandidates(g, e.From, cands)
|
|
sort.Slice(handlers, func(i, j int) bool { return handlers[i].ID < handlers[j].ID })
|
|
if len(handlers) == 0 {
|
|
resolved += mediatrRebind(e, nil, &reindex)
|
|
continue
|
|
}
|
|
resolved += mediatrRebind(e, handlers[0], &reindex)
|
|
for _, h := range handlers[1:] {
|
|
batch = append(batch, mediatrFanoutEdge(e, h, reqType))
|
|
resolved++
|
|
}
|
|
}
|
|
if len(reindex) > 0 {
|
|
g.ReindexEdges(reindex)
|
|
}
|
|
for _, ne := range batch {
|
|
g.AddEdge(ne)
|
|
}
|
|
return resolved
|
|
}
|
|
|
|
// mediatrRebind points the placeholder e at target (typed tier) or
|
|
// re-orphans it. Returns 1 when it lands on a real handler, else 0.
|
|
func mediatrRebind(e *graph.Edge, target *graph.Node, reindex *[]graph.EdgeReindex) int {
|
|
want := "unresolved::*.Handle"
|
|
if target != nil {
|
|
want = target.ID
|
|
}
|
|
if e.To == want {
|
|
if target != nil {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
oldTo := e.To
|
|
e.To = want
|
|
hit := 0
|
|
if target != nil {
|
|
e.Origin = graph.OriginASTInferred
|
|
e.Confidence = ConfidenceTyped
|
|
e.ConfidenceLabel = graph.ConfidenceLabelFor(graph.EdgeCalls, ConfidenceTyped)
|
|
StampSynthesizedTyped(e, SynthMediatR)
|
|
hit = 1
|
|
} else {
|
|
e.Origin = graph.OriginASTInferred
|
|
e.Confidence = 0
|
|
e.ConfidenceLabel = ""
|
|
UnstampSynthesized(e)
|
|
}
|
|
*reindex = append(*reindex, graph.EdgeReindex{Edge: e, OldTo: oldTo})
|
|
return hit
|
|
}
|
|
|
|
func mediatrFanoutEdge(e *graph.Edge, handler *graph.Node, reqType string) *graph.Edge {
|
|
return &graph.Edge{
|
|
From: e.From, To: handler.ID, Kind: graph.EdgeCalls,
|
|
FilePath: e.FilePath, Line: e.Line,
|
|
Origin: graph.OriginASTInferred,
|
|
Confidence: ConfidenceTyped,
|
|
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeCalls, ConfidenceTyped),
|
|
Meta: map[string]any{
|
|
"via": mediatrVia,
|
|
"mediatr_request_type": reqType,
|
|
"mediatr_kind": "notification",
|
|
MetaSynthesizedBy: SynthMediatR,
|
|
MetaProvenance: ProvenanceFramework,
|
|
},
|
|
}
|
|
}
|