Skip to content

Commit 36261e5

Browse files
committed
feat(go): add GraphAr info loader and client API
- add YAML dependency and parsing infrastructure - implement info metadata package and loaders
1 parent 81bf375 commit 36261e5

24 files changed

Lines changed: 2451 additions & 5 deletions

go/graphar/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@
1818
module github.com/apache/incubator-graphar/go/graphar
1919

2020
go 1.21
21+
22+
require gopkg.in/yaml.v3 v3.0.1

go/graphar/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

go/graphar/graphar.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,74 @@
1717

1818
package graphar
1919

20-
type Client struct{}
20+
import (
21+
"context"
22+
"fmt"
2123

22-
func NewClient() *Client {
23-
return &Client{}
24+
"github.com/apache/incubator-graphar/go/graphar/info"
25+
)
26+
27+
// FileSystem defines a minimal interface for reading files.
28+
// It matches the requirements for loading GraphAr info files.
29+
type FileSystem interface {
30+
// ReadFile reads the named file and returns the contents.
31+
ReadFile(ctx context.Context, name string) ([]byte, error)
32+
}
33+
34+
// GraphAr is the main entry point for using the GraphAr Go SDK.
35+
type GraphAr interface {
36+
// LoadGraphInfo loads a graph-level Info YAML (plus referenced vertex/edge info files)
37+
// using the configured FileSystem.
38+
LoadGraphInfo(ctx context.Context, path string) (*info.GraphInfo, error)
39+
}
40+
41+
type client struct {
42+
fs FileSystem
43+
}
44+
45+
// option configures the GraphAr instance.
46+
type option func(*client)
47+
48+
// WithFileSystem sets a custom FileSystem for the GraphAr instance.
49+
func WithFileSystem(fs FileSystem) option {
50+
return func(c *client) {
51+
c.fs = fs
52+
}
53+
}
54+
55+
// New creates a new GraphAr instance.
56+
func New(opts ...option) GraphAr {
57+
c := &client{}
58+
for _, opt := range opts {
59+
if opt != nil {
60+
opt(c)
61+
}
62+
}
63+
return c
64+
}
65+
66+
// LoadGraphInfo loads a graph-level Info YAML (plus referenced vertex/edge
67+
// info files) using the configured FileSystem.
68+
func (c *client) LoadGraphInfo(ctx context.Context, path string) (*info.GraphInfo, error) {
69+
var g *info.GraphInfo
70+
var err error
71+
72+
if c.fs != nil {
73+
// If custom FS is provided, we need to load via reader
74+
b, err := c.fs.ReadFile(ctx, path)
75+
if err != nil {
76+
return nil, fmt.Errorf("graphar: read graph info %q from fs: %w", path, err)
77+
}
78+
// We'll need to update info.LoadGraphInfo to accept a loader or use a simplified version
79+
g, err = info.LoadGraphInfoFromBytes(b, path, func(p string) ([]byte, error) {
80+
return c.fs.ReadFile(ctx, p)
81+
})
82+
} else {
83+
g, err = info.LoadGraphInfo(path)
84+
}
85+
86+
if err != nil {
87+
return nil, fmt.Errorf("graphar: load graph info %q: %w", path, err)
88+
}
89+
return g, nil
2490
}

go/graphar/graphar_client_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package graphar
19+
20+
import (
21+
"context"
22+
"path/filepath"
23+
"runtime"
24+
"testing"
25+
)
26+
27+
func getRepoRoot() string {
28+
_, filename, _, _ := runtime.Caller(0)
29+
// current file: go/graphar/graphar_client_test.go
30+
return filepath.Join(filepath.Dir(filename), "..", "..")
31+
}
32+
33+
func TestNewClient(t *testing.T) {
34+
t.Run("default", func(t *testing.T) {
35+
c := New()
36+
if c == nil {
37+
t.Fatalf("expected non-nil client")
38+
}
39+
})
40+
41+
t.Run("with options", func(t *testing.T) {
42+
called := false
43+
opt := func(c *client) {
44+
called = true
45+
}
46+
c := New(opt, nil)
47+
if c == nil {
48+
t.Fatalf("expected non-nil client")
49+
}
50+
if !called {
51+
t.Errorf("expected option to be called")
52+
}
53+
})
54+
}
55+
56+
func TestClientLoadGraphInfoErrors(t *testing.T) {
57+
c := New()
58+
_, err := c.LoadGraphInfo(context.Background(), "non-existent-path.yml")
59+
if err == nil {
60+
t.Errorf("expected error for non-existent path")
61+
}
62+
}
63+
64+
func TestClientLoadGraphInfo(t *testing.T) {
65+
root := getRepoRoot()
66+
graphPath := filepath.Join(root, "testing", "modern_graph", "modern_graph.graph.yml")
67+
68+
c := New()
69+
g, err := c.LoadGraphInfo(context.Background(), graphPath)
70+
if err != nil {
71+
t.Fatalf("LoadGraphInfo failed: %v", err)
72+
}
73+
if g.Name != "modern_graph" {
74+
t.Fatalf("unexpected graph name: %s", g.Name)
75+
}
76+
if !g.HasVertex("person") {
77+
t.Fatalf("expected graph to have vertex person")
78+
}
79+
if !g.HasEdge("person", "knows", "person") {
80+
t.Fatalf("expected graph to have edge person-knows-person")
81+
}
82+
}

go/graphar/graphar_test.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,62 @@
1717

1818
package graphar
1919

20-
import "testing"
20+
import (
21+
"context"
22+
"errors"
23+
"testing"
24+
)
2125

2226
func TestNew(t *testing.T) {
2327
t.Parallel()
2428

25-
c := NewClient()
29+
c := New()
2630
if c == nil {
2731
t.Fatalf("New() returned nil")
2832
}
2933
}
34+
35+
type mockFS struct {
36+
files map[string][]byte
37+
}
38+
39+
func (m *mockFS) ReadFile(ctx context.Context, name string) ([]byte, error) {
40+
if b, ok := m.files[name]; ok {
41+
return b, nil
42+
}
43+
return nil, errors.New("file not found")
44+
}
45+
46+
func TestClientWithFileSystem(t *testing.T) {
47+
fs := &mockFS{
48+
files: map[string][]byte{
49+
"graph.yaml": []byte(`
50+
name: test_graph
51+
prefix: /tmp/
52+
vertices: [v.yaml]
53+
edges: []
54+
version: 0.1.0
55+
`),
56+
"v.yaml": []byte(`
57+
type: person
58+
chunk_size: 100
59+
prefix: person/
60+
property_groups: []
61+
version: 0.1.0
62+
`),
63+
},
64+
}
65+
66+
c := New(WithFileSystem(fs))
67+
g, err := c.LoadGraphInfo(context.Background(), "graph.yaml")
68+
if err != nil {
69+
t.Fatalf("LoadGraphInfo failed: %v", err)
70+
}
71+
72+
if g.Name != "test_graph" {
73+
t.Errorf("expected name test_graph, got %s", g.Name)
74+
}
75+
if len(g.VertexInfos) != 1 {
76+
t.Errorf("expected 1 vertex info, got %d", len(g.VertexInfos))
77+
}
78+
}

0 commit comments

Comments
 (0)