qa 5.13.0 → 5.14.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c2bc5171a4ed527a81eb174b2e5e7f0e99d86c2df285c2619d415ec4587af84
4
- data.tar.gz: 8fd957474f3f185867025fb18e08d4262e207ae051bca57bc43eaac10bc7b097
3
+ metadata.gz: 790243f02ba25c268235f6247e96f2824bbc1d971463992c17fabc9f6a8109a0
4
+ data.tar.gz: e5fd83a8fc8aac46cb0c991184c6ceb8d39dd64a9fae10fff0f4edd6d30ae4c2
5
5
  SHA512:
6
- metadata.gz: 54cc9b6d0289157de648f34c040355eb06f206ca0d616664d96e6ae3d3e5e754e950c447127d61cf16f6d3f15f874fd65d23845d1f912ce89c3e1571875a9d8e
7
- data.tar.gz: 44e459c8a7424a8116f075979fa7ce2023b5307edb48c2a044cfdea5e5239435b8a85375687a9f2a51b7c63af60e2435bf27769043c9fa8be1de9d0f98c8fe9e
6
+ metadata.gz: a4a2fca9475e5c1b94960b2bcead2fa7d7b504b5a08767797148e59a017178677d3691c92d0a2fc785e4bc53021968850e558bff33fe1739ea344c8abc4a32db
7
+ data.tar.gz: 6ac6e9ffb528f9dfb7c8a5818793ddb29f83e572a5f61ed3864185407eaab503353ab9c222bc1ea8c0216cd52c00402353652e1e86f535d608ce21505756c27d
@@ -42,7 +42,7 @@ class Qa::LinkedDataTermsController < ::ApplicationController
42
42
  # get "/search/linked_data/:vocab(/:subauthority)"
43
43
  # @see Qa::Authorities::LinkedData::SearchQuery#search
44
44
  def search # rubocop:disable Metrics/MethodLength
45
- terms = @authority.search(query, request_header: request_header_service.search_header)
45
+ terms = @authority.search(query)
46
46
  cors_allow_origin_header(response)
47
47
  render json: terms
48
48
  rescue Qa::ServiceUnavailable
@@ -65,7 +65,7 @@ class Qa::LinkedDataTermsController < ::ApplicationController
65
65
  # get "/show/linked_data/:vocab/:subauthority/:id
66
66
  # @see Qa::Authorities::LinkedData::FindTerm#find
67
67
  def show # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
68
- term = @authority.find(id, request_header: request_header_service.fetch_header)
68
+ term = @authority.find(id)
69
69
  cors_allow_origin_header(response)
70
70
  render json: term, content_type: request_header_service.content_type_for_format
71
71
  rescue Qa::TermNotFound
@@ -95,7 +95,7 @@ class Qa::LinkedDataTermsController < ::ApplicationController
95
95
  # get "/fetch/linked_data/:vocab"
96
96
  # @see Qa::Authorities::LinkedData::FindTerm#find
97
97
  def fetch # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
98
- term = @authority.find(uri, request_header: request_header_service.fetch_header)
98
+ term = @authority.find(uri)
99
99
  cors_allow_origin_header(response)
100
100
  render json: term, content_type: request_header_service.content_type_for_format
101
101
  rescue Qa::TermNotFound
@@ -157,9 +157,14 @@ class Qa::LinkedDataTermsController < ::ApplicationController
157
157
  @request_header_service = request_header_service_class.new(request: request, params: params)
158
158
  end
159
159
 
160
+ # @see Qa::AuthorityWrapper for these methods
161
+ delegate :search_header, :fetch_header, to: :request_header_service
162
+
160
163
  def init_authority
161
- @authority = Qa::Authorities::LinkedData::GenericAuthority.new(vocab_param)
162
- rescue Qa::InvalidLinkedDataAuthority => e
164
+ @authority = Qa.authority_for(vocab: params[:vocab],
165
+ subauthority: params[:subauthority],
166
+ context: self)
167
+ rescue Qa::InvalidAuthorityError, Qa::InvalidLinkedDataAuthority => e
163
168
  msg = e.message
164
169
  logger.warn msg
165
170
  render json: { errors: msg }, status: :bad_request
@@ -69,26 +69,15 @@ class Qa::TermsController < ::ApplicationController
69
69
  end
70
70
 
71
71
  def init_authority # rubocop:disable Metrics/MethodLength
