Skip to content

Commit

Permalink
Merge pull request #279 from samvera/refactor_opt_attrs
Browse files Browse the repository at this point in the history
refactor optional attributes for linked data search/fetch to pass as hash
  • Loading branch information
elrayle authored Nov 8, 2019
2 parents 559393b + 1d405a2 commit 56e683e
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 140 deletions.
67 changes: 16 additions & 51 deletions app/controllers/qa/linked_data_terms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
56 changes: 35 additions & 21 deletions app/services/qa/linked_data/authority_url_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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<Symbol>] 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<Symbol>] 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

Expand All @@ -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

Expand All @@ -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
Expand Down
97 changes: 97 additions & 0 deletions app/services/qa/linked_data/request_header_service.rb
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 56e683e

Please sign in to comment.