europeana-api 0.3.6 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-style.yml +1 -1
- data/.travis.yml +4 -1
- data/README.md +4 -12
- data/europeana-api.gemspec +1 -1
- data/lib/europeana/api.rb +15 -4
- data/lib/europeana/api/record.rb +26 -80
- data/lib/europeana/api/record/hierarchy.rb +48 -0
- data/lib/europeana/api/record/hierarchy/ancestor_self_siblings.rb +12 -0
- data/lib/europeana/api/record/hierarchy/base.rb +32 -0
- data/lib/europeana/api/record/hierarchy/children.rb +12 -0
- data/lib/europeana/api/record/hierarchy/following_siblings.rb +12 -0
- data/lib/europeana/api/record/hierarchy/parent.rb +12 -0
- data/lib/europeana/api/record/hierarchy/preceding_siblings.rb +15 -0
- data/lib/europeana/api/record/hierarchy/self.rb +12 -0
- data/lib/europeana/api/requestable.rb +104 -0
- data/lib/europeana/api/search.rb +16 -44
- data/lib/europeana/api/version.rb +1 -1
- data/spec/europeana/api/record/hierarchy_spec.rb +15 -0
- data/spec/europeana/api/record_spec.rb +99 -99
- data/spec/spec_helper.rb +1 -1
- data/spec/support/shared_examples/record_request.rb +18 -23
- data/spec/support/webmock.rb +14 -0
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11327d8d43595f2888b4d3f501875fd45894abe5
|
4
|
+
data.tar.gz: 80e3e9ff21ab54e2bedcf70d8245fc7b84b30f50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec133494538ec61c5a45a01d9fb975f635b58b30136a5dd48c5f7c4efd5a3d5545ef36a238873b8ebb16b087628354e630c9903e1fe014cddd856412a1e3f35a
|
7
|
+
data.tar.gz: ca2870267f8f896def561a84ff9b087f3d73463f516c7c34b2080b5ec7da513a18804288c07bcfc6956ac10c9cc5e08df521f7888ca7cc7d417c3f4da841962a
|
data/.ruby-style.yml
CHANGED
@@ -496,7 +496,7 @@ Metrics/CyclomaticComplexity:
|
|
496
496
|
Metrics/LineLength:
|
497
497
|
Description: Limit lines to 80 characters.
|
498
498
|
StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits
|
499
|
-
Enabled:
|
499
|
+
Enabled: false
|
500
500
|
Max: 80
|
501
501
|
AllowURI: true
|
502
502
|
URISchemes:
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Europeana
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/
|
3
|
+
[![Build Status](https://travis-ci.org/europeana/europeana-api-client-ruby.svg?branch=master)](https://travis-ci.org/europeana/europeana-api-client-ruby) [![Coverage Status](https://coveralls.io/repos/europeana/europeana-api-client-ruby/badge.svg?branch=master&service=github)](https://coveralls.io/github/europeana/europeana-api-client-ruby?branch=master) [![security](https://hakiri.io/github/europeana/europeana-api-client-ruby/master.svg)](https://hakiri.io/github/europeana/europeana-api-client-ruby/master) [![Dependency Status](https://gemnasium.com/europeana/europeana-api-client-ruby.svg)](https://gemnasium.com/europeana/europeana-api-client-ruby)
|
4
4
|
|
5
5
|
Ruby client library for the search and retrieval of records from the [Europeana
|
6
6
|
REST API](http://labs.europeana.eu/api/introduction/).
|
@@ -33,7 +33,7 @@ Or install it yourself as:
|
|
33
33
|
|
34
34
|
Authentication is *required* for all requests to the Europeana API.
|
35
35
|
|
36
|
-
Only Basic Authentication (by API key) is supported.
|
36
|
+
Only Basic Authentication (by API key) is supported.
|
37
37
|
|
38
38
|
Sign up for an API key at: http://labs.europeana.eu/api/registration/
|
39
39
|
|
@@ -58,17 +58,9 @@ the search response.
|
|
58
58
|
### Record
|
59
59
|
|
60
60
|
```ruby
|
61
|
-
record = Europeana::API.record('abc/1234') # => { "success" => true, "object" => { ... }, ... }
|
62
|
-
record['object'] # => { "title" => "...", "proxies" => [ ... ], "aggregations" => [ ... ]
|
61
|
+
record = Europeana::API.record('/abc/1234') # => { "success" => true, "object" => { ... }, ... }
|
62
|
+
record['object'] # => { "title" => "...", "proxies" => [ ... ], "aggregations" => [ ... ], ... }
|
63
63
|
```
|
64
64
|
|
65
65
|
See http://labs.europeana.eu/api/record/ for details of the data returned in
|
66
66
|
the record response.
|
67
|
-
|
68
|
-
## Contributing
|
69
|
-
|
70
|
-
1. Fork it
|
71
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
73
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
-
5. Create new Pull Request
|
data/europeana-api.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.required_ruby_version = '>=
|
21
|
+
spec.required_ruby_version = '>= 2.0.0'
|
22
22
|
|
23
23
|
spec.add_dependency 'activesupport', '>= 3.0'
|
24
24
|
spec.add_dependency 'multi_json', '~> 1.0'
|
data/lib/europeana/api.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
require 'active_support/cache'
|
1
2
|
require 'active_support/core_ext/object'
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
4
|
+
require 'active_support/core_ext/hash/slice'
|
2
5
|
require 'active_support/hash_with_indifferent_access'
|
6
|
+
require 'active_support/inflector/methods'
|
3
7
|
require 'europeana/api/version'
|
4
8
|
require 'logger'
|
5
9
|
require 'uri'
|
@@ -8,10 +12,11 @@ module Europeana
|
|
8
12
|
##
|
9
13
|
# Europeana REST API client
|
10
14
|
module API
|
11
|
-
autoload :Errors,
|
12
|
-
autoload :Record,
|
13
|
-
autoload :Request,
|
14
|
-
autoload :
|
15
|
+
autoload :Errors, 'europeana/api/errors'
|
16
|
+
autoload :Record, 'europeana/api/record'
|
17
|
+
autoload :Request, 'europeana/api/request'
|
18
|
+
autoload :Requestable, 'europeana/api/requestable'
|
19
|
+
autoload :Search, 'europeana/api/search'
|
15
20
|
|
16
21
|
class << self
|
17
22
|
##
|
@@ -46,12 +51,18 @@ module Europeana
|
|
46
51
|
# @return [Logger]
|
47
52
|
attr_writer :logger
|
48
53
|
|
54
|
+
attr_accessor :cache_store
|
55
|
+
|
56
|
+
attr_accessor :cache_expires_in
|
57
|
+
|
49
58
|
##
|
50
59
|
# Sets configuration values to their defaults
|
51
60
|
def defaults!
|
52
61
|
self.url = 'http://www.europeana.eu/api/v2'
|
53
62
|
self.max_retries = 5
|
54
63
|
self.retry_delay = 10
|
64
|
+
self.cache_store = ActiveSupport::Cache::NullStore.new
|
65
|
+
self.cache_expires_in = 24.hours
|
55
66
|
end
|
56
67
|
|
57
68
|
##
|
data/lib/europeana/api/record.rb
CHANGED
@@ -5,31 +5,22 @@ module Europeana
|
|
5
5
|
#
|
6
6
|
# @see http://labs.europeana.eu/api/record/
|
7
7
|
class Record
|
8
|
+
autoload :Hierarchy, 'europeana/api/record/hierarchy'
|
9
|
+
|
10
|
+
include Requestable
|
11
|
+
|
8
12
|
# Europeana ID of the record
|
9
|
-
|
13
|
+
attr_reader :id
|
10
14
|
|
11
15
|
# Request parameters to send to the API
|
12
|
-
|
16
|
+
attr_reader :params
|
13
17
|
|
14
18
|
##
|
15
19
|
# @param [String] id Europeana ID of the record
|
16
20
|
# @param [Hash] params Request parameters
|
17
21
|
def initialize(id, params = {})
|
18
|
-
|
19
|
-
|
20
|
-
@hierarchy = HashWithIndifferentAccess.new
|
21
|
-
end
|
22
|
-
|
23
|
-
##
|
24
|
-
# Returns query params with API key added
|
25
|
-
#
|
26
|
-
# @return [Hash]
|
27
|
-
def params_with_authentication
|
28
|
-
return params if params.key?(:wskey) && params[:wskey].present?
|
29
|
-
unless Europeana::API.api_key.present?
|
30
|
-
fail Errors::MissingAPIKeyError
|
31
|
-
end
|
32
|
-
params.merge(wskey: Europeana::API.api_key)
|
22
|
+
@id = id
|
23
|
+
@params = params
|
33
24
|
end
|
34
25
|
|
35
26
|
##
|
@@ -60,92 +51,47 @@ module Europeana
|
|
60
51
|
end
|
61
52
|
|
62
53
|
##
|
63
|
-
# Gets the
|
54
|
+
# Gets the URL for this Record request
|
64
55
|
#
|
65
56
|
# @param [Hash{Symbol => Object}] options
|
66
57
|
# @option options [Boolean] :ld (false)
|
67
58
|
# Request JSON-LD
|
68
|
-
# @return [
|
69
|
-
def
|
70
|
-
|
71
|
-
url
|
72
|
-
|
73
|
-
|
74
|
-
uri
|
59
|
+
# @return [String]
|
60
|
+
def request_url(options = {})
|
61
|
+
options.assert_valid_keys(:ld)
|
62
|
+
(Europeana::API.url + "/record#{@id}.json").tap do |url|
|
63
|
+
url << 'ld' if options[:ld]
|
64
|
+
end
|
75
65
|
end
|
76
66
|
|
67
|
+
alias_method :get, :execute_request
|
68
|
+
|
77
69
|
##
|
78
|
-
#
|
70
|
+
# Examines the `success` and `error` fields of the response for failure
|
79
71
|
#
|
80
|
-
# @param [Hash{Symbol => Object}] options
|
81
|
-
# @option options [Boolean] :ld (false)
|
82
|
-
# Request JSON-LD
|
83
|
-
# @return [Hash]
|
84
|
-
# @raise [Europeana::Errors::ResponseError] if API response could not be
|
85
|
-
# parsed as JSON
|
86
72
|
# @raise [Europeana::Errors::RequestError] if API response has
|
87
73
|
# `success:false`
|
88
74
|
# @raise [Europeana::Errors::RequestError] if API response has 404 status
|
89
75
|
# code
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
fail Errors::RequestError, (body.key?('error') ? body['error'] : response.code)
|
96
|
-
end
|
97
|
-
body
|
98
|
-
rescue JSON::ParserError
|
99
|
-
if response.code.to_i == 404
|
100
|
-
# Handle HTML 404 responses on malformed record ID, emulating API's
|
101
|
-
# JSON response.
|
102
|
-
raise Errors::RequestError, "Invalid record identifier: #{@id}"
|
103
|
-
else
|
104
|
-
raise Errors::ResponseError
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
##
|
109
|
-
# Gets hierarchy data about this and related records from
|
110
|
-
# ancestor-self-siblings.json API method
|
111
|
-
#
|
112
|
-
# @note Proof of concept implementation for demo purposes.
|
113
|
-
# @todo Refactor if functionality to be retained.
|
114
|
-
def hierarchy(*args)
|
115
|
-
args = [:self] if args.blank?
|
116
|
-
|
117
|
-
data = {}
|
118
|
-
args.each do |method|
|
119
|
-
unless @hierarchy.key?(method)
|
120
|
-
@hierarchy[method] = hierarchical_data(method).select do |_k, v|
|
121
|
-
v.is_a?(Enumerable)
|
122
|
-
end
|
76
|
+
# @see Requestable#parse_response
|
77
|
+
def parse_response(response, options = {})
|
78
|
+
super.tap do |body|
|
79
|
+
if (options[:ld] && !(200..299).include?(response.code.to_i)) || (!options[:ld] && !body[:success])
|
80
|
+
fail Errors::RequestError, (body.key?(:error) ? body[:error] : response.code)
|
123
81
|
end
|
124
|
-
data.merge!(@hierarchy[method])
|
125
82
|
end
|
126
|
-
data
|
127
|
-
end
|
128
|
-
|
129
|
-
def hierarchical_data(method = :self)
|
130
|
-
request = Request.new(hierarchical_data_uri(method))
|
131
|
-
response = request.execute
|
132
|
-
body = JSON.parse(response.body)
|
133
|
-
fail Errors::RequestError, body['message'] unless body['success']
|
134
|
-
body
|
135
83
|
rescue JSON::ParserError
|
136
84
|
if response.code.to_i == 404
|
137
85
|
# Handle HTML 404 responses on malformed record ID, emulating API's
|
138
86
|
# JSON response.
|
139
87
|
raise Errors::RequestError, "Invalid record identifier: #{@id}"
|
140
88
|
else
|
141
|
-
raise
|
89
|
+
raise
|
142
90
|
end
|
143
91
|
end
|
144
92
|
|
145
|
-
def
|
146
|
-
|
147
|
-
uri.query = params_with_authentication.to_query
|
148
|
-
uri
|
93
|
+
def hierarchy
|
94
|
+
@hierarchy ||= Hierarchy.new(id)
|
149
95
|
end
|
150
96
|
end
|
151
97
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Europeana
|
2
|
+
module API
|
3
|
+
class Record
|
4
|
+
##
|
5
|
+
# Retrieve record hierarchies over the Europeana API
|
6
|
+
class Hierarchy
|
7
|
+
autoload :AncestorSelfSiblings, 'europeana/api/record/hierarchy/ancestor_self_siblings'
|
8
|
+
autoload :Base, 'europeana/api/record/hierarchy/base'
|
9
|
+
autoload :Children, 'europeana/api/record/hierarchy/children'
|
10
|
+
autoload :FollowingSiblings, 'europeana/api/record/hierarchy/following_siblings'
|
11
|
+
autoload :Parent, 'europeana/api/record/hierarchy/parent'
|
12
|
+
autoload :PrecedingSiblings, 'europeana/api/record/hierarchy/preceding_siblings'
|
13
|
+
autoload :Self, 'europeana/api/record/hierarchy/self'
|
14
|
+
|
15
|
+
def initialize(id)
|
16
|
+
@id = id
|
17
|
+
end
|
18
|
+
|
19
|
+
# bad idea having a method named self, but this is just a minimal
|
20
|
+
# helper class, so going with it for consistency with the API
|
21
|
+
def self(params = {})
|
22
|
+
Self.new(@id, params).execute_request
|
23
|
+
end
|
24
|
+
|
25
|
+
def parent(params = {})
|
26
|
+
Parent.new(@id, params).execute_request
|
27
|
+
end
|
28
|
+
|
29
|
+
def children(params = {})
|
30
|
+
Children.new(@id, params).execute_request
|
31
|
+
end
|
32
|
+
|
33
|
+
def preceding_siblings(params = {})
|
34
|
+
PrecedingSiblings.new(@id, params).execute_request
|
35
|
+
end
|
36
|
+
alias_method :preceeding_siblings, :preceding_siblings
|
37
|
+
|
38
|
+
def following_siblings(params = {})
|
39
|
+
FollowingSiblings.new(@id, params).execute_request
|
40
|
+
end
|
41
|
+
|
42
|
+
def ancestor_self_siblings(params = {})
|
43
|
+
AncestorSelfSiblings.new(@id, params).execute_request
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Europeana
|
2
|
+
module API
|
3
|
+
class Record
|
4
|
+
class Hierarchy
|
5
|
+
##
|
6
|
+
# Base class for common heirarchy API behaviour
|
7
|
+
class Base
|
8
|
+
include Requestable
|
9
|
+
|
10
|
+
attr_accessor :params
|
11
|
+
|
12
|
+
def initialize(id, params = {})
|
13
|
+
@id = id
|
14
|
+
@params = params
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_response(response, options = {})
|
18
|
+
super.slice(:self, :children, :parent, 'preceeding-siblings', 'following-siblings', 'ancestors')
|
19
|
+
end
|
20
|
+
|
21
|
+
def request_url(_options = {})
|
22
|
+
Europeana::API.url + "/record#{@id}/#{api_method}.json"
|
23
|
+
end
|
24
|
+
|
25
|
+
def api_method
|
26
|
+
self.class.to_s.demodulize.underscore.dasherize
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Europeana
|
2
|
+
module API
|
3
|
+
class Record
|
4
|
+
class Hierarchy
|
5
|
+
##
|
6
|
+
# Retrieve record preceding siblings hierarchy data over the Europeana API
|
7
|
+
class PrecedingSiblings < Base
|
8
|
+
def api_method
|
9
|
+
'preceeding-siblings' # mis-spelt on the API
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Europeana
|
2
|
+
module API
|
3
|
+
##
|
4
|
+
# Mixin for classes that need to make Europeana API requests
|
5
|
+
#
|
6
|
+
# Class needs to implement {#request_url}
|
7
|
+
module Requestable
|
8
|
+
##
|
9
|
+
# Request-specific params, to be overriden in including class
|
10
|
+
#
|
11
|
+
# @return [Hash]
|
12
|
+
def params
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Query params with API key added
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
def params_with_authentication
|
21
|
+
return params if params.key?(:wskey) && params[:wskey].present?
|
22
|
+
unless Europeana::API.api_key.present?
|
23
|
+
fail Europeana::API::Errors::MissingAPIKeyError
|
24
|
+
end
|
25
|
+
params.merge(wskey: Europeana::API.api_key)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Execute the API request
|
30
|
+
#
|
31
|
+
# @param options [Hash] Options sent on to {#request_uri} and {#parse_response}
|
32
|
+
# @return (see #parse_response)
|
33
|
+
# @raise [Europeana::Errors::ResponseError] if API response could not be
|
34
|
+
# parsed as JSON
|
35
|
+
def execute_request(options = {})
|
36
|
+
uri = request_uri(options)
|
37
|
+
cache_response_body(uri) do
|
38
|
+
response = Request.new(uri).execute
|
39
|
+
parse_response(response, options)
|
40
|
+
end
|
41
|
+
rescue JSON::ParserError
|
42
|
+
raise Errors::ResponseError
|
43
|
+
end
|
44
|
+
|
45
|
+
def cache_key(uri)
|
46
|
+
"Europeana/API/#{uri}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def cache_response_body(uri)
|
50
|
+
Europeana::API.cache_store.fetch(cache_key(uri), expires_in: Europeana::API.cache_expires_in) do
|
51
|
+
yield
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Parses a JSON response from the API
|
57
|
+
#
|
58
|
+
# @param response (see Net::HTTP#request)
|
59
|
+
# @param options [Hash] Options used by including class's implementation
|
60
|
+
# of this method
|
61
|
+
# @return [HashWithIndifferentAccess] Parsed body of API response
|
62
|
+
def parse_response(response, _options = {})
|
63
|
+
JSON.parse(response.body).with_indifferent_access
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# URI query param string
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
def request_uri_query
|
71
|
+
''.tap do |uri_query|
|
72
|
+
params_with_authentication.each_pair do |name, value|
|
73
|
+
[value].flatten.each do |v|
|
74
|
+
uri_query << '&' unless uri_query.blank?
|
75
|
+
uri_query << CGI.escape(name.to_s) + '=' + CGI.escape(v.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Gets the URI for this request, with any query parameters
|
83
|
+
#
|
84
|
+
# @param [Hash{Symbol => Object}] options passed to {#request_url}
|
85
|
+
# @return [String]
|
86
|
+
def request_uri(options = {})
|
87
|
+
URI.parse(request_url(options)).tap do |uri|
|
88
|
+
uri.query = request_uri_query
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# URL for the request, without query params
|
94
|
+
#
|
95
|
+
# To be implemented by the including class
|
96
|
+
#
|
97
|
+
# @param options [Hash] Options used by implementation
|
98
|
+
# @return [String] Request URL
|
99
|
+
def request_url(_options = {})
|
100
|
+
fail NotImplementedError, "Requestable class #{self.class} does not implement #request_url"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/europeana/api/search.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/hash'
|
2
|
-
|
3
1
|
module Europeana
|
4
2
|
module API
|
5
3
|
##
|
@@ -7,6 +5,8 @@ module Europeana
|
|
7
5
|
class Search
|
8
6
|
autoload :Fields, 'europeana/api/search/fields'
|
9
7
|
|
8
|
+
include Requestable
|
9
|
+
|
10
10
|
# Query params
|
11
11
|
attr_accessor :params
|
12
12
|
|
@@ -17,6 +17,7 @@ module Europeana
|
|
17
17
|
# @param [String] text Text to escape
|
18
18
|
# @return [String] Escaped text
|
19
19
|
def escape(text)
|
20
|
+
fail ArgumentError, "Expected String, got #{text.class}" unless text.is_a?(String)
|
20
21
|
specials = %w<\\ + - & | ! ( ) { } [ ] ^ " ~ * ? : / >
|
21
22
|
specials.each_with_object(text.dup) do |char, unescaped|
|
22
23
|
unescaped.gsub!(char, '\\\\' + char) # prepends *one* backslash
|
@@ -31,56 +32,27 @@ module Europeana
|
|
31
32
|
end
|
32
33
|
|
33
34
|
##
|
34
|
-
#
|
35
|
+
# Base URL for a Search request
|
35
36
|
#
|
36
|
-
# @return [
|
37
|
-
|
38
|
-
|
39
|
-
# @raise [Europeana::Errors::RequestError] if API response has
|
40
|
-
# `success:false`
|
41
|
-
def execute
|
42
|
-
request = Request.new(request_uri)
|
43
|
-
response = request.execute
|
44
|
-
body = JSON.parse(response.body)
|
45
|
-
unless body['success']
|
46
|
-
fail Errors::RequestError, (body.key?('error') ? body['error'] : response.code)
|
47
|
-
end
|
48
|
-
HashWithIndifferentAccess.new(body)
|
49
|
-
rescue JSON::ParserError
|
50
|
-
raise Errors::ResponseError
|
37
|
+
# @return [URI]
|
38
|
+
def request_url(_options = {})
|
39
|
+
Europeana::API.url + '/search.json'
|
51
40
|
end
|
52
41
|
|
53
|
-
|
54
|
-
# Returns query params with API key added
|
55
|
-
#
|
56
|
-
# @return [Hash]
|
57
|
-
def params_with_authentication
|
58
|
-
return params if params.key?(:wskey) && params[:wskey].present?
|
59
|
-
unless Europeana::API.api_key.present?
|
60
|
-
fail Errors::MissingAPIKeyError
|
61
|
-
end
|
62
|
-
params.merge(wskey: Europeana::API.api_key)
|
63
|
-
end
|
42
|
+
alias_method :execute, :execute_request
|
64
43
|
|
65
44
|
##
|
66
|
-
#
|
45
|
+
# Examines the `success` and `error` fields of the response for failure
|
67
46
|
#
|
68
|
-
# @
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
def request_uri_query
|
76
|
-
uri_query = ''
|
77
|
-
params_with_authentication.each_pair do |name, value|
|
78
|
-
[value].flatten.each do |v|
|
79
|
-
uri_query << '&' unless uri_query.blank?
|
80
|
-
uri_query << CGI.escape(name.to_s) + '=' + CGI.escape(v.to_s)
|
47
|
+
# @raise [Europeana::Errors::RequestError] if API response has
|
48
|
+
# `success:false`
|
49
|
+
# @see Requestable#parse_response
|
50
|
+
def parse_response(response, _options = {})
|
51
|
+
super.tap do |body|
|
52
|
+
unless body[:success]
|
53
|
+
fail Errors::RequestError, (body.key?(:error) ? body[:error] : response.code)
|
81
54
|
end
|
82
55
|
end
|
83
|
-
uri_query
|
84
56
|
end
|
85
57
|
end
|
86
58
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
RSpec.describe Europeana::API::Record::Hierarchy do
|
2
|
+
let(:record_id) { '/abc/1234' }
|
3
|
+
let(:params) { { callback: 'doSomething();' } }
|
4
|
+
|
5
|
+
subject { described_class.new(record_id) }
|
6
|
+
|
7
|
+
%w(self parent children).each do |relation|
|
8
|
+
describe "##{relation}" do
|
9
|
+
it "should retrieve #{relation} hierarchy data from the API" do
|
10
|
+
subject.send(relation)
|
11
|
+
expect(a_request(:get, %r{www.europeana.eu/api/v2/record#{record_id}/#{relation}.json})).to have_been_made.once
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,133 +1,133 @@
|
|
1
|
-
|
1
|
+
RSpec.describe Europeana::API::Record do
|
2
|
+
let(:api_key) { 'xyz' }
|
3
|
+
let(:record_id) { '/abc/1234' }
|
4
|
+
let(:params) { { callback: 'doSomething();' } }
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
let(:api_key) { 'xyz' }
|
7
|
-
let(:record_id) { '/abc/1234' }
|
8
|
-
let(:params) { { callback: 'doSomething();' } }
|
6
|
+
before do
|
7
|
+
Europeana::API.api_key = api_key
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
describe '#new' do
|
11
|
+
context 'without record ID' do
|
12
|
+
it 'raises error' do
|
13
|
+
expect { subject }.to raise_error(ArgumentError)
|
12
14
|
end
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
expect { subject }.to raise_error(ArgumentError)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
context "with record ID" do
|
22
|
-
context "without params" do
|
23
|
-
subject { described_class.new(record_id) }
|
24
|
-
|
25
|
-
it "should not raise error" do
|
26
|
-
expect { subject }.to_not raise_error
|
27
|
-
end
|
17
|
+
context 'with record ID' do
|
18
|
+
context 'without params' do
|
19
|
+
subject { described_class.new(record_id) }
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
21
|
+
it 'should not raise error' do
|
22
|
+
expect { subject }.to_not raise_error
|
23
|
+
end
|
33
24
|
|
34
|
-
|
35
|
-
|
25
|
+
it 'sets id attribute' do
|
26
|
+
expect(subject.instance_variable_get(:@id)).to eq(record_id)
|
27
|
+
end
|
28
|
+
end
|
36
29
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
30
|
+
context 'with params' do
|
31
|
+
subject { described_class.new(record_id, params) }
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
33
|
+
it 'should not raise error' do
|
34
|
+
expect { subject }.to_not raise_error
|
45
35
|
end
|
46
|
-
end
|
47
36
|
|
48
|
-
|
49
|
-
|
50
|
-
it "gets id attribute" do
|
51
|
-
expect(subject.id).to eq(subject.instance_variable_get(:@id))
|
37
|
+
it 'sets params attribute' do
|
38
|
+
expect(subject.instance_variable_get(:@params)).to eq(params)
|
52
39
|
end
|
53
40
|
end
|
41
|
+
end
|
42
|
+
end
|
54
43
|
|
55
|
-
|
56
|
-
|
44
|
+
describe '#id' do
|
45
|
+
subject { described_class.new(record_id) }
|
46
|
+
it 'gets id attribute' do
|
47
|
+
expect(subject.id).to eq(subject.instance_variable_get(:@id))
|
48
|
+
end
|
49
|
+
end
|
57
50
|
|
58
|
-
|
59
|
-
|
60
|
-
subject.id = "/xyz/5678"
|
61
|
-
expect(subject.instance_variable_get(:@id)).to eq("/xyz/5678")
|
62
|
-
end
|
63
|
-
end
|
51
|
+
describe '#id=' do
|
52
|
+
subject { described_class.new(record_id) }
|
64
53
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
54
|
+
context 'with valid ID' do
|
55
|
+
it 'sets id attribute' do
|
56
|
+
subject.id = '/xyz/5678'
|
57
|
+
expect(subject.instance_variable_get(:@id)).to eq('/xyz/5678')
|
70
58
|
end
|
59
|
+
end
|
71
60
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
expect(subject.params).to eq(subject.instance_variable_get(:@params))
|
76
|
-
end
|
61
|
+
context 'invalid ID' do
|
62
|
+
it 'raises error' do
|
63
|
+
expect { subject.id = 'invalid' }.to raise_error('Invalid Europeana record ID: "invalid"')
|
77
64
|
end
|
65
|
+
end
|
66
|
+
end
|
78
67
|
|
79
|
-
|
80
|
-
|
68
|
+
describe '#params' do
|
69
|
+
subject { described_class.new(record_id, params) }
|
70
|
+
it 'gets params attribute' do
|
71
|
+
expect(subject.params).to eq(subject.instance_variable_get(:@params))
|
72
|
+
end
|
73
|
+
end
|
81
74
|
|
82
|
-
|
83
|
-
|
84
|
-
subject.params = params
|
85
|
-
expect(subject.instance_variable_get(:@params)).to eq(params)
|
86
|
-
end
|
87
|
-
end
|
75
|
+
describe '#params=' do
|
76
|
+
subject { described_class.new(record_id, {}) }
|
88
77
|
|
89
|
-
|
90
|
-
|
91
|
-
|
78
|
+
context 'valid params' do
|
79
|
+
it 'sets params attribute' do
|
80
|
+
subject.params = params
|
81
|
+
expect(subject.instance_variable_get(:@params)).to eq(params)
|
92
82
|
end
|
83
|
+
end
|
93
84
|
|
94
|
-
|
95
|
-
|
85
|
+
it 'validates param names' do
|
86
|
+
expect { subject.params = { invalid: 'parameter' } }.to raise_error(/Unknown key: :?invalid/)
|
87
|
+
end
|
88
|
+
end
|
96
89
|
|
97
|
-
|
98
|
-
|
99
|
-
expect(subject.params_with_authentication).to eq(params.merge(:wskey => api_key))
|
100
|
-
end
|
101
|
-
end
|
90
|
+
describe '#params_with_authentication' do
|
91
|
+
subject { described_class.new(record_id, params) }
|
102
92
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
expect { subject.params_with_authentication }.to raise_error(Europeana::API::Errors::MissingAPIKeyError)
|
107
|
-
end
|
108
|
-
end
|
93
|
+
context 'with API key' do
|
94
|
+
it 'adds API key to params' do
|
95
|
+
expect(subject.params_with_authentication).to eq(params.merge(wskey: api_key))
|
109
96
|
end
|
97
|
+
end
|
110
98
|
|
111
|
-
|
112
|
-
|
99
|
+
context 'without API key' do
|
100
|
+
it 'raises an error' do
|
101
|
+
Europeana::API.api_key = nil
|
102
|
+
expect { subject.params_with_authentication }.to raise_error(Europeana::API::Errors::MissingAPIKeyError)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
113
106
|
|
114
|
-
|
115
|
-
|
116
|
-
end
|
107
|
+
describe '#request_uri' do
|
108
|
+
subject { described_class.new(record_id, params) }
|
117
109
|
|
118
|
-
|
119
|
-
|
120
|
-
|
110
|
+
it 'returns a URI' do
|
111
|
+
expect(subject.request_uri).to be_a(URI)
|
112
|
+
end
|
121
113
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
end
|
114
|
+
it 'includes the record ID' do
|
115
|
+
expect(subject.request_uri.to_s).to include(record_id)
|
116
|
+
end
|
126
117
|
|
127
|
-
|
128
|
-
|
129
|
-
it_behaves_like "record request"
|
130
|
-
end
|
118
|
+
it 'includes the query params' do
|
119
|
+
expect(subject.request_uri.to_s).to include(params.to_query)
|
131
120
|
end
|
132
121
|
end
|
122
|
+
|
123
|
+
describe '#get' do
|
124
|
+
subject { described_class.new(record_id, params).get }
|
125
|
+
it_behaves_like 'record request'
|
126
|
+
end
|
127
|
+
|
128
|
+
describe '#hierarchy' do
|
129
|
+
let(:record) { described_class.new(record_id, params) }
|
130
|
+
subject { record.hierarchy }
|
131
|
+
it { is_expected.to be_a(Europeana::API::Record::Hierarchy) }
|
132
|
+
end
|
133
133
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,31 +1,26 @@
|
|
1
|
-
shared_examples
|
1
|
+
shared_examples 'record request' do
|
2
|
+
let(:api_record_endpoint) { %r{www.europeana.eu/api/v2/record#{record_id}\.json} }
|
3
|
+
let(:api_key) { 'xyz' }
|
4
|
+
let(:record_id) { '/abc/1234' }
|
5
|
+
let(:params) { { callback: 'doSomething();' } }
|
6
|
+
|
2
7
|
before(:each) do
|
3
|
-
stub_request(:get,
|
4
|
-
|
8
|
+
stub_request(:get, api_record_endpoint).to_return(body: '{"success":true}')
|
9
|
+
Europeana::API.api_key = api_key
|
5
10
|
end
|
6
11
|
|
7
|
-
it_behaves_like
|
8
|
-
|
9
|
-
context "with API key" do
|
10
|
-
before(:each) do
|
11
|
-
Europeana::API.api_key = api_key
|
12
|
-
end
|
13
|
-
|
14
|
-
let(:api_key) { 'xyz' }
|
15
|
-
let(:record_id) { '/abc/1234' }
|
16
|
-
let(:params) { { callback: 'doSomething();' } }
|
12
|
+
it_behaves_like 'API request'
|
17
13
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
it 'sends a Record request to the API' do
|
15
|
+
subject
|
16
|
+
expect(a_request(:get, api_record_endpoint)).to have_been_made.once
|
17
|
+
end
|
22
18
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
19
|
+
context 'when record ID is invalid' do
|
20
|
+
it 'raises RequestError from HTML 404 response' do
|
21
|
+
stub_request(:get, api_record_endpoint).
|
22
|
+
to_return(body: '<html></html>', headers: { 'Content-Type' => 'text/html' }, status: 404)
|
23
|
+
expect { subject }.to raise_error(Europeana::API::Errors::RequestError, "Invalid record identifier: #{record_id}")
|
29
24
|
end
|
30
25
|
end
|
31
26
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.before do
|
3
|
+
Europeana::API.api_key = 'xyz'
|
4
|
+
|
5
|
+
stub_request(:get, %r{www.europeana.eu/api/v2/record/[^/]+/[^/]+/self\.json}).
|
6
|
+
to_return(body: lambda { |_| '{"success":true, "self":{"id":"' + record_id + '", "childrenCount":0, "hasChildren":false}}' })
|
7
|
+
|
8
|
+
stub_request(:get, %r{www.europeana.eu/api/v2/record/[^/]+/[^/]+/parent\.json}).
|
9
|
+
to_return(body: lambda { |_| '{"success":true, "self":{"id":"' + record_id + '", "childrenCount":5, "hasChildren":false}, "parent":{}}' })
|
10
|
+
|
11
|
+
stub_request(:get, %r{www.europeana.eu/api/v2/record/[^/]+/[^/]+/children\.json}).
|
12
|
+
to_return(body: lambda { |_| '{"success":true, "self":{"id":"' + record_id + '", "childrenCount":5, "hasChildren":false}, "children":[]}' })
|
13
|
+
end
|
14
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: europeana-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Doe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -115,11 +115,21 @@ files:
|
|
115
115
|
- lib/europeana/api.rb
|
116
116
|
- lib/europeana/api/errors.rb
|
117
117
|
- lib/europeana/api/record.rb
|
118
|
+
- lib/europeana/api/record/hierarchy.rb
|
119
|
+
- lib/europeana/api/record/hierarchy/ancestor_self_siblings.rb
|
120
|
+
- lib/europeana/api/record/hierarchy/base.rb
|
121
|
+
- lib/europeana/api/record/hierarchy/children.rb
|
122
|
+
- lib/europeana/api/record/hierarchy/following_siblings.rb
|
123
|
+
- lib/europeana/api/record/hierarchy/parent.rb
|
124
|
+
- lib/europeana/api/record/hierarchy/preceding_siblings.rb
|
125
|
+
- lib/europeana/api/record/hierarchy/self.rb
|
118
126
|
- lib/europeana/api/request.rb
|
127
|
+
- lib/europeana/api/requestable.rb
|
119
128
|
- lib/europeana/api/search.rb
|
120
129
|
- lib/europeana/api/search/fields.rb
|
121
130
|
- lib/europeana/api/version.rb
|
122
131
|
- spec/europeana/api/errors_spec.rb
|
132
|
+
- spec/europeana/api/record/hierarchy_spec.rb
|
123
133
|
- spec/europeana/api/record_spec.rb
|
124
134
|
- spec/europeana/api/search_spec.rb
|
125
135
|
- spec/europeana/api_spec.rb
|
@@ -127,6 +137,7 @@ files:
|
|
127
137
|
- spec/support/shared_examples/api_request.rb
|
128
138
|
- spec/support/shared_examples/record_request.rb
|
129
139
|
- spec/support/shared_examples/search_request.rb
|
140
|
+
- spec/support/webmock.rb
|
130
141
|
homepage: https://github.com/europeana/europeana-api-client-ruby
|
131
142
|
licenses:
|
132
143
|
- EUPL V.1.1
|
@@ -139,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
150
|
requirements:
|
140
151
|
- - ">="
|
141
152
|
- !ruby/object:Gem::Version
|
142
|
-
version:
|
153
|
+
version: 2.0.0
|
143
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
155
|
requirements:
|
145
156
|
- - ">="
|
@@ -153,6 +164,7 @@ specification_version: 4
|
|
153
164
|
summary: Ruby client library for the Europeana API
|
154
165
|
test_files:
|
155
166
|
- spec/europeana/api/errors_spec.rb
|
167
|
+
- spec/europeana/api/record/hierarchy_spec.rb
|
156
168
|
- spec/europeana/api/record_spec.rb
|
157
169
|
- spec/europeana/api/search_spec.rb
|
158
170
|
- spec/europeana/api_spec.rb
|
@@ -160,3 +172,4 @@ test_files:
|
|
160
172
|
- spec/support/shared_examples/api_request.rb
|
161
173
|
- spec/support/shared_examples/record_request.rb
|
162
174
|
- spec/support/shared_examples/search_request.rb
|
175
|
+
- spec/support/webmock.rb
|