72
- begin
73
- mod = authority_class.camelize.constantize
74
- rescue NameError
75
- msg = "Unable to initialize authority #{authority_class}"
76
- logger.warn msg
77
- render json: { errors: msg }, status: :bad_request
78
- return
79
- end
80
- begin
81
- @authority = if mod.is_a? Class
82
- mod.new
83
- else
84
- raise Qa::MissingSubAuthority, "No sub-authority provided" if params[:subauthority].blank?
85
- mod.subauthority_for(params[:subauthority])
86
- end
87
- rescue Qa::InvalidSubAuthority, Qa::MissingSubAuthority => e
88
- msg = e.message
89
- logger.warn msg
90
- render json: { errors: msg }, status: :bad_request
91
- end
72
+ @authority = Qa.authority_for(vocab: params[:vocab],
73
+ subauthority: params[:subauthority],
74
+ # Included to preserve error message text
75
+ try_linked_data_config: false,
76
+ context: self)
77
+ rescue Qa::InvalidAuthorityError, Qa::InvalidSubAuthority, Qa::MissingSubAuthority => e
78
+ msg = e.message
79
+ logger.warn msg
80
+ render json: { errors: msg }, status: :bad_request
92
81
  end
93
82
 
94
83
  def check_query_param
@@ -29,5 +29,8 @@ module Qa::Authorities
29
29
  def find(_id)
30
30
  raise NotImplementedError, "#{self.class}#find is unimplemented."
31
31
  end
32
+
33
+ class_attribute :linked_data, instance_writer: false
34
+ self.linked_data = false
32
35
  end
33
36
  end
@@ -13,6 +13,8 @@ module Qa::Authorities
13
13
  attr_reader :authority_config
14
14
  private :authority_config
15
15
 
16
+ self.linked_data = true
17
+
16
18
  delegate :supports_term?, :term_subauthorities?, :term_subauthority?,
17
19
  :term_id_expects_uri?, :term_id_expects_id?, to: :term_config
18
20
 
@@ -35,7 +35,8 @@ module Qa::Authorities
35
35
  end
36
36
 
37
37
  def find_url(id)
38
- "https://id.loc.gov/authorities/#{@subauthority}/#{id}.json"
38
+ root_fetch_slug = Loc.root_fetch_slug_for(@subauthority)
39
+ File.join("https://id.loc.gov/", root_fetch_slug, "/#{@subauthority}/#{id}.json")
39
40
  end
40
41
 
41
42
  private
@@ -1,4 +1,5 @@
1
1
  module Qa::Authorities::LocSubauthority
2
+ # @todo Rename to reflect that this is a URI encoded url fragement used only for searching.
2
3
  def get_url_for_authority(authority)
3
4
  if authorities.include?(authority) then authority_base_url
4
5
  elsif vocabularies.include?(authority) then vocab_base_url
@@ -7,6 +8,23 @@ module Qa::Authorities::LocSubauthority
7
8
  end
8
9
  end
9
10
 
11
+ # @note The returned value is the root directory of the URL. The graphicMaterials sub-authority
12
+ # has a "type" of vocabulary. https://id.loc.gov/vocabulary/graphicMaterials/tgm008083.html
13
+ # In some cases, this is plural and in others this is singular.
14
+ #
15
+ # @param authority [String] the LOC authority that matches one of the types
16
+ # @return [String]
17
+ #
18
+ # @note there is a relationship between the returned value and the encoded URLs returned by
19
+ # {#get_url_for_authority}.
20
+ def root_fetch_slug_for(authority)
21
+ validate_subauthority!(authority)
22
+ return "authorities" if authorities.include?(authority)
23
+ return "vocabulary" if vocabularies.include?(authority)
24
+ return "datatype" if datatypes.include?(authority)
25
+ return "vocabulary/preservation" if preservation.include?(authority)
26
+ end
27
+
10
28
  def authorities
