siren_client 0.1
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 +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +103 -0
- data/Rakefile +11 -0
- data/bin/siren_cli +6 -0
- data/lib/siren_cli.rb +9 -0
- data/lib/siren_cli/shell.rb +85 -0
- data/lib/siren_cli/version.rb +3 -0
- data/lib/siren_client.rb +17 -0
- data/lib/siren_client/action.rb +47 -0
- data/lib/siren_client/base.rb +32 -0
- data/lib/siren_client/entity.rb +112 -0
- data/lib/siren_client/exceptions.rb +6 -0
- data/lib/siren_client/field.rb +17 -0
- data/lib/siren_client/link.rb +22 -0
- data/lib/siren_client/version.rb +3 -0
- data/siren_client.gemspec +32 -0
- data/spec/helper/live_spec_helper.rb +20 -0
- data/spec/helper/spec_helper.rb +112 -0
- data/spec/live/traverse_spec.rb +101 -0
- data/spec/support/endpoints/concepts.rb +85 -0
- data/spec/support/endpoints/root.rb +101 -0
- data/spec/support/test_server.rb +17 -0
- data/spec/unit/action_spec.rb +86 -0
- data/spec/unit/base_spec.rb +52 -0
- data/spec/unit/entity_spec.rb +179 -0
- data/spec/unit/field_spec.rb +44 -0
- data/spec/unit/link_spec.rb +47 -0
- metadata +240 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module SirenClient
|
2
|
+
class Field
|
3
|
+
attr_reader :payload, :name, :type, :value, :title
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
if data.class != Hash
|
7
|
+
raise ArgumentError, "You must pass in a Hash to SirenClient::Action.new"
|
8
|
+
end
|
9
|
+
@payload = data
|
10
|
+
|
11
|
+
@name = @payload['name'] || ''
|
12
|
+
@type = @payload['type'] || 'text'
|
13
|
+
@value = @payload['value'] || ''
|
14
|
+
@title = @payload['title'] || ''
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SirenClient
|
2
|
+
class Link
|
3
|
+
attr_reader :payload, :rels, :href, :title, :type, :config
|
4
|
+
|
5
|
+
def initialize(data, config={})
|
6
|
+
if data.class != Hash
|
7
|
+
raise ArgumentError, "You must pass in a Hash to SirenClient::Link.new"
|
8
|
+
end
|
9
|
+
@payload = data
|
10
|
+
@config = { format: :json }.merge config
|
11
|
+
|
12
|
+
@rels = @payload['rel'] || []
|
13
|
+
@href = @payload['href'] || ''
|
14
|
+
@title = @payload['title'] || ''
|
15
|
+
@type = @payload['type'] || ''
|
16
|
+
end
|
17
|
+
|
18
|
+
def go
|
19
|
+
Entity.new(self.href, @config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'siren_client/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "siren_client"
|
8
|
+
spec.version = SirenClient::VERSION
|
9
|
+
spec.authors = ["Chason Choate"]
|
10
|
+
spec.email = ["cha55son@gmail.com"]
|
11
|
+
spec.summary = %q{A client to traverse Siren APIs https://github.com/kevinswiber/siren}
|
12
|
+
spec.description = %q{SirenClient provides an ActiveRecord-like syntax to traverse Siren APIs.}
|
13
|
+
spec.homepage = "https://github.com/cha55son/siren_client"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "httparty", "~> 0.13"
|
21
|
+
spec.add_dependency "activesupport", "~> 4.2"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rack"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "sinatra"
|
27
|
+
spec.add_development_dependency "sinatra-simple-auth"
|
28
|
+
spec.add_development_dependency "rspec"
|
29
|
+
spec.add_development_dependency "byebug"
|
30
|
+
spec.add_development_dependency "guard"
|
31
|
+
spec.add_development_dependency "guard-rspec"
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'helper/spec_helper'
|
3
|
+
require 'support/test_server'
|
4
|
+
|
5
|
+
# Start a local rack server to serve up dummy data.
|
6
|
+
server_thread = Thread.new do
|
7
|
+
Rack::Handler::WEBrick.run(
|
8
|
+
TestServer.new,
|
9
|
+
:Port => 9292,
|
10
|
+
:AccessLog => [],
|
11
|
+
:Logger => WEBrick::Log::new("/dev/null", 7)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
sleep(1) # wait a sec for the server to be boot
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.after(:suite) {
|
18
|
+
server_thread.kill
|
19
|
+
}
|
20
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
require 'siren_client'
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
# Ensure we only use `expect` and not `should`.
|
6
|
+
config.expect_with :rspec do |c|
|
7
|
+
c.syntax = :expect
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def siren_body
|
12
|
+
{
|
13
|
+
'class' => ['graphs', 'collection'],
|
14
|
+
'properties' => {
|
15
|
+
'page' => 1,
|
16
|
+
'limit' => 20
|
17
|
+
},
|
18
|
+
'entities' => [
|
19
|
+
{
|
20
|
+
'class' => ['graphs'],
|
21
|
+
'rel' => ['/rels/graphs'],
|
22
|
+
'properties' => {
|
23
|
+
'name' => 'test1'
|
24
|
+
},
|
25
|
+
'entities' => [
|
26
|
+
{
|
27
|
+
'class' => ['messages', 'collection'],
|
28
|
+
'rel' => ['/rels/messages'],
|
29
|
+
'href' => '/graphs/test1/messages'
|
30
|
+
},
|
31
|
+
{
|
32
|
+
'class' => ['concepts', 'collection'],
|
33
|
+
'rel' => ['/rels/concepts'],
|
34
|
+
'href' => '/graphs/test2/concepts'
|
35
|
+
}
|
36
|
+
],
|
37
|
+
'links' => [
|
38
|
+
{
|
39
|
+
'rel' => ['self'],
|
40
|
+
'href' => '/graphs/test1'
|
41
|
+
}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
],
|
45
|
+
'actions' => [
|
46
|
+
{
|
47
|
+
'name' => 'filter_concepts',
|
48
|
+
'method' => 'GET',
|
49
|
+
'href' => '/graphs/test1/concepts',
|
50
|
+
'title' => 'Get an optionally filtered list of Concepts',
|
51
|
+
'type' => 'application/x-www-form-urlencoded',
|
52
|
+
'fields' => [
|
53
|
+
{
|
54
|
+
'name' => 'limit',
|
55
|
+
'title' => 'Max number of results in each page',
|
56
|
+
'type' => 'NUMBER',
|
57
|
+
'required' => false
|
58
|
+
},
|
59
|
+
{
|
60
|
+
'name' => 'page',
|
61
|
+
'title' => 'Page number, starting at 1',
|
62
|
+
'type' => 'NUMBER',
|
63
|
+
'required' => false
|
64
|
+
},
|
65
|
+
{
|
66
|
+
'name' => 'search',
|
67
|
+
'title' => 'Keyword search',
|
68
|
+
'type' => 'TEXT',
|
69
|
+
'required' => true
|
70
|
+
}
|
71
|
+
]
|
72
|
+
},
|
73
|
+
{
|
74
|
+
'name' => 'filter_messages',
|
75
|
+
'method' => 'GET',
|
76
|
+
'href' => '/graphs/test1/messages',
|
77
|
+
'title' => 'Get an optionally filtered list of Messages',
|
78
|
+
'type' => 'application/x-www-form-urlencoded',
|
79
|
+
'fields' => [
|
80
|
+
{
|
81
|
+
'name' => 'limit',
|
82
|
+
'title' => 'Max number of results in each page',
|
83
|
+
'type' => 'NUMBER',
|
84
|
+
'required' => false
|
85
|
+
},
|
86
|
+
{
|
87
|
+
'name' => 'page',
|
88
|
+
'title' => 'Page number, starting at 1',
|
89
|
+
'type' => 'NUMBER',
|
90
|
+
'required' => false
|
91
|
+
},
|
92
|
+
{
|
93
|
+
'name' => 'search',
|
94
|
+
'title' => 'Keyword search',
|
95
|
+
'type' => 'TEXT',
|
96
|
+
'required' => true
|
97
|
+
}
|
98
|
+
]
|
99
|
+
}
|
100
|
+
],
|
101
|
+
'links' => [
|
102
|
+
{
|
103
|
+
'rel' => ['self'],
|
104
|
+
'href' => '/graphs?limit=1&page=1&order_by=name'
|
105
|
+
},
|
106
|
+
{
|
107
|
+
'rel' => ['next'],
|
108
|
+
'href' => '/graphs?limit=1&page=2&order_by=name'
|
109
|
+
}
|
110
|
+
]
|
111
|
+
}
|
112
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'helper/live_spec_helper'
|
2
|
+
|
3
|
+
URL = 'http://localhost:9292'
|
4
|
+
describe HTTParty do
|
5
|
+
it 'will be blocked by basic auth' do
|
6
|
+
expect(HTTParty.get(URL).code).to eq(401)
|
7
|
+
end
|
8
|
+
it 'can access the server' do
|
9
|
+
expect(HTTParty.get(URL, basic_auth: { username: 'admin', password: '1234' }).code).to eq(200)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe SirenClient do
|
14
|
+
context 'when creating an entity' do
|
15
|
+
let (:headers_ent) {
|
16
|
+
SirenClient.get({
|
17
|
+
url: URL,
|
18
|
+
basic_auth: { username: 'admin', password: '1234' },
|
19
|
+
headers: { "Accept" => "application/json" }
|
20
|
+
})
|
21
|
+
}
|
22
|
+
it 'can set HTTP headers' do
|
23
|
+
expect {
|
24
|
+
expect(headers_ent.config[:headers]).to be_a Hash
|
25
|
+
expect(headers_ent.config[:headers]).to eq({
|
26
|
+
"Accept" => "application/json"
|
27
|
+
})
|
28
|
+
}.to_not raise_error
|
29
|
+
end
|
30
|
+
it 'it\'s actions inherit the same config' do
|
31
|
+
expect(headers_ent.filter_concepts_get.config[:headers]).to eq({
|
32
|
+
"Accept" => "application/json"
|
33
|
+
})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
let (:client) {
|
38
|
+
|
39
|
+
SirenClient.get(
|
40
|
+
url: URL,
|
41
|
+
timeout: 2,
|
42
|
+
basic_auth: { username: 'admin', password: '1234' },
|
43
|
+
headers: { "Accept" => "application/json" }
|
44
|
+
)
|
45
|
+
}
|
46
|
+
context 'when accessing the root entity' do
|
47
|
+
it 'to return an entity' do
|
48
|
+
expect(client).to be_a SirenClient::Entity
|
49
|
+
end
|
50
|
+
it 'to access properties' do
|
51
|
+
expect(client.properties['name']).to eq('Test 1')
|
52
|
+
end
|
53
|
+
it 'to not have entities' do
|
54
|
+
expect(client.length).to eq(0)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
context 'when accessing a link' do
|
58
|
+
it 'to return a link' do
|
59
|
+
expect(client.links['concepts']).to be_a SirenClient::Link
|
60
|
+
end
|
61
|
+
it 'to follow the link' do
|
62
|
+
expect(client.concepts).to be_a SirenClient::Entity
|
63
|
+
expect(client.concepts.links['self'].href).to eq(URL + '/concepts')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
context 'when accessing an action with GET' do
|
67
|
+
it 'to return an action' do
|
68
|
+
expect(client.filter_concepts_get).to be_a SirenClient::Action
|
69
|
+
end
|
70
|
+
context 'with .where' do
|
71
|
+
it 'to execute the action' do
|
72
|
+
params = { search: 'obama' }
|
73
|
+
expect(client.filter_concepts_get.where(params)).to be_a SirenClient::Entity
|
74
|
+
expect(client.filter_concepts_get.where(params).length).to eq(1)
|
75
|
+
expect(client.filter_concepts_get.where(params)[0]).to be_a SirenClient::Entity
|
76
|
+
expect(client.filter_concepts_get.where(params)[0].category).to eq('PERSON')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
context 'when accessing an action with POST' do
|
81
|
+
it 'to return an action' do
|
82
|
+
expect(client.filter_concepts_post).to be_a SirenClient::Action
|
83
|
+
end
|
84
|
+
context 'with .where' do
|
85
|
+
it 'to execute the action' do
|
86
|
+
params = { search: 'obama' }
|
87
|
+
expect(client.filter_concepts_post.where(params)).to be_a SirenClient::Entity
|
88
|
+
expect(client.filter_concepts_post.where(params).length).to eq(1)
|
89
|
+
expect(client.filter_concepts_post.where(params)[0]).to be_a SirenClient::Entity
|
90
|
+
expect(client.filter_concepts_post.where(params)[0].category).to eq('PERSON')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
let (:concepts) { SirenClient.get({ url: URL, basic_auth: { username: 'admin', password: '1234' }}).concepts }
|
96
|
+
context 'when accessing an entity' do
|
97
|
+
it 'to return an entity' do
|
98
|
+
expect(concepts).to be_a SirenClient::Entity
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class TestServer < Sinatra::Base
|
2
|
+
CON_1 = <<-EOF
|
3
|
+
{
|
4
|
+
"class":["concepts"],
|
5
|
+
"rel":["/rels/concepts"],
|
6
|
+
"properties":{
|
7
|
+
"text":"barack obama",
|
8
|
+
"category":"PERSON"
|
9
|
+
},
|
10
|
+
"links":[
|
11
|
+
{
|
12
|
+
"rel":["self"],
|
13
|
+
"href":"#{@@url}/concepts/1"
|
14
|
+
}
|
15
|
+
]
|
16
|
+
}
|
17
|
+
EOF
|
18
|
+
CON_2 = <<-EOF
|
19
|
+
{
|
20
|
+
"class":["concepts"],
|
21
|
+
"rel":["/rels/concepts"],
|
22
|
+
"properties":{
|
23
|
+
"text":"tennessee",
|
24
|
+
"category":"LOCATION"
|
25
|
+
},
|
26
|
+
"links":[
|
27
|
+
{
|
28
|
+
"rel":["self"],
|
29
|
+
"href":"#{@@url}/concepts/2"
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
33
|
+
EOF
|
34
|
+
post '/concepts/?' do
|
35
|
+
query = (request.query_string.length > 0 ? '?' : '') + request.query_string
|
36
|
+
<<-EOF
|
37
|
+
{
|
38
|
+
"class":["concepts","collection"],
|
39
|
+
"properties": { "count": 1 },
|
40
|
+
"entities": [#{CON_1}],
|
41
|
+
"links": [
|
42
|
+
{
|
43
|
+
"rel": ["self"],
|
44
|
+
"href": "#{@@url}/concepts#{query}"
|
45
|
+
}
|
46
|
+
]
|
47
|
+
}
|
48
|
+
EOF
|
49
|
+
end
|
50
|
+
get '/concepts/?' do
|
51
|
+
query = (request.query_string.length > 0 ? '?' : '') + request.query_string
|
52
|
+
# search=obama
|
53
|
+
if request.params['search']
|
54
|
+
<<-EOF
|
55
|
+
{
|
56
|
+
"class":["concepts","collection"],
|
57
|
+
"properties": { "count": 1 },
|
58
|
+
"entities": [#{CON_1}],
|
59
|
+
"links": [
|
60
|
+
{
|
61
|
+
"rel": ["self"],
|
62
|
+
"href": "#{@@url}/concepts#{query}"
|
63
|
+
}
|
64
|
+
]
|
65
|
+
}
|
66
|
+
EOF
|
67
|
+
else
|
68
|
+
<<-EOF
|
69
|
+
{
|
70
|
+
"class":["concepts","collection"],
|
71
|
+
"properties":{
|
72
|
+
"count":2
|
73
|
+
},
|
74
|
+
"entities":[#{CON_1}, #{CON_2}],
|
75
|
+
"links":[
|
76
|
+
{
|
77
|
+
"rel":["self"],
|
78
|
+
"href":"#{@@url}/concepts#{query}"
|
79
|
+
}
|
80
|
+
]
|
81
|
+
}
|
82
|
+
EOF
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class TestServer < Sinatra::Base
|
2
|
+
get '/?' do
|
3
|
+
<<-EOF
|
4
|
+
{
|
5
|
+
"properties": {
|
6
|
+
"page": 1,
|
7
|
+
"name": "Test 1"
|
8
|
+
},
|
9
|
+
"links": [
|
10
|
+
{
|
11
|
+
"rel": ["self"],
|
12
|
+
"href":"#{@@url}/"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"rel": ["collection", "concepts"],
|
16
|
+
"href":"#{@@url}/concepts",
|
17
|
+
"title": "Concepts"
|
18
|
+
},
|
19
|
+
{ "rel": ["messages", "collection"],
|
20
|
+
"href": "#{@@url}/messages",
|
21
|
+
"title":"Messages"
|
22
|
+
}
|
23
|
+
],
|
24
|
+
"actions": [
|
25
|
+
{
|
26
|
+
"name":"filter-concepts-get",
|
27
|
+
"method":"GET",
|
28
|
+
"href":"#{@@url}/concepts",
|
29
|
+
"title":"Get an optionally filtered list of Concepts",
|
30
|
+
"type":"application/x-www-form-urlencoded",
|
31
|
+
"fields":[
|
32
|
+
{
|
33
|
+
"name":"limit",
|
34
|
+
"title":"Max number of results in each page",
|
35
|
+
"type":"number"
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"name":"page",
|
39
|
+
"title":"Page number, starting at 1",
|
40
|
+
"type":"number"
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"name":"search",
|
44
|
+
"title":"Keyword search on concept text",
|
45
|
+
"type":"text"
|
46
|
+
}
|
47
|
+
]
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"name":"filter-concepts-post",
|
51
|
+
"method":"POST",
|
52
|
+
"href":"#{@@url}/concepts",
|
53
|
+
"title":"Get an optionally filtered list of Concepts",
|
54
|
+
"type":"application/x-www-form-urlencoded",
|
55
|
+
"fields":[
|
56
|
+
{
|
57
|
+
"name":"limit",
|
58
|
+
"title":"Max number of results in each page",
|
59
|
+
"type":"number"
|
60
|
+
},
|
61
|
+
{
|
62
|
+
"name":"page",
|
63
|
+
"title":"Page number, starting at 1",
|
64
|
+
"type":"number"
|
65
|
+
},
|
66
|
+
{
|
67
|
+
"name":"search",
|
68
|
+
"title":"Keyword search on concept text",
|
69
|
+
"type":"text"
|
70
|
+
}
|
71
|
+
]
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"name":"filter-messages",
|
75
|
+
"method":"GET",
|
76
|
+
"href":"#{@@url}/messages",
|
77
|
+
"title":"Get an optionally filtered list of Messages",
|
78
|
+
"type":"application/x-www-form-urlencoded",
|
79
|
+
"fields":[
|
80
|
+
{
|
81
|
+
"name":"limit",
|
82
|
+
"title":"Max number of results in each page",
|
83
|
+
"type":"number"
|
84
|
+
},
|
85
|
+
{
|
86
|
+
"name":"page",
|
87
|
+
"title":"Page number, starting at 1",
|
88
|
+
"type":"number"
|
89
|
+
},
|
90
|
+
{
|
91
|
+
"name":"search",
|
92
|
+
"title":"Keyword search on message body",
|
93
|
+
"type":"number"
|
94
|
+
}
|
95
|
+
]
|
96
|
+
}
|
97
|
+
]
|
98
|
+
}
|
99
|
+
EOF
|
100
|
+
end
|
101
|
+
end
|