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.
@@ -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
+ }
@@ -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
+ }