europeana-api 0.5.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +4 -2
  4. data/Gemfile +1 -8
  5. data/README.md +103 -21
  6. data/Rakefile +2 -1
  7. data/bin/console +21 -0
  8. data/europeana-api.gemspec +12 -1
  9. data/lib/europeana/api.rb +44 -68
  10. data/lib/europeana/api/annotation.rb +20 -0
  11. data/lib/europeana/api/client.rb +51 -0
  12. data/lib/europeana/api/entity.rb +16 -0
  13. data/lib/europeana/api/errors.rb +25 -41
  14. data/lib/europeana/api/faraday_middleware.rb +26 -0
  15. data/lib/europeana/api/faraday_middleware/request/authenticated_request.rb +25 -0
  16. data/lib/europeana/api/faraday_middleware/request/parameter_repetition.rb +25 -0
  17. data/lib/europeana/api/faraday_middleware/response/handle_text.rb +19 -0
  18. data/lib/europeana/api/faraday_middleware/response/parse_json_to_various.rb +52 -0
  19. data/lib/europeana/api/logger.rb +10 -0
  20. data/lib/europeana/api/queue.rb +47 -0
  21. data/lib/europeana/api/record.rb +29 -83
  22. data/lib/europeana/api/request.rb +77 -38
  23. data/lib/europeana/api/resource.rb +30 -0
  24. data/lib/europeana/api/response.rb +47 -0
  25. data/lib/europeana/api/version.rb +2 -1
  26. data/spec/europeana/api/annotation_spec.rb +77 -0
  27. data/spec/europeana/api/client_spec.rb +46 -0
  28. data/spec/europeana/api/entity_spec.rb +6 -0
  29. data/spec/europeana/api/faraday_middleware/request/authenticated_request_spec.rb +22 -0
  30. data/spec/europeana/api/queue_spec.rb +4 -0
  31. data/spec/europeana/api/record_spec.rb +54 -104
  32. data/spec/europeana/api/request_spec.rb +3 -0
  33. data/spec/europeana/api/resource_spec.rb +47 -0
  34. data/spec/europeana/api/response_spec.rb +10 -0
  35. data/spec/europeana/api_spec.rb +34 -84
  36. data/spec/spec_helper.rb +8 -0
  37. data/spec/support/shared_examples/resource_endpoint.rb +11 -0
  38. metadata +158 -34
  39. data/lib/europeana/api/record/hierarchy.rb +0 -48
  40. data/lib/europeana/api/record/hierarchy/ancestor_self_siblings.rb +0 -12
  41. data/lib/europeana/api/record/hierarchy/base.rb +0 -30
  42. data/lib/europeana/api/record/hierarchy/children.rb +0 -12
  43. data/lib/europeana/api/record/hierarchy/following_siblings.rb +0 -12
  44. data/lib/europeana/api/record/hierarchy/parent.rb +0 -12
  45. data/lib/europeana/api/record/hierarchy/preceding_siblings.rb +0 -15
  46. data/lib/europeana/api/record/hierarchy/self.rb +0 -12
  47. data/lib/europeana/api/requestable.rb +0 -118
  48. data/lib/europeana/api/search.rb +0 -64
  49. data/lib/europeana/api/search/fields.rb +0 -112
  50. data/spec/europeana/api/errors_spec.rb +0 -23
  51. data/spec/europeana/api/record/hierarchy_spec.rb +0 -15
  52. data/spec/europeana/api/search_spec.rb +0 -97
  53. data/spec/support/shared_examples/api_request.rb +0 -65
  54. data/spec/support/shared_examples/record_request.rb +0 -26
  55. data/spec/support/shared_examples/search_request.rb +0 -42
  56. data/spec/support/webmock.rb +0 -14
