grom_native 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +79 -0
- data/Gopkg.lock +170 -0
- data/Gopkg.toml +49 -0
- data/GromNative.gemspec +35 -0
- data/LICENSE.txt +21 -0
- data/Makefile +40 -0
- data/README.md +71 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/gromnative.go +82 -0
- data/ext/gromnative_test.go +150 -0
- data/ext/net/net.go +63 -0
- data/ext/net/spec/methods_test.go +164 -0
- data/ext/net/spec/net_suite_test.go +31 -0
- data/ext/processor/processor.go +104 -0
- data/ext/processor/spec/methods_test.go +102 -0
- data/ext/processor/spec/processor_suite_test.go +16 -0
- data/ext/types/net.proto +25 -0
- data/ext/types/net/net.pb.go +207 -0
- data/lib/grom_native.rb +110 -0
- data/lib/grom_native/node.rb +105 -0
- data/lib/grom_native/version.rb +3 -0
- metadata +199 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
package spec
|
2
|
+
|
3
|
+
import (
|
4
|
+
"errors"
|
5
|
+
. "github.com/onsi/ginkgo"
|
6
|
+
. "github.com/onsi/gomega"
|
7
|
+
"github.com/ukparliament/gromnative/ext/net"
|
8
|
+
. "github.com/ukparliament/gromnative/ext/types/net"
|
9
|
+
"gopkg.in/jarcoal/httpmock.v1"
|
10
|
+
"net/http"
|
11
|
+
)
|
12
|
+
|
13
|
+
type errReader int
|
14
|
+
|
15
|
+
func (errReader) Read(p []byte) (n int, err error) { return 0, errors.New("test error") }
|
16
|
+
func (errReader) Close() error { return nil }
|
17
|
+
|
18
|
+
var _ = Describe("Net", func() {
|
19
|
+
Describe("Get", func() {
|
20
|
+
Context("with a valid URI", func() {
|
21
|
+
BeforeEach(func() {
|
22
|
+
httpmock.RegisterResponder("GET", "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
23
|
+
httpmock.NewStringResponder(200, `<https://id.parliament.uk/43RHonMf> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://id.parliament.uk/schema/Person> .\n\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/personGivenName> "Diane" .\n\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/personOtherNames> "Julie" .\n\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/personFamilyName> "Abbott" .\n\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/oppositionPersonHasOppositionIncumbency> <https://id.parliament.uk/wE8Hq016> .\n\r<https://id.parliament.uk/wE8Hq016> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://id.parliament.uk/schema/OppositionIncumbency> .\n\r<https://id.parliament.uk/wE8Hq016> <https://id.parliament.uk/schema/incumbencyStartDate> "2016-06-27+01:00"^^<http://www.w3.org/2001/XMLSchema#date> .`))
|
24
|
+
})
|
25
|
+
|
26
|
+
It("makes the expected request", func() {
|
27
|
+
resp, err := net.Get(&GetInput{Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf"})
|
28
|
+
|
29
|
+
Expect(resp).To(Equal(&GetOutput{
|
30
|
+
Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
31
|
+
Body: []byte("<https://id.parliament.uk/43RHonMf> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://id.parliament.uk/schema/Person> .\\n\\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/personGivenName> \"Diane\" .\\n\\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/personOtherNames> \"Julie\" .\\n\\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/personFamilyName> \"Abbott\" .\\n\\r<https://id.parliament.uk/43RHonMf> <https://id.parliament.uk/schema/oppositionPersonHasOppositionIncumbency> <https://id.parliament.uk/wE8Hq016> .\\n\\r<https://id.parliament.uk/wE8Hq016> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://id.parliament.uk/schema/OppositionIncumbency> .\\n\\r<https://id.parliament.uk/wE8Hq016> <https://id.parliament.uk/schema/incumbencyStartDate> \"2016-06-27+01:00\"^^<http://www.w3.org/2001/XMLSchema#date> ."),
|
32
|
+
StatusCode: 200,
|
33
|
+
}))
|
34
|
+
Expect(err).NotTo(HaveOccurred())
|
35
|
+
})
|
36
|
+
|
37
|
+
Context("with headers", func() {
|
38
|
+
It("includes the headers in a request", func() {
|
39
|
+
httpmock.RegisterResponder(
|
40
|
+
"GET",
|
41
|
+
"https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
42
|
+
func(req *http.Request) (*http.Response, error) {
|
43
|
+
httpHeaders := http.Header{}
|
44
|
+
httpHeaders.Add("Foo", "Bar")
|
45
|
+
httpHeaders.Add("Bar", "Baz")
|
46
|
+
|
47
|
+
Expect(req.Header).To(Equal(httpHeaders))
|
48
|
+
|
49
|
+
return httpmock.NewStringResponse(200, "done"), nil
|
50
|
+
},
|
51
|
+
)
|
52
|
+
|
53
|
+
var headers []*GetInput_Header
|
54
|
+
headers = append(headers, &GetInput_Header{Key: "Foo", Value: "Bar"}, &GetInput_Header{Key: "Bar", Value: "Baz"})
|
55
|
+
resp, err := net.Get(&GetInput{Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf", Headers: headers})
|
56
|
+
|
57
|
+
Expect(resp).To(Equal(&GetOutput{
|
58
|
+
Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
59
|
+
Body: []byte("done"),
|
60
|
+
StatusCode: 200,
|
61
|
+
}))
|
62
|
+
Expect(err).NotTo(HaveOccurred())
|
63
|
+
})
|
64
|
+
})
|
65
|
+
|
66
|
+
Context("with an error making the request", func() {
|
67
|
+
BeforeEach(func() {
|
68
|
+
httpmock.RegisterResponder(
|
69
|
+
"GET",
|
70
|
+
"https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
71
|
+
func(req *http.Request) (*http.Response, error) {
|
72
|
+
return httpmock.NewStringResponse(200, "done"), errors.New("There was a problem")
|
73
|
+
},
|
74
|
+
)
|
75
|
+
})
|
76
|
+
|
77
|
+
It("returns the expected response and error", func() {
|
78
|
+
resp, err := net.Get(&GetInput{Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf"})
|
79
|
+
|
80
|
+
Expect(resp).To(Equal(&GetOutput{
|
81
|
+
Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
82
|
+
Error: "Get https://api.parliament.uk/query/person_by_id?person_id=43RHonMf: There was a problem",
|
83
|
+
}))
|
84
|
+
|
85
|
+
Expect(err).To(HaveOccurred())
|
86
|
+
Expect(err.Error()).To(Equal("Get https://api.parliament.uk/query/person_by_id?person_id=43RHonMf: There was a problem"))
|
87
|
+
})
|
88
|
+
})
|
89
|
+
|
90
|
+
Context("with an error reading the response body", func() {
|
91
|
+
BeforeEach(func() {
|
92
|
+
httpmock.RegisterResponder(
|
93
|
+
"GET",
|
94
|
+
"https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
95
|
+
func(req *http.Request) (*http.Response, error) {
|
96
|
+
return &http.Response{StatusCode: 200, Body: errReader(0)}, nil
|
97
|
+
},
|
98
|
+
)
|
99
|
+
})
|
100
|
+
|
101
|
+
It("returns an error", func() {
|
102
|
+
resp, err := net.Get(&GetInput{Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf"})
|
103
|
+
|
104
|
+
Expect(resp).To(Equal(&GetOutput{
|
105
|
+
Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
106
|
+
StatusCode: int32(200),
|
107
|
+
Error: "Error reading body from https://api.parliament.uk/query/person_by_id?person_id=43RHonMf: test error",
|
108
|
+
}))
|
109
|
+
|
110
|
+
Expect(err).To(HaveOccurred())
|
111
|
+
Expect(err.Error()).To(Equal("test error"))
|
112
|
+
})
|
113
|
+
})
|
114
|
+
|
115
|
+
Context("with a non-200 status code", func() {
|
116
|
+
BeforeEach(func() {
|
117
|
+
httpmock.RegisterResponder(
|
118
|
+
"GET",
|
119
|
+
"https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
120
|
+
func(req *http.Request) (*http.Response, error) {
|
121
|
+
return httpmock.NewStringResponse(500, "Error"), nil
|
122
|
+
},
|
123
|
+
)
|
124
|
+
})
|
125
|
+
|
126
|
+
It("returns an error", func() {
|
127
|
+
resp, err := net.Get(&GetInput{Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf"})
|
128
|
+
|
129
|
+
Expect(resp).To(Equal(&GetOutput{
|
130
|
+
Uri: "https://api.parliament.uk/query/person_by_id?person_id=43RHonMf",
|
131
|
+
Body: []byte("Error"),
|
132
|
+
StatusCode: int32(500),
|
133
|
+
Error: "Received 500 status code from https://api.parliament.uk/query/person_by_id?person_id=43RHonMf: Error",
|
134
|
+
}))
|
135
|
+
|
136
|
+
Expect(err).To(HaveOccurred())
|
137
|
+
Expect(err.Error()).To(Equal("Received 500 status code from https://api.parliament.uk/query/person_by_id?person_id=43RHonMf: Error"))
|
138
|
+
})
|
139
|
+
})
|
140
|
+
})
|
141
|
+
|
142
|
+
Context("with an invalid URI", func() {
|
143
|
+
BeforeEach(func() {
|
144
|
+
httpmock.DeactivateAndReset()
|
145
|
+
})
|
146
|
+
|
147
|
+
AfterEach(func() {
|
148
|
+
httpmock.Activate()
|
149
|
+
})
|
150
|
+
|
151
|
+
It("returns the expected values", func() {
|
152
|
+
uri := "some_invalid-value.foo"
|
153
|
+
errorString := "Get some_invalid-value.foo: unsupported protocol scheme \"\""
|
154
|
+
|
155
|
+
resp, err := net.Get(&GetInput{Uri: uri})
|
156
|
+
|
157
|
+
Expect(resp).To(Equal(&GetOutput{Uri: "some_invalid-value.foo", Error: errorString}))
|
158
|
+
|
159
|
+
Expect(err).To(HaveOccurred())
|
160
|
+
Expect(err.Error()).To(Equal(errorString))
|
161
|
+
})
|
162
|
+
})
|
163
|
+
})
|
164
|
+
})
|
@@ -0,0 +1,31 @@
|
|
1
|
+
package spec
|
2
|
+
|
3
|
+
import (
|
4
|
+
"gopkg.in/jarcoal/httpmock.v1"
|
5
|
+
"io/ioutil"
|
6
|
+
"log"
|
7
|
+
"testing"
|
8
|
+
|
9
|
+
. "github.com/onsi/ginkgo"
|
10
|
+
. "github.com/onsi/gomega"
|
11
|
+
)
|
12
|
+
|
13
|
+
func TestNet(t *testing.T) {
|
14
|
+
log.SetOutput(ioutil.Discard)
|
15
|
+
RegisterFailHandler(Fail)
|
16
|
+
RunSpecs(t, "Net Suite")
|
17
|
+
}
|
18
|
+
|
19
|
+
var _ = BeforeSuite(func() {
|
20
|
+
// block all HTTP requests
|
21
|
+
httpmock.Activate()
|
22
|
+
})
|
23
|
+
|
24
|
+
var _ = BeforeEach(func() {
|
25
|
+
// remove any mocks
|
26
|
+
httpmock.Reset()
|
27
|
+
})
|
28
|
+
|
29
|
+
var _ = AfterSuite(func() {
|
30
|
+
httpmock.DeactivateAndReset()
|
31
|
+
})
|
@@ -0,0 +1,104 @@
|
|
1
|
+
package processor
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bytes"
|
5
|
+
"strings"
|
6
|
+
"github.com/wallix/triplestore"
|
7
|
+
"log"
|
8
|
+
)
|
9
|
+
|
10
|
+
type Triple struct {
|
11
|
+
Subject string `json:"subject"`
|
12
|
+
Predicate string `json:"predicate"`
|
13
|
+
Object string `json:"object"`
|
14
|
+
}
|
15
|
+
|
16
|
+
type ProcessorInput struct {
|
17
|
+
Body []byte
|
18
|
+
}
|
19
|
+
|
20
|
+
type ProcessorOutput struct {
|
21
|
+
StatementsBySubject map[string][]Triple
|
22
|
+
EdgesBySubject map[string]map[string][]string
|
23
|
+
Error string
|
24
|
+
}
|
25
|
+
|
26
|
+
func NewTriple(t triplestore.Triple) Triple {
|
27
|
+
object := ""
|
28
|
+
|
29
|
+
bnode, isBnode := t.Object().Bnode()
|
30
|
+
literalObj, isLiteral := t.Object().Literal()
|
31
|
+
resource, _ := t.Object().Resource()
|
32
|
+
|
33
|
+
if isBnode {
|
34
|
+
object = "_:" + bnode
|
35
|
+
} else if isLiteral {
|
36
|
+
var literal string
|
37
|
+
|
38
|
+
if literalObj.Lang() != "" {
|
39
|
+
literal = "\"" + literalObj.Value() + "\"@" + literalObj.Lang()
|
40
|
+
} else {
|
41
|
+
literal = "\"" + literalObj.Value() + "\"^^<" + string(literalObj.Type()) + ">"
|
42
|
+
}
|
43
|
+
|
44
|
+
object = literal
|
45
|
+
} else {
|
46
|
+
object = "<" + resource + ">"
|
47
|
+
}
|
48
|
+
|
49
|
+
return Triple{
|
50
|
+
Subject: t.Subject(),
|
51
|
+
Predicate: t.Predicate(),
|
52
|
+
Object: object,
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
func Process(input *ProcessorInput) (*ProcessorOutput, error) {
|
57
|
+
output := ProcessorOutput{}
|
58
|
+
|
59
|
+
// Used to group all statements under a shared subject
|
60
|
+
statementsBySubject := make(map[string][]Triple)
|
61
|
+
// Used to show how one object, through a predicate, links to one or more object
|
62
|
+
edgesBySubject := make(map[string]map[string][]string)
|
63
|
+
|
64
|
+
log.Println("Decoding")
|
65
|
+
dec := triplestore.NewDatasetDecoder(triplestore.NewLenientNTDecoder, bytes.NewReader(input.Body))
|
66
|
+
tris, err := dec.Decode()
|
67
|
+
if err != nil {
|
68
|
+
log.Printf("Error decoding: %v\n", err)
|
69
|
+
output.Error = err.Error()
|
70
|
+
return &output, err
|
71
|
+
}
|
72
|
+
log.Printf("Decoded %v triples", len(tris))
|
73
|
+
|
74
|
+
for i := 0; i < len(tris); i++ {
|
75
|
+
triple := tris[i]
|
76
|
+
|
77
|
+
subject := triple.Subject()
|
78
|
+
predicate := triple.Predicate()
|
79
|
+
object := triple.Object()
|
80
|
+
|
81
|
+
statementsBySubject[subject] = append(statementsBySubject[subject], NewTriple(triple))
|
82
|
+
|
83
|
+
// decide if this is an edge
|
84
|
+
objectResource, _ := object.Resource()
|
85
|
+
if objectResource != "" && predicate != "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
|
86
|
+
if edgesBySubject[subject] == nil {
|
87
|
+
edgesBySubject[subject] = make(map[string][]string)
|
88
|
+
}
|
89
|
+
|
90
|
+
predicateSlice := strings.Split(predicate, "/")
|
91
|
+
predicateObject := predicateSlice[len(predicateSlice)-1]
|
92
|
+
|
93
|
+
edgesBySubject[subject][predicateObject] = append(edgesBySubject[subject][predicateObject], objectResource)
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
log.Printf("Found %v subjects\n", len(statementsBySubject))
|
98
|
+
|
99
|
+
// Pass our statements and edges back in our response
|
100
|
+
output.StatementsBySubject = statementsBySubject
|
101
|
+
output.EdgesBySubject = edgesBySubject
|
102
|
+
|
103
|
+
return &output, nil
|
104
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
package spec
|
2
|
+
|
3
|
+
import (
|
4
|
+
. "github.com/onsi/ginkgo"
|
5
|
+
. "github.com/onsi/gomega"
|
6
|
+
"github.com/ukparliament/gromnative/ext/processor"
|
7
|
+
"github.com/wallix/triplestore"
|
8
|
+
"io/ioutil"
|
9
|
+
)
|
10
|
+
|
11
|
+
var _ = Describe("Processor", func() {
|
12
|
+
Describe("NetTriple", func() {
|
13
|
+
Context("object is a BNode", func() {
|
14
|
+
It("creates the expected triple", func() {
|
15
|
+
expected := processor.Triple{
|
16
|
+
Subject: "https://id.parliament.uk/12345678",
|
17
|
+
Predicate: "https://id.parliament.uk/shema/PredicateName",
|
18
|
+
Object: "_:node39387803",
|
19
|
+
}
|
20
|
+
|
21
|
+
triple := triplestore.SubjPredBnode("https://id.parliament.uk/12345678", "https://id.parliament.uk/shema/PredicateName", "node39387803")
|
22
|
+
|
23
|
+
result := processor.NewTriple(triple)
|
24
|
+
|
25
|
+
Expect(result).To(Equal(expected))
|
26
|
+
})
|
27
|
+
})
|
28
|
+
|
29
|
+
Context("object is a Literal", func() {
|
30
|
+
It("creates the expected triple", func() {
|
31
|
+
expected := processor.Triple{
|
32
|
+
Subject: "https://id.parliament.uk/12345678",
|
33
|
+
Predicate: "https://id.parliament.uk/shema/PredicateName",
|
34
|
+
Object: "\"12\"^^<xsd:integer>",
|
35
|
+
}
|
36
|
+
|
37
|
+
triple, err := triplestore.SubjPredLit("https://id.parliament.uk/12345678", "https://id.parliament.uk/shema/PredicateName", 12)
|
38
|
+
|
39
|
+
result := processor.NewTriple(triple)
|
40
|
+
|
41
|
+
Expect(result).To(Equal(expected))
|
42
|
+
Expect(err).NotTo(HaveOccurred())
|
43
|
+
})
|
44
|
+
})
|
45
|
+
|
46
|
+
Context("object is a Resource", func() {
|
47
|
+
It("creates the expected triple", func() {
|
48
|
+
expected := processor.Triple{
|
49
|
+
Subject: "https://id.parliament.uk/12345678",
|
50
|
+
Predicate: "https://id.parliament.uk/shema/PredicateName",
|
51
|
+
Object: "<https://id.parliament.uk/23456789>",
|
52
|
+
}
|
53
|
+
|
54
|
+
triple := triplestore.SubjPredRes("https://id.parliament.uk/12345678", "https://id.parliament.uk/shema/PredicateName", "https://id.parliament.uk/23456789")
|
55
|
+
|
56
|
+
result := processor.NewTriple(triple)
|
57
|
+
|
58
|
+
Expect(result).To(Equal(expected))
|
59
|
+
})
|
60
|
+
})
|
61
|
+
})
|
62
|
+
|
63
|
+
Describe("Process", func() {
|
64
|
+
Context("with no edges", func() {
|
65
|
+
It("returns empty edges object", func() {
|
66
|
+
fixture, _ := ioutil.ReadFile("../../../spec/fixtures/no_edges.nt")
|
67
|
+
|
68
|
+
res, err := processor.Process(&processor.ProcessorInput{ Body: fixture })
|
69
|
+
|
70
|
+
Expect(len(res.StatementsBySubject)).To(Equal(1))
|
71
|
+
Expect(len(res.EdgesBySubject)).To(Equal(0))
|
72
|
+
Expect(err).NotTo(HaveOccurred())
|
73
|
+
})
|
74
|
+
})
|
75
|
+
|
76
|
+
Context("with empty data", func() {
|
77
|
+
It("returns empty objects", func() {
|
78
|
+
expected := &processor.ProcessorOutput{
|
79
|
+
StatementsBySubject: make(map[string][]processor.Triple),
|
80
|
+
EdgesBySubject: make(map[string]map[string][]string),
|
81
|
+
}
|
82
|
+
|
83
|
+
res, err := processor.Process(&processor.ProcessorInput{ Body: []byte("") })
|
84
|
+
|
85
|
+
Expect(res).To(Equal(expected))
|
86
|
+
Expect(err).NotTo(HaveOccurred())
|
87
|
+
})
|
88
|
+
})
|
89
|
+
|
90
|
+
Context("with full data", func() {
|
91
|
+
It("returns the expected objects", func() {
|
92
|
+
fixture, _ := ioutil.ReadFile("../../../spec/fixtures/full.nt")
|
93
|
+
|
94
|
+
res, err := processor.Process(&processor.ProcessorInput{ Body: fixture })
|
95
|
+
|
96
|
+
Expect(len(res.StatementsBySubject)).To(Equal(43))
|
97
|
+
Expect(len(res.EdgesBySubject)).To(Equal(26))
|
98
|
+
Expect(err).NotTo(HaveOccurred())
|
99
|
+
})
|
100
|
+
})
|
101
|
+
})
|
102
|
+
})
|
@@ -0,0 +1,16 @@
|
|
1
|
+
package spec
|
2
|
+
|
3
|
+
import (
|
4
|
+
"io/ioutil"
|
5
|
+
"log"
|
6
|
+
"testing"
|
7
|
+
|
8
|
+
. "github.com/onsi/ginkgo"
|
9
|
+
. "github.com/onsi/gomega"
|
10
|
+
)
|
11
|
+
|
12
|
+
func TestProcessor(t *testing.T) {
|
13
|
+
log.SetOutput(ioutil.Discard)
|
14
|
+
RegisterFailHandler(Fail)
|
15
|
+
RunSpecs(t, "Processor Suite")
|
16
|
+
}
|
data/ext/types/net.proto
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
syntax = "proto3";
|
2
|
+
package net;
|
3
|
+
option go_package = "github.com/ukparliament/gromnative/ext/types/net";
|
4
|
+
|
5
|
+
service Net {
|
6
|
+
rpc Get (GetInput) returns (GetOutput);
|
7
|
+
}
|
8
|
+
|
9
|
+
message GetInput {
|
10
|
+
string uri = 1;
|
11
|
+
|
12
|
+
message Header {
|
13
|
+
string key = 1;
|
14
|
+
string value = 2;
|
15
|
+
}
|
16
|
+
|
17
|
+
repeated Header headers = 2;
|
18
|
+
}
|
19
|
+
|
20
|
+
message GetOutput {
|
21
|
+
string uri = 1;
|
22
|
+
bytes body = 2;
|
23
|
+
int32 statusCode = 3;
|
24
|
+
string error = 4;
|
25
|
+
}
|