Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions napkin-app/src/test_data/graph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"title": "Spring '26 Schema",
"type": "object",
"properties": {
"nodes": {
"Server": {
"type": "ec2",
"properties": {
"id": "string",
"name": "string",
"instanceType": "string",
"region": "string"
},
"ports": {
"inputs": {
"in-env": "data",
"in-network": "network"
},
"outputs": {
"out-network": "network",
"out-data": "data"
}
}
},
"Database": {
"properties": {
"id": "string",
"name": "string",
"engine": {
"type": "string",
"enum": ["mysql", "postgres", "mongodb", "oracle"]
},
"storageSize": "number",
"region": "string"
},
"ports": {
"inputs": {
"in-network": "network"
},
"outputs": {
"out-conn": "data"
}
}
}
},
"edges": {
"ServerToDatabase": {
"source": {
"node": "Server",
"port": "out-data"
},
"target": {
"node": "Database",
"port": "in-network"
},
"type": "data-flow"
}
}
}
}
File renamed without changes.
72 changes: 72 additions & 0 deletions napkin-backend/graph/graph_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graph

import (
"encoding/json"
"fmt"
"testing"
)
Expand Down Expand Up @@ -74,4 +75,75 @@ func TestFromJSON(t *testing.T) {
t.Fatal("expected api -> db edge")
}
fmt.Println(g)
}

func TestIntentGraphWithSpring26Schema(t *testing.T) {
rawJSON := `{
"nodes": [
{
"id": "srv-01",
"type": "Server",
"data": {
"instanceType": "t3.medium",
"region": "us-east-1",
"name": "Production-API",
"ports": {
"inputs": ["in-env", "in-network"],
"outputs": ["out-network", "out-data"]
}
}
},
{
"id": "db-01",
"type": "Database",
"data": {
"engine": "postgres",
"storageSize": 100,
"region": "us-east-1",
"ports": {
"inputs": ["in-network"],
"outputs": ["out-conn"]
}
}
}
],
"edges": [
{
"source": "srv-01",
"target": "db-01"
}
]
}`

ig := NewIntentGraph()

err := ig.FromJSON([]byte(rawJSON))
if err != nil {
t.Fatalf("Failed to parse Spring '26 instance: %v", err)
}

prettyJSON, _ := json.MarshalIndent(ig, "", " ")
fmt.Printf("IntentGraph Structure:\n%s\n", string(prettyJSON))

srvNode, exists := ig.Nodes["srv-01"]
if !exists {
t.Fatal("Server node srv-01 was not found in IntentGraph")
}

if srvNode.Data["instanceType"] != "t3.medium" {
t.Errorf("Expected instanceType t3.medium, got %v", srvNode.Data["instanceType"])
}

if !ig.HasEdge("srv-01", "db-01") {
t.Error("Edge between Server and Database not found")
}

dg, err := ig.ToDirectedGraph()
if err != nil {
t.Fatalf("Failed to translate to DirectedGraph: %v", err)
}

if !dg.HasNode("db-01") {
t.Error("DirectedGraph failed to import the Database node")
}
}
96 changes: 96 additions & 0 deletions napkin-backend/graph/intent_graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package graph

import (
"encoding/json"
"errors"
)

type IntentGraph struct {
Nodes map[NodeID]Node
Edges []Edge
}

func NewIntentGraph() *IntentGraph {
return &IntentGraph{
Nodes: make(map[NodeID]Node),
Edges: []Edge{},
}
}

func (ig *IntentGraph) FromJSON(data []byte) error {
var payload GraphJSON
if err := json.Unmarshal(data, &payload); err != nil {
return err
}

for _, n := range payload.Nodes {
ig.Nodes[n.ID] = n
}
ig.Edges = payload.Edges
return nil
}

func (ig *IntentGraph) ToDirectedGraph() (*DirectedGraph, error) {
dg := NewDirectedGraph()

for _, node := range ig.Nodes {
if err := dg.AddNode(node); err != nil {
return nil, err
}
}

for _, edge := range ig.Edges {
if err := dg.AddEdge(edge); err != nil {
return nil, err
}
}

return dg, nil
}

func (ig *IntentGraph) AddNode(node Node) error {
ig.Nodes[node.ID] = node
return nil
}

func (ig *IntentGraph) AddEdge(edge Edge) error {
ig.Edges = append(ig.Edges, edge)
return nil
}

func (ig *IntentGraph) HasNode(id NodeID) bool {
_, ok := ig.Nodes[id]
return ok
}

func (ig *IntentGraph) HasEdge(from, to NodeID) bool {
for _, e := range ig.Edges {
if e.From == from && e.To == to {
return true
}
}
return false
}

func (ig *IntentGraph) RemoveNode(id NodeID) error {
delete(ig.Nodes, id)
// Remove associated edges
newEdges := []Edge{}
for _, e := range ig.Edges {
if e.From != id && e.To != id {
newEdges = append(newEdges, e)
}
}
ig.Edges = newEdges
return nil
}

func (ig *IntentGraph) RemoveEdge(from, to NodeID) error {
for i, e := range ig.Edges {
if e.From == from && e.To == to {
ig.Edges = append(ig.Edges[:i], ig.Edges[i+1:]...)
return nil
}
}
return errors.New("edge not found")
}
3 changes: 3 additions & 0 deletions napkin-backend/graph/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ type Node struct {
type Edge struct {
From NodeID `json:"source"`
To NodeID `json:"target"`
SourcePort string `json:"sourcePort,omitempty"`
TargetPort string `json:"targetPort,omitempty"`
Type string `json:"type,omitempty"`
}

type GraphJSON struct {
Expand Down
2 changes: 1 addition & 1 deletion napkin-backend/output.tf
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
terraform {
required_providers {
aws = {
version = "~> 5.0"
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Expand Down
60 changes: 60 additions & 0 deletions napkin-backend/test_data/graph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"title": "Spring '26 Schema",
"type": "object",
"properties": {
"nodes": {
"Server": {
"type": "ec2",
"properties": {
"id": "string",
"name": "string",
"instanceType": "string",
"region": "string"
},
"ports": {
"inputs": {
"in-env": "data",
"in-network": "network"
},
"outputs": {
"out-network": "network",
"out-data": "data"
}
}
},
"Database": {
"properties": {
"id": "string",
"name": "string",
"engine": {
"type": "string",
"enum": ["mysql", "postgres", "mongodb", "oracle"]
},
"storageSize": "number",
"region": "string"
},
"ports": {
"inputs": {
"in-network": "network"
},
"outputs": {
"out-conn": "data"
}
}
}
},
"edges": {
"ServerToDatabase": {
"source": {
"node": "Server",
"port": "out-data"
},
"target": {
"node": "Database",
"port": "in-network"
},
"type": "data-flow"
}
}
}
}
Loading