@@ -1,48 +0,0 @@
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
@@ -1,12 +0,0 @@
1
- module Europeana
2
- module API
3
- class Record
4
- class Hierarchy
5
- ##
6
- # Retrieve record full(ish) hierarchy data over the Europeana API
7
- class AncestorSelfSiblings < Base
8
- end
9
- end
10
- end
11
- end
12
- end
@@ -1,30 +0,0 @@
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
- def initialize(id, params = {})
11
- @id = id
12
- self.request_params = params
13
- end
14
-
15
- def parse_response(response, options = {})
16
- super.slice(:self, :children, :parent, 'preceding-siblings', 'preceeding-siblings', 'following-siblings', 'ancestors')
17
- end
18
-
19
- def request_url(_options = {})
20
- api_url + "/record#{@id}/#{api_method}.json"
21
- end
22
-
23
- def api_method
24
- self.class.to_s.demodulize.underscore.dasherize
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,12 +0,0 @@
1
- module Europeana
2
- module API
3
- class Record
4
- class Hierarchy
5
- ##
6
- # Retrieve record children hierarchy data over the Europeana API
7
- class Children < Base
8
- end
9
- end
10
- end
11
- end
12
- end
@@ -1,12 +0,0 @@
1
- module Europeana
2
- module API
3
- class Record
4
- class Hierarchy
5
- ##
6
- # Retrieve record following siblings hierarchy data over the Europeana API
7
- class FollowingSiblings < Base
8
- end
9
- end
10
- end
11
- end
12
- end
@@ -1,12 +0,0 @@
1
- module Europeana
2
- module API
3
- class Record
4
- class Hierarchy
5
- ##
6
- # Retrieve record parent hierarchy data over the Europeana API
7
- class Parent < Base
8
- end
9
- end
10
- end
11
- end
12
- end
@@ -1,15 +0,0 @@
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
@@ -1,12 +0,0 @@
1
- module Europeana
2
- module API
3
- class Record
4
- class Hierarchy
5
- ##
6
- # Retrieve record self hierarchy data over the Europeana API
7
- class Self < Base
8
- end
9
- end
10
- end
11
- end
12
- end
@@ -1,118 +0,0 @@
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
- def extract_api_url(params)
9
- @api_url = params.delete(:api_url)
10
- end
11
-
12
- def api_url
13
- @api_url || Europeana::API.url
14
- end
15
-
16
- ##
17
- # Request-specific params, to be overriden in including class
18
- #
19
- # @return [Hash]
20
- def params
21
- {}
22
- end
23
-
24
- def request_params=(params)
25
- params = params.deep_dup
26
- extract_api_url(params)
27
- @params = HashWithIndifferentAccess.new(params)
28
- end
29
-
30
- ##
31
- # Query params with API key added
32
- #
33
- # @return [Hash]
34
- def params_with_authentication
35
- return params if params.key?(:wskey) && params[:wskey].present?
36
- unless Europeana::API.api_key.present?
37
- fail Europeana::API::Errors::MissingAPIKeyError
38
- end
39
- params.merge(wskey: Europeana::API.api_key)
40
- end
41
-
42
- ##
43
- # Execute the API request
44
- #
45
- # @param options [Hash] Options sent on to {#request_uri} and {#parse_response}
46
- # @return (see #parse_response)
47
- # @raise [Europeana::Errors::ResponseError] if API response could not be
48
- # parsed as JSON
49
- def execute_request(options = {})
50
- uri = request_uri(options)
51
- cache_response_body(uri) do
52
- response = Request.new(uri).execute
53
- parse_response(response, options)
54
- end
55
- rescue JSON::ParserError
56
- raise Errors::ResponseError
57
- end
58
-
59
- def cache_key(uri)
60
- "Europeana/API/#{uri}"
61
- end
62
-
63
- def cache_response_body(uri)
64
- Europeana::API.cache_store.fetch(cache_key(uri), expires_in: Europeana::API.cache_expires_in) do
65
- yield
66
- end
67
- end
68
-
69
- ##
70
- # Parses a JSON response from the API
71
- #
72
- # @param response (see Net::HTTP#request)
73
- # @param options [Hash] Options used by including class's implementation
74
- # of this method
75
- # @return [HashWithIndifferentAccess] Parsed body of API response
76
- def parse_response(response, _options = {})
77
- JSON.parse(response.body).with_indifferent_access
78
- end
79
-
80
- ##
81
- # URI query param string
82
- #
83
- # @return [String]
84
- def request_uri_query
85
- ''.tap do |uri_query|
86
- params_with_authentication.each_pair do |name, value|
87
- [value].flatten.each do |v|
88
- uri_query << '&' unless uri_query.blank?
89
- uri_query << CGI.escape(name.to_s) + '=' + CGI.escape(v.to_s)
90
- end
91
- end
92
- end
93
- end
94
-
95
- ##
96
- # Gets the URI for this request, with any query parameters
97
- #
98
- # @param [Hash{Symbol => Object}] options passed to {#request_url}
99
- # @return [String]
100
- def request_uri(options = {})
101
- URI.parse(request_url(options)).tap do |uri|
102
- uri.query = request_uri_query
103
- end
104
- end
105
-
106
- ##
107
- # URL for the request, without query params
108
- #
109
- # To be implemented by the including class
110
- #
111
- # @param options [Hash] Options used by implementation
112
- # @return [String] Request URL
113
- def request_url(_options = {})
114
- fail NotImplementedError, "Requestable class #{self.class} does not implement #request_url"
115
- end
116
- end
117
- end
118
- end
@@ -1,64 +0,0 @@
1
- module Europeana
2
- module API
3
- ##
4
- # Interface to Europeana API's Search method
5
- class Search
6
- autoload :Fields, 'europeana/api/search/fields'
7
-
8
- include Requestable
9
-
10
- # Query params
11
- attr_accessor :params
12
-
13
- class << self
14
- ##
15
- # Escapes Lucene syntax special characters for use in query parameters
16
- #
17
- # @param [String] text Text to escape
18
- # @return [String] Escaped text
19
- def escape(text)
20
- fail ArgumentError, "Expected String, got #{text.class}" unless text.is_a?(String)
21
- specials = %w<\\ + - & | ! ( ) { } [ ] ^ " ~ * ? : / >
22
- specials.each_with_object(text.dup) do |char, unescaped|
23
- unescaped.gsub!(char, '\\\\' + char) # prepends *one* backslash
24
- end
25
- end
26
- end
27
-
28
- ##
29
- # @param [Hash] params Europeana API request parameters for Search query
30
- def initialize(params = {})
31
- self.request_params = params
32
- end
33
-
34
- ##
35
- # Base URL for a Search request
36
- #
37
- # @return [URI]
38
- def request_url(_options = {})
39
- api_url + '/search.json'
40
- end
41
-
42
- alias_method :execute, :execute_request
43
-
44
- ##
45
- # Examines the `success` and `error` fields of the response for failure
46
- #
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
- klass = if body.key?(:error) && body[:error] =~ /1000 search results/
54
- Errors::Request::PaginationError
55
- else
56
- Errors::RequestError
57
- end
58
- fail klass, (body.key?(:error) ? body[:error] : response.code)
59
- end
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,112 +0,0 @@
1
- module Europeana
2
- module API
3
- class Search
4
- ##
5
- # API field information and validation in search context
6
- #
7
- # @see http://labs.europeana.eu/api/data-fields/
8
- module Fields
9
- EDM_PROVIDED_CHO = %w(edm_europeana_proxy proxy_dc_contributor
10
- proxy_dc_coverage proxy_dc_creator proxy_dc_date proxy_dc_description
11
- proxy_dc_format proxy_dc_identifier proxy_dc_language
12
- proxy_dc_publisher proxy_dc_relation proxy_dc_rights proxy_dc_source
13
- proxy_dc_subject proxy_dc_title proxy_dc_type
14
- proxy_dcterms_alternative proxy_dcterms_conformsTo
15
- proxy_dcterms_created proxy_dcterms_extent proxy_dcterms_hasFormat
16
- proxy_dcterms_hasPart proxy_dcterms_hasVersion
17
- proxy_dcterms_isFormatOf proxy_dcterms_isPartOf
18
- proxy_dcterms_isReferencedBy proxy_dcterms_isReplacedBy
19
- proxy_dcterms_isRequiredBy proxy_dcterms_issued
20
- proxy_dcterms_isVersionOf proxy_dcterms_medium
21
- proxy_dcterms_provenance proxy_dcterms_references
22
- proxy_dcterms_replaces proxy_dcterms_requires proxy_dcterms_spatial
23
- proxy_dcterms_tableOfContents proxy_dcterms_temporal
24
- proxy_edm_currentLocation proxy_edm_currentLocation_lat
25
- proxy_edm_currentLocation_lon proxy_edm_hasMet proxy_edm_hasType
26
- proxy_edm_incorporates proxy_edm_isDerivativeOf
27
- proxy_edm_isNextInSequence proxy_edm_isRelatedTo
28
- proxy_edm_isRepresentationOf proxy_edm_isSimilarTo
29
- proxy_edm_isSuccessorOf proxy_edm_realizes proxy_edm_type
30
- proxy_edm_unstored proxy_edm_wasPresentAt proxy_owl_sameAs
31
- proxy_edm_rights proxy_edm_userTags proxy_edm_year proxy_ore_proxy
32
- proxy_ore_proxyFor proxy_ore_proxyIn)
33
-
34
- ORE_AGGREGATION = %w(provider_aggregation_ore_aggregation
35
- provider_aggregation_ore_aggregates
36
- provider_aggregation_edm_aggregatedCHO
37
- provider_aggregation_edm_dataProvider
38
- provider_aggregation_edm_hasView provider_aggregation_edm_isShownAt
39
- provider_aggregation_edm_isShownBy provider_aggregation_edm_object
40
- provider_aggregation_edm_provider provider_aggregation_dc_rights
41
- provider_aggregation_edm_rights edm_UGC
42
- provider_aggregation_edm_unstored edm_previewNoDistribute)
43
-
44
- EDM_EUROPEANA_AGGREGATION = %w(edm_europeana_aggregation
45
- europeana_aggregation_dc_creator europeana_aggregation_edm_country
46
- europeana_aggregation_edm_hasView europeana_aggregation_edm_isShownBy
47
- europeana_aggregation_edm_landingPage
48
- europeana_aggregation_edm_language europeana_aggregation_edm_rights
49
- europeana_aggregation_ore_aggregatedCHO
50
- europeana_aggregation_ore_aggregates)
51
-
52
- EDM_WEB_RESOURCE = %w(edm_webResource wr_dc_description wr_dc_format
53
- wr_dc_rights wr_dc_source wr_dcterms_conformsTo wr_dcterms_created
54
- wr_dcterms_extent wr_dcterms_hasPart wr_dcterms_isFormatOf
55
- wr_dcterms_issued wr_edm_isNextInSequence wr_edm_rights)
56
-
57
- EDM_AGENT = %w(edm_agent ag_skos_prefLabel ag_skos_altLabel
58
- ag_skos_hiddenLabel ag_skos_note ag_dc_date ag_dc_identifier
59
- ag_edm_begin ag_edm_end ag_edm_hasMet ag_edm_isRelatedTo
60
- ag_edm_wasPresentAt ag_foaf_name ag_rdagr2_biographicalInformation
61
- ag_rdagr2_dateOfBirth ag_rdagr2_dateOfDeath
62
- ag_rdagr2_dateOfEstablishment ag_rdagr2_dateOfTermination
63
- ag_rdagr2_gender ag_rdagr2_professionOrOccupation ag_owl_sameAs)
64
-
65
- SKOS_CONCEPT = %w(skos_concept cc_skos_prefLabel cc_skos_altLabel
66
- cc_skos_hiddenLabel cc_skos_broader cc_skos_broaderLabel
67
- cc_skos_narrower cc_skos_related cc_skos_broadMatch
68
- cc_skos_narrowMatch cc_skos_relatedMatch cc_skos_exactMatch
69
- cc_skos_closeMatch cc_skos_note cc_skos_notation cc_skos_inScheme)
70
-
71
- EDM_PLACE = %w(edm_place pl_wgs84_pos_lat pl_wgs84_pos_long
72
- pl_wgs84_pos_alt pl_wgs84_pos_lat_long pl_skos_prefLabel
73
- pl_skos_altLabel pl_skos_hiddenLabel pl_skos_note pl_dcterms_hasPart
74
- pl_dcterms_isPartOf pl_owl_sameAs pl_dcterms_isPartOf_label)
75
-
76
- EDM_TIME_SPAN = %w(edm_timespan ts_skos_prefLabel ts_skos_altLabel
77
- ts_skos_hiddenLabel ts_skos_note ts_dcterms_hasPart
78
- ts_dcterms_isPartOf ts_edm_begin ts_edm_end ts_owl_sameAs
79
- ts_dcterms_isPartOf_label)
80
-
81
- NON_EDM = %w(europeana_completeness europeana_previewNoDistribute
82
- timestamp europeana_id identifier europeana_collectionName)
83
-
84
- AGGREGATED = %w(country date description format language location
85
- publisher relation rights source subject text title what when where
86
- who)
87
-
88
- FACETS = %w(COMPLETENESS CONTRIBUTOR COUNTRY DATA_PROVIDER LANGUAGE
89
- LOCATION PROVIDER RIGHTS SUBJECT TYPE UGC USERTAGS YEAR)
90
-
91
- AGGREGATED_FACETS = %w(DEFAULT)
92
-
93
- MEDIA = %w(COLOURPALETTE IMAGE_ASPECTRATIO IMAGE_COLOR IMAGE_COLOUR
94
- IMAGE_GRAYSCALE IMAGE_GREYSCALE IMAGE_SIZE MEDIA MIME_TYPE
95
- SOUND_DURATION SOUND_HQ TEXT_FULLTEXT VIDEO_DURATION VIDEO_HD)
96
-
97
- ##
98
- # Tests whether the given field name is valid in the API search context
99
- #
100
- # @param name [String] field name
101
- # @return [Boolean]
102
- def self.include?(name)
103
- constants.any? do |scope|
104
- const_get(scope).any? do |field|
105
- field == name
106
- end
107
- end
108
- end
109
- end
110
- end
111
- end
112
- end