Files

97 lines
3.2 KiB
Ruby

# frozen_string_literal: true
module Api
# The API has always spoken dashes, as in `planted-at` and `filter[owner-id]`, while Graphiti works in
# underscores. This puts the dashes back at the edges: request params go in underscored, and the
# attribute names in the response come out dasherised.
module DashedKeys
extend ActiveSupport::Concern
included do
before_action :underscore_request_keys
after_action :dasherize_response_keys
end
private
def underscore_request_keys
underscore_filters
params[:sort] = params[:sort].split(',').map { |field| underscore_field(field) }.join(',') if params[:sort]
underscore_fields
underscore_page_limit
underscore_attributes
end
def underscore_attributes
data = params[:data]
return unless data.respond_to?(:key?) && data[:attributes].respond_to?(:transform_keys)
data[:attributes] = data[:attributes].transform_keys(&:underscore)
end
# filter[crop]=1 has always meant the same as filter[crop_id]=1.
def underscore_filters
filters = params[:filter]
return unless filters
params[:filter] = filters.transform_keys do |name|
name = name.underscore
relationship_filter?(name, filters[name]) ? "#{name}_id" : name
end
end
def relationship_filter?(name, value)
known = resource_class.config[:filters]
!value.respond_to?(:keys) && !known.key?(name.to_sym) && known.key?(:"#{name}_id")
end
def underscore_field(field)
field.start_with?('-') ? "-#{field.delete_prefix('-').underscore}" : field.underscore
end
def underscore_fields
return unless params[:fields]
params[:fields] = params[:fields].transform_values { |names| names.to_s.underscore }
end
# Graphiti pages by number and size; clients of this API have always paged by `page[limit]` and
# `page[offset]`. An offset that lands on a page boundary is the same as a page number.
def underscore_page_limit
page = params[:page]
return unless page&.key?(:limit)
size = page.delete(:limit).to_i
page[:size] = size
offset = page[:offset]
return unless offset.present? && size.positive? && (offset.to_i % size).zero?
page.delete(:offset)
page[:number] = (offset.to_i / size) + 1
end
def dasherize_response_keys
return unless response.media_type == Mime[:jsonapi].to_s && response.body.present?
document = JSON.parse(response.body)
resources = [document['data'], document['included']].flatten.compact
resources.each { |resource| dasherize_attributes(resource) }
Array(document['errors']).each { |error| dasherize_pointer(error) }
response.body = JSON.generate(document)
end
def dasherize_attributes(resource)
return unless resource.is_a?(Hash) && resource['attributes'].is_a?(Hash)
resource['attributes'] = resource['attributes'].transform_keys(&:dasherize)
end
def dasherize_pointer(error)
pointer = error.dig('source', 'pointer')
return unless pointer
error['source']['pointer'] = pointer.sub(%r{\A/data/attributes/(.+)\z}) { "/data/attributes/#{Regexp.last_match(1).dasherize}" }
end
end
end