11
29
  [
12
30
  "subjects",
@@ -0,0 +1,49 @@
1
+ module Qa
2
+ # @note THIS IS NOT TESTED NOR EXERCISED CODE IT IS PROVIDED AS CONJECTURE. FUTURE CHANGES MIGHT
3
+ # BUILD AND REFACTOR UPON THIS.
4
+ #
5
+ # @api private
6
+ # @abstract
7
+ #
8
+ # This class is responsible for exposing methods that are required by both linked data and
9
+ # non-linked data authorities. As of v5.10.0, those three methods are: params, search_header,
10
+ # fetch_header. Those are the methods that are used in {Qa::LinkedData::RequestHeaderService} and
11
+ # in {Qa::Authorities::Discogs::GenericAuthority}.
12
+ #
13
+ # The intention is to provide a class that can behave like a controller object without being that
14
+ # entire controller object.
15
+ #
16
+ # @see Qa::LinkedData::RequestHeaderService
17
+ # @see Qa::Authorities::Discogs::GenericAuthority
18
+ class AuthorityRequestContext
19
+ def self.fallback
20
+ new
21
+ end
22
+
23
+ def initialize(params: {}, headers: {}, **kwargs)
24
+ @params = params
25
+ @headers = headers
26
+ (SEARCH_HEADER_KEYS + FETCH_HEADER_KEYS).uniq.each do |key|
27
+ send("#{key}=", kwargs[key]) if kwargs.key?(key)
28
+ end
29
+ end
30
+
31
+ SEARCH_HEADER_KEYS = %i[request request_id subauthority user_language performance_data context response_header replacements].freeze
32
+ FETCH_HEADER_KEYS = %i[request request_id subauthority user_language performance_data format response_header replacements].freeze
33
+
34
+ attr_accessor :params, :headers
35
+ attr_accessor(*(SEARCH_HEADER_KEYS + FETCH_HEADER_KEYS).uniq)
36
+
37
+ def search_header
38
+ SEARCH_HEADER_KEYS.each_with_object(headers.deep_dup) do |key, header|
39
+ header[key] = send(key) if send(key).present?
40
+ end.compact
41
+ end
42
+
43
+ def fetch_header
44
+ FETCH_HEADER_KEYS.each_with_object(headers.deep_dup) do |key, header|
45
+ header[key] = send(key) if send(key).present?
46
+ end.compact
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,63 @@
1
+ module Qa
2
+ # @api public
3
+ # @since v5.11.0
4
+ #
5
+ # The intention of this wrapper is to provide a common interface that both linked and non-linked
6
+ # data can use. There are implementation differences between the two, but with this wrapper, the
7
+ # goal is to draw attention to those differences and insulate the end user from those issues.
8
+ #
9
+ # One benefit in introducing this class is that when interacting with a questioning authority
10
+ # implementation you don't need to consider "Hey when I instantiate an authority, is this linked
11
+ # data or not?" And what specifically are the parameter differences. You will need to perhaps
12
+ # include some additional values in the context if you don't call this from a controller.
13
+ class AuthorityWrapper
14
+ require 'qa/authority_request_context.rb'
15
+ # @param authority [#find, #search]
16
+ # @param subauthority [#to_s]
17
+ # @param context [#params, #search_header, #fetch_header]
18
+ def initialize(authority:, subauthority:, context:)
19
+ @authority = authority
20
+ @subauthority = subauthority
21
+ @context = context
22
+ configure!
23
+ end
24
+ attr_reader :authority, :context, :subauthority
25
+
26
+ def search(value)
27
+ if linked_data?
28
+ # should respond to search_header
29
+ authority.search(value, request_header: context.search_header)
30
+ elsif authority.method(:search).arity == 2
31
+ # This context should respond to params; see lib/qa/authorities/discogs/generic_authority.rb
32
+ authority.search(value, context)
33
+ else
34
+ authority.search(value)
35
+ end
36
+ end
37
+
38
+ # context has params
39
+ def find(value)
40
+ if linked_data?
41
+ # should respond to fetch_header
42
+ authority.find(value, request_header: context.fetch_header)
43
+ elsif authority.method(:find).arity == 2
44
+ authority.find(value, context)
45
+ else
46
+ authority.find(value)
47
+ end
48
+ end
49
+ alias fetch find
50
+
51
+ def method_missing(method_name, *arguments, &block)
52
+ authority.send(method_name, *arguments, &block)
53
+ end
54
+
55
+ def respond_to_missing?(method_name, include_private = false)
56
+ authority.respond_to?(method_name, include_private)
57
+ end
58
+
59
+ def configure!
60
+ @context.subauthority = @subauthority if @context.respond_to?(:subauthority)
61
+ end
62
+ end
63
+ end
data/lib/qa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qa
2
- VERSION = "5.13.0".freeze
2
+ VERSION = "5.14.0".freeze
3
3
  end
data/lib/qa.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "qa/engine"
2
2
  require "active_record"
3
3
  require "activerecord-import"
4
+ require "qa/authority_wrapper"
4
5
 
5
6
  module Qa
6
7
  extend ActiveSupport::Autoload
@@ -30,6 +31,13 @@ module Qa
30
31
  warn "[DEPRECATED] #{in_msg}#{msg} It will be removed in the next major release."
31
32
  end
32
33
 
34
+ # Raised when the authority is not valid
35
+ class InvalidAuthorityError < RuntimeError
36
+ def initialize(authority_class)
37
+ super "Unable to initialize authority #{authority_class}"
38
+ end
39
+ end
40
+
33
41
  # Raised when the configuration directory for local authorities doesn't exist
34
42
  class ConfigDirectoryNotFound < StandardError; end
35
43
 
@@ -67,4 +75,46 @@ module Qa
67
75
 
68
76
  # Raised when data is returned but cannot be normalized
69
77
  class DataNormalizationError < StandardError; end
78
+
79
+ # @api public
80
+ # @since 5.11.0
81
+ #
82
+ # @param vocab [String]
83
+ # @param subauthority [String]
84
+ # @param context [#params, #search_header, #fetch_header]
85
+ # @param try_linked_data_config [Boolean] when true attempt to check for a linked data authority;
86
+ # this is included as an option to help preserve error messaging from the 5.10.0 branch.
87
+ # Unless you want to mirror the error messages of `Qa::TermsController#init_authority` then
88
+ # use the default value.
89
+ #
90
+ # @note :try_linked_data_config is included to preserve error message text; something which is
91
+ # extensively tested in this gem.
92
+ #
93
+ # @return [#search, #find] an authority that will respond to #search and #find; and in some cases
94
+ # #fetch. This is provided as a means of normalizing how we initialize an authority.
95
+ # And to provide a means to request an authority both within a controller request cycle as
96
+ # well as outside of that cycle.
97
+ def self.authority_for(vocab:, context:, subauthority: nil, try_linked_data_config: true)
98
+ authority = build_authority_for(vocab: vocab,
99
+ subauthority: subauthority,
100
+ try_linked_data_config: try_linked_data_config)
101
+ AuthorityWrapper.new(authority: authority, subauthority: subauthority, context: context)
102
+ end
103
+
104
+ # @api private
105
+ def self.build_authority_for(vocab:, subauthority: nil, try_linked_data_config: true)
106
+ authority_constant_name = "Qa::Authorities::#{vocab.to_s.camelcase}"
107
+ authority_constant = authority_constant_name.safe_constantize
108
+ if authority_constant.nil?
109
+ return Qa::Authorities::LinkedData::GenericAuthority.new(vocab.upcase.to_sym) if try_linked_data_config
110
+
111
+ raise InvalidAuthorityError, authority_constant_name
112
+ end
113
+
114
+ return authority_constant.new if authority_constant.is_a?(Class)
115
+ return authority_constant.subauthority_for(subauthority) if subauthority.present?
116
+
117
+ raise Qa::MissingSubAuthority, "No sub-authority provided"
118
+ end
119
+ private_class_method :build_authority_for
70
120
  end
@@ -42,6 +42,18 @@ describe Qa::Authorities::Loc do
42
42
  end
43
43
  end
44
44
 
45
+ describe ".root_fetch_slug_for" do
46
+ it "raises an error for an invalid subauthority" do
47
+ expect do
48
+ described_class.root_fetch_slug_for("no-one-would-ever-have-this-one")
49
+ end.to raise_error Qa::InvalidSubAuthority
50
+ end
51
+
52
+ it "returns the corresponding type for the given subauthority" do
53
+ expect(described_class.root_fetch_slug_for("graphicMaterials")).to eq("vocabulary")
54
+ end
55
+ end
56
+
45
57
  describe "#response" do
46
58
  subject { authority.response(url) }
47
59
  let :authority do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qa
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.13.0
4
+ version: 5.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Anderson
@@ -16,7 +16,7 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2024-08-20 00:00:00.000000000 Z
19
+ date: 2024-12-12 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: activerecord-import
@@ -117,7 +117,7 @@ dependencies:
117
117
  version: '5.0'
118
118
  - - "<"
119
119
  - !ruby/object:Gem::Version
120
- version: '8.0'
120
+ version: '8.1'
121
121
  type: :runtime
122
122
  prerelease: false
123
123
  version_requirements: !ruby/object:Gem::Requirement
@@ -127,7 +127,7 @@ dependencies:
127
127
  version: '5.0'
128
128
  - - "<"
129
129
  - !ruby/object:Gem::Version
130
- version: '8.0'
130
+ version: '8.1'
131
131
  - !ruby/object:Gem::Dependency
132
132
  name: rdf
133
133
  requirement: !ruby/object:Gem::Requirement
@@ -501,6 +501,8 @@ files:
501
501
  - lib/qa/authorities/oclcts/generic_oclc_authority.rb
502
502
  - lib/qa/authorities/tgnlang.rb
503
503
  - lib/qa/authorities/web_service_base.rb
504
+ - lib/qa/authority_request_context.rb
505
+ - lib/qa/authority_wrapper.rb
504
506
  - lib/qa/configuration.rb
505
507
  - lib/qa/data/TGN_LANGUAGES.xml
506
508
  - lib/qa/engine.rb