diff --git a/app/controllers/qa/linked_data_terms_controller.rb b/app/controllers/qa/linked_data_terms_controller.rb index 9bd4bdaa..5363e052 100644 --- a/app/controllers/qa/linked_data_terms_controller.rb +++ b/app/controllers/qa/linked_data_terms_controller.rb @@ -7,9 +7,15 @@ class Qa::LinkedDataTermsController < ::ApplicationController before_action :check_show_subauthority, :check_id_param, only: :show before_action :check_uri_param, only: :fetch before_action :validate_auth_reload_token, only: :reload + before_action :create_request_header_service, only: [:search, :show, :fetch] delegate :cors_allow_origin_header, to: Qa::ApplicationController + class_attribute :request_header_service_class + self.request_header_service_class = Qa::LinkedData::RequestHeaderService + + attr_reader :request_header_service + # Provide a warning if there is a request for all terms. def index logger.warn 'Linked data authorities do not support retrieving all terms.' @@ -35,8 +41,8 @@ def reload # Return a list of terms based on a query # get "/search/linked_data/:vocab(/:subauthority)" # @see Qa::Authorities::LinkedData::SearchQuery#search - def search # rubocop:disable Metrics/MethodLength, Metrics/AbcSize - terms = @authority.search(query, subauth: subauthority, language: language, replacements: replacement_params, context: context?, performance_data: performance_data?) + def search # rubocop:disable Metrics/MethodLength + terms = @authority.search(query, request_header: request_header_service.search_header) cors_allow_origin_header(response) render json: terms rescue Qa::ServiceUnavailable @@ -59,9 +65,9 @@ def search # rubocop:disable Metrics/MethodLength, Metrics/AbcSize # get "/show/linked_data/:vocab/:subauthority/:id # @see Qa::Authorities::LinkedData::FindTerm#find def show # rubocop:disable Metrics/MethodLength, Metrics/AbcSize - term = @authority.find(id, subauth: subauthority, language: language, replacements: replacement_params, format: format, performance_data: performance_data?) + term = @authority.find(id, request_header: request_header_service.fetch_header) cors_allow_origin_header(response) - render json: term, content_type: content_type_for_format + render json: term, content_type: request_header_service.content_type_for_format rescue Qa::TermNotFound msg = "Term Not Found - Fetch term #{id} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}" logger.warn msg @@ -89,9 +95,9 @@ def show # rubocop:disable Metrics/MethodLength, Metrics/AbcSize # get "/fetch/linked_data/:vocab" # @see Qa::Authorities::LinkedData::FindTerm#find def fetch # rubocop:disable Metrics/MethodLength, Metrics/AbcSize - term = @authority.find(uri, subauth: subauthority, language: language, replacements: replacement_params, format: format, performance_data: performance_data?) + term = @authority.find(uri, request_header: request_header_service.fetch_header) cors_allow_origin_header(response) - render json: term, content_type: content_type_for_format + render json: term, content_type: request_header_service.content_type_for_format rescue Qa::TermNotFound msg = "Term Not Found - Fetch term #{uri} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}" logger.warn msg @@ -147,6 +153,10 @@ def check_show_subauthority end end + def create_request_header_service + @request_header_service = request_header_service_class.new(request, params) + end + def init_authority @authority = Qa::Authorities::LinkedData::GenericAuthority.new(vocab_param) rescue Qa::InvalidLinkedDataAuthority => e @@ -190,64 +200,19 @@ def id params[:id] end - def language - request_language = request.env['HTTP_ACCEPT_LANGUAGE'] - request_language = request_language.scan(/^[a-z]{2}/).first if request_language.present? - params[:lang] || request_language - end - def subauthority params[:subauthority] end - def replacement_params - params.reject { |k, _v| ['q', 'vocab', 'controller', 'action', 'subauthority', 'lang', 'id'].include?(k) } - end - def subauth_warn_msg subauthority.blank? ? "" : " sub-authority #{subauthority} in" end - def format - return 'json' unless params.key?(:format) - return 'json' if params[:format].blank? - params[:format] - end - - def jsonld? - format.casecmp?('jsonld') - end - - def n3? - format.casecmp?('n3') - end - - def ntriples? - format.casecmp?('ntriples') - end - - def content_type_for_format - return 'application/ld+json' if jsonld? - return 'text/n3' if n3? - return 'application/n-triples' if ntriples? - 'application/json' - end - - def context? - context = params.fetch(:context, 'false') - context.casecmp?('true') - end - def details? details = params.fetch(:details, 'false') details.casecmp?('true') end - def performance_data? - performance_data = params.fetch(:performance_data, 'false') - performance_data.casecmp?('true') - end - def validate_auth_reload_token token = params.key?(:auth_token) ? params[:auth_token] : nil valid = Qa.config.valid_authority_reload_token?(token) diff --git a/app/services/qa/linked_data/authority_url_service.rb b/app/services/qa/linked_data/authority_url_service.rb index 59a6cb7a..824d3f6b 100644 --- a/app/services/qa/linked_data/authority_url_service.rb +++ b/app/services/qa/linked_data/authority_url_service.rb @@ -7,14 +7,17 @@ class << self # @param action_config [Qa::Authorities::LinkedData::SearchConfig | Qa::Authorities::LinkedData::TermConfig] action configuration for the authority # @param action [Symbol] action with valid values :search or :term # @param action_request [String] the request the user is making of the authority (e.g. query text or term id/uri) - # @param substitutions [Hash] variable-value pairs to substitute into the URL template (optional) - # @param subauthority [String] name of a subauthority (optional) - # @param language [Array] languages for filtering returned literals (optional) + # @param request_header [Hash] optional attributes that can be appended to the generated URL + # @option replacements [Hash] variable-value pairs to substitute into the URL template + # @option subauthority [String] name of a subauthority + # @option language [Array] languages for filtering returned literals # @return a valid URL that submits the action request to the external authority - def build_url(action_config:, action:, action_request:, substitutions: {}, subauthority: nil, language: nil) # rubocop:disable Metrics/ParameterLists + # @note All parameters after request_header are deprecated and will be removed in the next major release. + def build_url(action_config:, action:, action_request:, request_header: {}, substitutions: {}, subauthority: nil, language: nil) # rubocop:disable Metrics/ParameterLists + request_header = build_request_header(substitutions, subauthority, language) if request_header.empty? action_validation(action) url_config = action_config.url_config - selected_substitutions = url_config.extract_substitutions(combined_substitutions(action_config, action, action_request, substitutions, subauthority, language)) + selected_substitutions = url_config.extract_substitutions(combined_substitutions(action_config, action, action_request, request_header)) Qa::IriTemplateService.build_url(url_config: url_config, substitutions: selected_substitutions) end @@ -25,10 +28,12 @@ def action_validation(action) raise Qa::UnsupportedAction, "#{action} Not Supported - Action must be one of the supported actions (e.g. :term, :search)" end - def combined_substitutions(action_config, action, action_request, substitutions, subauthority, language) # rubocop:disable Metrics/ParameterLists + def combined_substitutions(action_config, action, action_request, request_header) + substitutions = request_header.fetch(:replacements, {}) substitutions[action_request_variable(action_config, action)] = action_request - substitutions[action_subauth_variable(action_config)] = action_subauth_variable_value(action_config, subauthority) if supports_subauthorities?(action_config) && subauthority.present? - substitutions[action_language_variable(action_config)] = language_value(language) if supports_language_parameter?(action_config) && language.present? + substitutions[action_subauth_variable(action_config)] = action_subauth_variable_value(action_config, request_header) + substitutions[action_language_variable(action_config)] = language_value(action_config, request_header) + substitutions.reject { |_k, v| v.nil? } substitutions end @@ -37,29 +42,38 @@ def action_request_variable(action_config, action) action_config.qa_replacement_patterns[key] end - def supports_subauthorities?(action_config) - action_config.supports_subauthorities? - end - def action_subauth_variable(action_config) action_config.qa_replacement_patterns[:subauth] end - def action_subauth_variable_value(action_config, subauthority) - action_config.subauthorities[subauthority.to_sym] - end - - def supports_language_parameter?(action_config) - action_config.supports_language_parameter? + def action_subauth_variable_value(action_config, request_header) + subauth = request_header.fetch(:subauthority, nil) + return nil unless subauth && action_config.supports_subauthorities? + action_config.subauthorities[subauth.to_sym] end def action_language_variable(action_config) action_config.qa_replacement_patterns[:lang] end - def language_value(language) - return nil if language.blank? - language.first + def language_value(action_config, request_header) + return nil unless action_config.supports_language_parameter? + request_header.fetch(:language, []).first + end + + # This is providing support for calling build_url with individual parameters instead of the request_header. + # This is deprecated and will be removed in the next major release. + def build_request_header(substitutions, subauthority, language) # rubocop:disable Metrics/CyclomaticComplexity + return {} if substitutions.blank? && subauthority.blank? && language.blank? + Qa.deprecation_warning( + in_msg: 'Qa::LinkedData::AuthorityUrlService', + msg: "individual attributes for options (e.g. substitutions, subauthority, language) are deprecated; use request_header instead" + ) + request_header = {} + request_header[:replacements] = substitutions unless substititions.blank? + request_header[:subauthority] = subauthority unless subauthority.blank? + request_header[:language] = language unless language.blank? + request_header end end end diff --git a/app/services/qa/linked_data/request_header_service.rb b/app/services/qa/linked_data/request_header_service.rb new file mode 100644 index 00000000..58a875ec --- /dev/null +++ b/app/services/qa/linked_data/request_header_service.rb @@ -0,0 +1,97 @@ +# Service to construct a request header that includes optional attributes for search and fetch requests. +module Qa + module LinkedData + class RequestHeaderService + attr_reader :request, :params + + # @param request [HttpRequest] request from controller + # @param params [Hash] attribute-value pairs holding the request parameters + # @option subauthority [String] the subauthority to query + # @option lang [Symbol] language used to select literals when multi-language is supported (e.g. :en, :fr, etc.) + # @option performance_data [Boolean] true if include_performance_data should be returned with the results; otherwise, false (default: false) + # @option context [Boolean] true if context should be returned with the results; otherwise, false (default: false) (search only) + # @option format [String] return data in this format (fetch only) + # @note params may have additional attribute-value pairs that are passed through via replacements (only configured replacements are used) + def initialize(request, params) + @request = request + @params = params + end + + # Construct request parameters to pass to search_query (linked data module). + # @returns [Hash] parsed out attribute-value pairs that are required for the search query + # @see Qa::Authorities::LinkedData::SearchQuery + def search_header + header = {} + header[:subauthority] = params.fetch(:subauthority, nil) + header[:user_language] = user_language + header[:performance_data] = performance_data? + header[:context] = context? + header[:replacements] = replacements + header + end + + # Construct request parameters to pass to fetching a term (linked data module). + # @returns [Hash] parsed out attribute-value pairs that are required for the term fetch. + # @see Qa::Authorities::LinkedData::FindTerm + def fetch_header + header = {} + header[:subauthority] = params.fetch(:subauthority, nil) + header[:user_language] = user_language + header[:performance_data] = performance_data? + header[:format] = format + header[:replacements] = replacements + header + end + + # @returns [String] the response header content type based on requested format + def content_type_for_format + case format + when 'jsonld' + 'application/ld+json' + when 'n3' + 'text/n3' + when 'ntriples' + 'application/n-triples' + else + 'application/json' + end + end + + private + + # filter literals in results to this language + def user_language + request_language = request.env['HTTP_ACCEPT_LANGUAGE'] + request_language = request_language.scan(/^[a-z]{2}/).first if request_language.present? + lang = params[:lang] || request_language + lang.present? ? Array(lang) : nil + end + + # include extended context in the results if true (applies to search only) + def context? + context = params.fetch(:context, 'false') + context.casecmp?('true') + end + + # include performance data in the results if true + def performance_data? + performance_data = params.fetch(:performance_data, 'false') + performance_data.casecmp?('true') + end + + # any params not specifically handled are passed through via replacements + def replacements + params.reject do |k, _v| + ['q', 'vocab', 'controller', 'action', 'subauthority', 'lang', 'id', + 'context', 'performance_data', 'response_header', 'format'].include?(k) + end + end + + # results are returned in the format (applies to fetch only) + def format + f = params.fetch(:format, 'json').downcase + ['jsonld', 'n3', 'ntriples'].include?(f) ? f : 'json' + end + end + end +end diff --git a/lib/qa/authorities/linked_data/find_term.rb b/lib/qa/authorities/linked_data/find_term.rb index 5f6b767a..e07df5a2 100644 --- a/lib/qa/authorities/linked_data/find_term.rb +++ b/lib/qa/authorities/linked_data/find_term.rb @@ -15,17 +15,21 @@ def initialize(term_config) @term_config = term_config end - attr_reader :term_config, :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size - private :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size + attr_reader :term_config, :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size, :subauthority + private :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size, :subauthority delegate :term_subauthority?, :prefixes, :authority_name, to: :term_config # Find a single term in a linked data authority # @param [String] the id of the term to fetch - # @param [Symbol] (optional) language: language used to select literals when multi-language is supported (e.g. :en, :fr, etc.) - # @param [Hash] (optional) replacements: replacement values with { pattern_name (defined in YAML config) => value } - # @param [String] subauth: the subauthority from which to fetch the term - # @return [Hash] json results + # @param request_header [Hash] optional attributes that can be appended to the generated URL + # @option language [Symbol] language used to select literals when multi-language is supported (e.g. :en, :fr, etc.) + # @option replacements [Hash] replacement values with { pattern_name (defined in YAML config) => value } + # @option subauthority [String] the subauthority from which to fetch the term + # @option format [String] return data in this format + # @option performance_data [Boolean] true if include_performance_data should be returned with the results; otherwise, false (default: false) + # @note All parameters after request_header are deprecated and will be removed in the next major release. + # @return [Hash, String] normalized json results when format='json'; otherwise, serialized RDF in the requested format # @example Json Results for Linked Data Term # { "uri":"http://id.worldcat.org/fast/530369", # "id":"530369","label":"Cornell University", @@ -39,21 +43,12 @@ def initialize(term_config) # "http://schema.org/name":["Cornell University","Ithaca (N.Y.). Cornell University"], # "http://www.w3.org/2004/02/skos/core#altLabel":["Ithaca (N.Y.). Cornell University"], # "http://schema.org/sameAs":["http://id.loc.gov/authorities/names/n79021621","https://viaf.org/viaf/126293486"] } } - def find(id, language: nil, replacements: {}, subauth: nil, format: nil, jsonld: false, performance_data: false) # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength - # TODO: When jsonld parameter is removed, the format parameter should default to 'json'. Not making this change now for backward compatibility of the default for jsonld parameter. - raise Qa::InvalidLinkedDataAuthority, "Unable to initialize linked data term sub-authority #{subauth}" unless subauth.nil? || term_subauthority?(subauth) - @language = language_service.preferred_language(user_language: language, authority_language: term_config.term_language) + def find(id, request_header: {}, language: nil, replacements: {}, subauth: nil, format: 'json', performance_data: false) # rubocop:disable Metrics/ParameterLists + request_header = build_request_header(language: language, replacements: replacements, subauth: subauth, format: format, performance_data: performance_data) if request_header.empty? + unpack_request_header(request_header) + raise Qa::InvalidLinkedDataAuthority, "Unable to initialize linked data term sub-authority #{subauthority}" unless subauthority.nil? || term_subauthority?(subauthority) @id = id - @performance_data = performance_data - @format = format - @jsonld = jsonld if @format.blank? - if jsonld - Qa.deprecation_warning( - in_msg: 'Qa::Authorities::LinkedData::FindTerm', - msg: "jsonld parameter to find method is deprecated; use `format: 'jsonld'` instead" - ) - end - url = authority_service.build_url(action_config: term_config, action: :term, action_request: normalize_id, substitutions: replacements, subauthority: subauth, language: @language) + url = authority_service.build_url(action_config: term_config, action: :term, action_request: normalize_id, request_header: request_header) Rails.logger.info "QA Linked Data term url: #{url}" load_graph(url: url) normalize_results @@ -75,14 +70,14 @@ def load_graph(url:) def normalize_results normalize_start_dt = Time.now.utc - json = perform_normalization + results = perform_normalization normalize_end_dt = Time.now.utc @normalize_time_s = normalize_end_dt - normalize_start_dt - @normalized_size = json.to_s.size if performance_data? - Rails.logger.info("Time to convert data to json: #{normalize_time_s}s") - json = append_performance_data(json) if performance_data? - json + @normalized_size = results.to_s.size if performance_data? + Rails.logger.info("Time to normalize data: #{normalize_time_s}s") + results = append_performance_data(results) if performance_data? + results end def perform_normalization @@ -97,6 +92,15 @@ def perform_normalization convert_results_to_json(results) end + def unpack_request_header(request_header) + @subauthority = request_header.fetch(:subauthority, nil) + @format = request_header.fetch(:format, 'json') + @performance_data = request_header.fetch(:performance_data, false) + @language = language_service.preferred_language(user_language: request_header.fetch(:user_language, nil), + authority_language: term_config.term_language) + request_header[:language] = Array(@language) + end + def filter_graph @filtered_graph = graph_service.deep_copy(graph: @full_graph) @filtered_graph = graph_service.filter(graph: @filtered_graph, language: language) unless language.blank? @@ -290,6 +294,24 @@ def append_performance_data(results) total_time_s: (access_time_s + normalize_time_s) } { performance: performance, results: results } end + + # This is providing support for calling build_url with individual parameters instead of the request_header. + # This is deprecated and will be removed in the next major release. + def build_request_header(language:, replacements:, subauth:, format:, performance_data:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + unless language.blank? && replacements.blank? && subauth.blank? && format == 'json' && !performance_data + Qa.deprecation_warning( + in_msg: 'Qa::Authorities::LinkedData::FindTerm', + msg: "individual attributes for options (e.g. replacements, subauth, language) are deprecated; use request_header instead" + ) + end + request_header = {} + request_header[:replacements] = replacements || {} + request_header[:subauthority] = subauth || nil + request_header[:language] = language || nil + request_header[:format] = format || 'json' + request_header[:performance_data] = performance_data + request_header + end end end end diff --git a/lib/qa/authorities/linked_data/search_query.rb b/lib/qa/authorities/linked_data/search_query.rb index 7f501e0d..059dafd9 100644 --- a/lib/qa/authorities/linked_data/search_query.rb +++ b/lib/qa/authorities/linked_data/search_query.rb @@ -15,29 +15,30 @@ def initialize(search_config) @search_config = search_config end - attr_reader :search_config, :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size - private :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size + attr_reader :search_config, :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size, :subauthority + private :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size, :subauthority delegate :subauthority?, :supports_sort?, :prefixes, :authority_name, to: :search_config # Search a linked data authority # @praram [String] the query - # @param language [Symbol] (optional) language used to select literals when multi-language is supported (e.g. :en, :fr, etc.) - # @param replacements [Hash] (optional) replacement values with { pattern_name (defined in YAML config) => value } - # @param subauth [String] (optional) the subauthority to query - # @param context [Boolean] (optional) true if context should be returned with the results; otherwise, false (default: false) - # @param performance_data [Boolean] (optional) true if include_performance_data should be returned with the results; otherwise, false (default: false) + # @param request_header [Hash] optional attributes that can be appended to the generated URL + # @option language [Symbol] language used to select literals when multi-language is supported (e.g. :en, :fr, etc.) + # @option replacements [Hash] replacement values with { pattern_name (defined in YAML config) => value } + # @option subauthority [String] the subauthority to query + # @option context [Boolean] true if context should be returned with the results; otherwise, false (default: false) + # @option performance_data [Boolean] true if include_performance_data should be returned with the results; otherwise, false (default: false) # @return [String] json results + # @note All parameters after request_header are deprecated and will be removed in the next major release. # @example Json Results for Linked Data Search # [ {"uri":"http://id.worldcat.org/fast/5140","id":"5140","label":"Cornell, Joseph"}, # {"uri":"http://id.worldcat.org/fast/72456","id":"72456","label":"Cornell, Sarah Maria, 1802-1832"}, # {"uri":"http://id.worldcat.org/fast/409667","id":"409667","label":"Cornell, Ezra, 1807-1874"} ] - def search(query, language: nil, replacements: {}, subauth: nil, context: false, performance_data: false) # rubocop:disable Metrics/ParameterLists - raise Qa::InvalidLinkedDataAuthority, "Unable to initialize linked data search sub-authority #{subauth}" unless subauth.nil? || subauthority?(subauth) - @context = context - @performance_data = performance_data - @language = language_service.preferred_language(user_language: language, authority_language: search_config.language) - url = authority_service.build_url(action_config: search_config, action: :search, action_request: query, substitutions: replacements, subauthority: subauth, language: @language) + def search(query, request_header: {}, language: nil, replacements: {}, subauth: nil, context: false, performance_data: false) # rubocop:disable Metrics/ParameterLists + request_header = build_request_header(language: language, replacements: replacements, subauth: subauth, context: context, performance_data: performance_data) if request_header.empty? + unpack_request_header(request_header) + raise Qa::InvalidLinkedDataAuthority, "Unable to initialize linked data search sub-authority #{subauthority}" unless subauthority.nil? || subauthority?(subauthority) + url = authority_service.build_url(action_config: search_config, action: :search, action_request: query, request_header: request_header) Rails.logger.info "QA Linked Data search url: #{url}" load_graph(url: url) normalize_results @@ -66,7 +67,7 @@ def normalize_results normalize_end_dt = Time.now.utc @normalize_time_s = normalize_end_dt - normalize_start_dt @normalized_size = json.to_s.size if performance_data? - Rails.logger.info("Time to convert data to json: #{normalize_time_s}s") + Rails.logger.info("Time to normalize data: #{normalize_time_s}s") json = append_performance_data(json) if performance_data? json end @@ -87,7 +88,16 @@ def map_results results_mapper_service.map_values(graph: filtered_graph, prefixes: prefixes, ldpath_map: ldpath_map, predicate_map: predicate_map, sort_key: :sort, - preferred_language: @language, context_map: context_map) + preferred_language: language, context_map: context_map) + end + + def unpack_request_header(request_header) + @subauthority = request_header.fetch(:subauthority, nil) + @context = request_header.fetch(:context, false) + @performance_data = request_header.fetch(:performance_data, false) + @language = language_service.preferred_language(user_language: request_header.fetch(:user_language, nil), + authority_language: search_config.language) + request_header[:language] = Array(@language) end def context_map @@ -180,6 +190,24 @@ def append_performance_data(results) total_time_s: (access_time_s + normalize_time_s) } { performance: performance, results: results } end + + # This is providing support for calling build_url with individual parameters instead of the request_header. + # This is deprecated and will be removed in the next major release. + def build_request_header(language:, replacements:, subauth:, context:, performance_data:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + unless language.blank? && replacements.blank? && subauth.blank? && !context && !performance_data + Qa.deprecation_warning( + in_msg: 'Qa::Authorities::LinkedData::SearchQuery', + msg: "individual attributes for options (e.g. replacements, subauth, language) are deprecated; use request_header instead" + ) + end + request_header = {} + request_header[:replacements] = replacements || {} + request_header[:subauthority] = subauth || nil + request_header[:language] = language || nil + request_header[:context] = context + request_header[:performance_data] = performance_data + request_header + end end end end diff --git a/spec/lib/authorities/linked_data/find_term_spec.rb b/spec/lib/authorities/linked_data/find_term_spec.rb index fc31c255..cf1f9eab 100644 --- a/spec/lib/authorities/linked_data/find_term_spec.rb +++ b/spec/lib/authorities/linked_data/find_term_spec.rb @@ -24,7 +24,7 @@ end context 'when set to true' do let :results do - lod_oclc.find('530369', performance_data: true) + lod_oclc.find('530369', request_header: { performance_data: true }) end it 'includes performance in return hash' do expect(results.keys).to match_array [:performance, :results] @@ -38,7 +38,7 @@ context 'when set to false' do let :results do - lod_oclc.find('530369', performance_data: false) + lod_oclc.find('530369', request_header: { performance_data: false }) end it 'does NOT include performance in return hash' do expect(results.keys).not_to include(:performance) @@ -114,7 +114,7 @@ .to_return(status: 200, body: webmock_fixture('lod_loc_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' }) end - let(:results) { lod_loc.find('sh 85118553', subauth: 'subjects') } + let(:results) { lod_loc.find('sh 85118553', request_header: { subauthority: 'subjects' }) } it 'has correct primary predicate values' do expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553' @@ -182,8 +182,8 @@ .to_return(status: 200, body: webmock_fixture('lod_loc_second_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' }) end - let(:results) { lod_loc.find('sh 85118553', subauth: 'subjects') } - let(:second_results) { lod_loc.find('sh 1234', subauth: 'subjects') } + let(:results) { lod_loc.find('sh 85118553', request_header: { subauthority: 'subjects' }) } + let(:second_results) { lod_loc.find('sh 1234', request_header: { subauthority: 'subjects' }) } it 'has correct primary predicate values for second request' do expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553' @@ -201,7 +201,7 @@ .to_return(status: 200, body: webmock_fixture('lod_loc_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' }) end - let(:results_without_blank) { lod_loc.find('sh85118553', subauth: 'subjects') } + let(:results_without_blank) { lod_loc.find('sh85118553', request_header: { subauthority: 'subjects' }) } it 'extracts correct uri' do expect(results_without_blank[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553' @@ -215,7 +215,7 @@ end it 'raises DataNormalizationError' do - expect { lod_loc.find('sh85118553', subauth: 'subjects') }.to raise_error Qa::DataNormalizationError, "Unable to extract URI based on ID: sh85118553" + expect { lod_loc.find('sh85118553', request_header: { subauthority: 'subjects' }) }.to raise_error Qa::DataNormalizationError, "Unable to extract URI based on ID: sh85118553" end end @@ -226,7 +226,7 @@ allow(lod_loc.term_config).to receive(:authority_name).and_return('ALT_LOC_AUTHORITY') end - let(:results) { lod_loc.find('sh 85118553', subauth: 'subjects') } + let(:results) { lod_loc.find('sh 85118553', request_header: { subauthority: 'subjects' }) } it 'does special processing to remove blank from id' do expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553' @@ -313,7 +313,7 @@ let :results do stub_request(:get, "http://aims.fao.org/aos/agrovoc/c_9513") .to_return(status: 200, body: webmock_fixture("lod_lang_term_enfr.rdf.xml"), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_lang_defaults.find('http://aims.fao.org/aos/agrovoc/c_9513', language: 'fr') + lod_lang_defaults.find('http://aims.fao.org/aos/agrovoc/c_9513', request_header: { language: 'fr' }) end it "is filtered to specified language" do expect(results[:label]).to eq ['Babeurre'] @@ -328,7 +328,7 @@ let :results do stub_request(:get, "http://aims.fao.org/aos/agrovoc/c_9513") .to_return(status: 200, body: webmock_fixture("lod_lang_term_enfr_noalt.rdf.xml"), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_lang_defaults.find('http://aims.fao.org/aos/agrovoc/c_9513', language: 'fr') + lod_lang_defaults.find('http://aims.fao.org/aos/agrovoc/c_9513', request_header: { language: 'fr' }) end it "is filtered to specified language" do expect(results[:label]).to eq ['Babeurre'] @@ -357,7 +357,7 @@ let :results do stub_request(:get, "http://aims.fao.org/aos/agrovoc/c_9513?lang=fr") .to_return(status: 200, body: webmock_fixture("lod_lang_term_fr.rdf.xml"), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_lang_param.find('http://aims.fao.org/aos/agrovoc/c_9513', replacements: { 'lang' => 'fr' }) + lod_lang_param.find('http://aims.fao.org/aos/agrovoc/c_9513', request_header: { replacements: { 'lang' => 'fr' } }) end it "is correctly parsed" do expect(results[:label]).to eq ['Babeurre'] diff --git a/spec/lib/authorities/linked_data/search_query_spec.rb b/spec/lib/authorities/linked_data/search_query_spec.rb index cec2e710..f7d6d164 100644 --- a/spec/lib/authorities/linked_data/search_query_spec.rb +++ b/spec/lib/authorities/linked_data/search_query_spec.rb @@ -11,7 +11,7 @@ end context 'when set to true' do let :results do - lod_oclc.search('cornell', subauth: 'personal_name', replacements: { 'maximumRecords' => '3' }, performance_data: true) + lod_oclc.search('cornell', request_header: { subauthority: 'personal_name', replacements: { 'maximumRecords' => '3' }, performance_data: true }) end it 'includes performance in return hash' do expect(results).to be_kind_of Hash @@ -27,7 +27,7 @@ context 'when set to false' do let :results do - lod_oclc.search('cornell', subauth: 'personal_name', replacements: { 'maximumRecords' => '3' }, performance_data: false) + lod_oclc.search('cornell', request_header: { subauthority: 'personal_name', replacements: { 'maximumRecords' => '3' }, performance_data: false }) end it 'does NOT include performance in return hash' do expect(results).to be_kind_of Array @@ -36,7 +36,7 @@ context 'when using default setting' do let :results do - lod_oclc.search('cornell', subauth: 'personal_name', replacements: { 'maximumRecords' => '3' }) + lod_oclc.search('cornell', request_header: { subauthority: 'personal_name', replacements: { 'maximumRecords' => '3' } }) end it 'does NOT include performance in return hash' do expect(results).to be_kind_of Array @@ -49,7 +49,7 @@ let :results do stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22supercalifragilisticexpialidocious%22&sortKeys=usage') .to_return(status: 200, body: webmock_fixture('lod_oclc_query_no_results.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_oclc.search('supercalifragilisticexpialidocious', replacements: { 'maximumRecords' => '3' }) + lod_oclc.search('supercalifragilisticexpialidocious', request_header: { replacements: { 'maximumRecords' => '3' } }) end it 'returns an empty array' do expect(results).to eq([]) @@ -60,7 +60,7 @@ let :results do stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage') .to_return(status: 200, body: webmock_fixture('lod_oclc_all_query_3_results.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_oclc.search('cornell', replacements: { 'maximumRecords' => '3' }) + lod_oclc.search('cornell', request_header: { replacements: { 'maximumRecords' => '3' } }) end it 'is correctly parsed' do expect(results.count).to eq(3) @@ -76,7 +76,7 @@ let :results do stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=oclc.personalName%20all%20%22supercalifragilisticexpialidocious%22&sortKeys=usage') .to_return(status: 200, body: webmock_fixture('lod_oclc_query_no_results.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_oclc.search('supercalifragilisticexpialidocious', subauth: 'personal_name', replacements: { 'maximumRecords' => '3' }) + lod_oclc.search('supercalifragilisticexpialidocious', request_header: { subauthority: 'personal_name', replacements: { 'maximumRecords' => '3' } }) end it 'returns an empty array' do expect(results).to eq([]) @@ -87,7 +87,7 @@ let :results do stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=oclc.personalName%20all%20%22cornell%22&sortKeys=usage') .to_return(status: 200, body: webmock_fixture('lod_oclc_personalName_query_3_results.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_oclc.search('cornell', subauth: 'personal_name', replacements: { 'maximumRecords' => '3' }) + lod_oclc.search('cornell', request_header: { subauthority: 'personal_name', replacements: { 'maximumRecords' => '3' } }) end it 'is correctly parsed' do expect(results.count).to eq(3) @@ -197,7 +197,7 @@ let :results do stub_request(:get, "http://localhost/test_default/search?query=milk") .to_return(status: 200, body: webmock_fixture("lod_lang_search_enfr.rdf.xml"), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_lang_defaults.search('milk', language: :fr) + lod_lang_defaults.search('milk', request_header: { language: :fr }) end it "is filtered to specified language" do expect(results.first[:label]).to eq('Babeurre (délicieux)') @@ -226,7 +226,7 @@ let :results do stub_request(:get, "http://localhost/test_replacement/search?query=milk&lang=fr") .to_return(status: 200, body: webmock_fixture("lod_lang_search_fr.rdf.xml"), headers: { 'Content-Type' => 'application/rdf+xml' }) - lod_lang_param.search("milk", replacements: { 'lang' => 'fr' }) + lod_lang_param.search("milk", request_header: { replacements: { 'lang' => 'fr' } }) end it "is correctly parsed" do expect(results.first[:label]).to eq('Babeurre (délicieux)') diff --git a/spec/services/linked_data/authority_url_service_spec.rb b/spec/services/linked_data/authority_url_service_spec.rb index bd6b9b98..07a73de4 100644 --- a/spec/services/linked_data/authority_url_service_spec.rb +++ b/spec/services/linked_data/authority_url_service_spec.rb @@ -14,13 +14,19 @@ end describe '.build_url' do + let(:request_header) do + { + subauthority: subauthority, + replacements: substitutions + } + end context 'when authority is not registered' do let(:authority) { :BAD_AUTHORITY } it 'raises error' do expected_error = Qa::InvalidLinkedDataAuthority expected_error_message = "Unable to initialize linked data authority 'BAD_AUTHORITY'" - expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) } + expect { described_class.build_url(action_config: action_config, action: action, action_request: action_request, request_header: request_header) } .to raise_error(expected_error, expected_error_message) end end @@ -33,7 +39,7 @@ skip "Pending better handling of unsupported subauthorities" expected_error = Qa::InvalidLinkedDataAuthority expected_error_message = "Unable to initialize linked data sub-authority BAD_SUBAUTHORITY" - expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) } + expect { described_class.build_url(action_config: action_config, action: action, action_request: action_request, request_header: request_header) } .to raise_error(expected_error, expected_error_message) end end @@ -44,7 +50,7 @@ it 'raises error' do expected_error = Qa::UnsupportedAction expected_error_message = "BAD_ACTION Not Supported - Action must be one of the supported actions (e.g. :term, :search)" - expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) } + expect { described_class.build_url(action_config: action_config, action: action, action_request: action_request, request_header: request_header) } .to raise_error(expected_error, expected_error_message) end end @@ -55,16 +61,16 @@ it 'raises error' do expected_error = Qa::IriTemplate::MissingParameter expected_error_message = "query is required, but missing" - expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) } + expect { described_class.build_url(action_config: action_config, action: action, action_request: action_request, request_header: request_header) } .to raise_error(expected_error, expected_error_message) end end - subject do - described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) - end - context 'when no errors' do + subject do + described_class.build_url(action_config: action_config, action: action, action_request: action_request, request_header: request_header) + end + context 'and performing search action' do context 'and all substitutions specified' do let(:substitutions) do diff --git a/spec/services/linked_data/request_header_service_spec.rb b/spec/services/linked_data/request_header_service_spec.rb new file mode 100644 index 00000000..fb9ae553 --- /dev/null +++ b/spec/services/linked_data/request_header_service_spec.rb @@ -0,0 +1,124 @@ +require 'spec_helper' + +RSpec.describe Qa::LinkedData::RequestHeaderService do + let(:request) { double } + + describe '#search_header' do + context 'when optional params are defined' do + let(:search_params) do + { + 'subauthority' => 'person', + 'lang' => 'sp', + 'maxRecords' => '4', + 'context' => 'true', + 'performance_data' => 'true' + }.with_indifferent_access + end + before { allow(request).to receive(:env).and_return('HTTP_ACCEPT_LANGUAGE' => 'de') } + + it 'uses passed in params' do + expected_results = + { + context: true, + performance_data: true, + replacements: { 'maxRecords' => '4' }, + subauthority: 'person', + user_language: ['sp'] + } + expect(described_class.new(request, search_params).search_header).to eq expected_results + end + end + + context 'when none of the optional params are defined' do + context 'and request does not define language' do + before { allow(request).to receive(:env).and_return('HTTP_ACCEPT_LANGUAGE' => nil) } + it 'returns defaults' do + expected_results = + { + context: false, + performance_data: false, + replacements: {}, + subauthority: nil, + user_language: nil + } + expect(described_class.new(request, {}).search_header).to eq expected_results + end + end + + context 'and request does define language' do + before { allow(request).to receive(:env).and_return('HTTP_ACCEPT_LANGUAGE' => 'de') } + it 'returns defaults with language set to request language' do + expected_results = + { + context: false, + performance_data: false, + replacements: {}, + subauthority: nil, + user_language: ['de'] + } + expect(described_class.new(request, {}).search_header).to eq expected_results + end + end + end + end + + describe '#fetch_header' do + context 'when optional params are defined' do + let(:fetch_params) do + { + 'subauthority' => 'person', + 'lang' => 'sp', + 'extra' => 'data', + 'even' => 'more data', + 'format' => 'n3', + 'performance_data' => 'true' + }.with_indifferent_access + end + before { allow(request).to receive(:env).and_return('HTTP_ACCEPT_LANGUAGE' => 'de') } + + it 'uses passed in params' do + expected_results = + { + format: 'n3', + performance_data: true, + replacements: { 'extra' => 'data', 'even' => 'more data' }, + subauthority: 'person', + user_language: ['sp'] + } + expect(described_class.new(request, fetch_params).fetch_header).to eq expected_results + end + end + + context 'when none of the optional params are defined' do + context 'and request does not define language' do + before { allow(request).to receive(:env).and_return('HTTP_ACCEPT_LANGUAGE' => nil) } + it 'returns defaults' do + expected_results = + { + format: 'json', + performance_data: false, + replacements: {}, + subauthority: nil, + user_language: nil + } + expect(described_class.new(request, {}).fetch_header).to eq expected_results + end + end + + context 'and request does define language' do + before { allow(request).to receive(:env).and_return('HTTP_ACCEPT_LANGUAGE' => 'de') } + it 'returns defaults with language set to request language' do + expected_results = + { + format: 'json', + performance_data: false, + replacements: {}, + subauthority: nil, + user_language: ['de'] + } + expect(described_class.new(request, {}).fetch_header).to eq expected_results + end + end + end + end +end