-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgraph.go
158 lines (140 loc) · 3.27 KB
/
graph.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright (c) 2023 Z5Labs and Contributors
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
package rdf
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"io"
rdfpb "github.com/z5labs/rdf/proto"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// Graph represents a set of RDF Triples.
type Graph []*rdfpb.Triple
// Add the given RDF Triple to the graph.
func (g *Graph) Add(t *rdfpb.Triple) *Graph {
*g = append(*g, t)
return g
}
// Triples returns all the returns contained within the graph.
func (g Graph) Triples() []*rdfpb.Triple {
return g
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
//
// Binary format = repeat 8 bytes + n bytes
// 8 bytes = length of RDF Triple protobuf message
// n bytes = RDF Triple protobuf message
func (g Graph) MarshalBinary() ([]byte, error) {
if len(g) == 0 {
return nil, nil
}
var buf bytes.Buffer
err := marshalSequentially(&buf, g)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func marshalSequentially(w io.Writer, g Graph) error {
for _, t := range g {
b, err := proto.Marshal(t)
if err != nil {
return err
}
length := len(b)
err = binary.Write(w, binary.LittleEndian, uint64(length))
if err != nil {
return err
}
n, err := w.Write(b)
if err != nil {
return err
}
if n != length {
return errors.New("failed to write all of rdf triple proto message")
}
}
return nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
//
// Binary format = repeat 8 bytes + n bytes
// 8 bytes = length of RDF Triple protobuf message
// n bytes = RDF Triple protobuf message
func (g *Graph) UnmarshalBinary(data []byte) error {
for {
if len(data) == 0 {
return nil
}
if len(data) < 8 {
return errors.New("malformed rdf triple length")
}
length := binary.LittleEndian.Uint64(data[0:8])
data = data[8:]
if uint64(len(data)) < length {
return errors.New("triple length does not match remaining bytes")
}
var triple rdfpb.Triple
b := data[0:length]
err := proto.Unmarshal(b, &triple)
if err != nil {
return err
}
g.Add(&triple)
data = data[length:]
}
}
type jsonGraph struct {
Triples []json.RawMessage `json:"triples"`
}
// MarshalJSON implements json.Marshaler interface.
//
// CAUTION!! Always prefer MarshalBinary over this.
// This relies in protojson which does not
// define a stable version.
func (g Graph) MarshalJSON() ([]byte, error) {
if len(g) == 0 {
return nil, nil
}
triples := make([]json.RawMessage, len(g))
for i := range g {
b, err := protojson.Marshal(g[i])
if err != nil {
return nil, err
}
triples[i] = b
}
b, err := json.Marshal(jsonGraph{
Triples: triples,
})
if err != nil {
return nil, err
}
return b, nil
}
// UnmarshalJSON implements json.Unmarshaler interface.
//
// CAUTION!! Always prefer UnmarshalBinary over this.
// This relies in protojson which does not
// define a stable version.
func (g *Graph) UnmarshalJSON(data []byte) error {
var jg jsonGraph
err := json.Unmarshal(data, &jg)
if err != nil {
return err
}
for _, b := range jg.Triples {
var triple rdfpb.Triple
err = protojson.Unmarshal(b, &triple)
if err != nil {
return err
}
g.Add(&triple)
}
return nil
}