europeana-api 0.5.2 → 1.0.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.
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