require "ostruct"
module FmRest
module Spyke
Metadata = Struct.new(:messages, :script, :data_info) do
alias_method :scripts, :script
end
class DataInfo < OpenStruct
def total_record_count; totalRecordCount; end
def found_count; foundCount; end
def returned_count; returnedCount; end
end
ScriptResult = Struct.new(:result, :error) do
def success?; error == "0"; end
def error?; !success?; end
end
class SpykeFormatter < ::Faraday::Middleware
SINGLE_RECORD_RE = %r(/records/\d+\z).freeze
MULTIPLE_RECORDS_RE = %r(/records\z).freeze
CONTAINER_RE = %r(/records/\d+/containers/[^/]+/\d+\z).freeze
FIND_RECORDS_RE = %r(/_find\b).freeze
SCRIPT_REQUEST_RE = %r(/script/[^/]+\z).freeze
VALIDATION_ERROR_RANGE = 500..599
def initialize(app, model)
super(app)
@model = model
end
def on_complete(env)
return unless env.body.is_a?(Hash)
json = env.body
case
when single_record_request?(env)
env.body = prepare_single_record(json)
when multiple_records_request?(env), find_request?(env)
env.body = prepare_collection(json)
when create_request?(env), update_request?(env), delete_request?(env), container_upload_request?(env)
env.body = prepare_save_response(json)
when execute_script_request?(env)
env.body = build_base_hash(json)
else
env.body = build_base_hash(json)
end
end
private
def prepare_save_response(json)
response = json[:response]
data = {}
data[:__mod_id] = response[:modId] if response[:modId]
data[:__record_id] = response[:recordId] if response[:recordId]
data[:__new_portal_record_info] = response[:newPortalRecordInfo] if response[:newPortalRecordInfo]
build_base_hash(json, true).merge!(data: data)
end
def prepare_single_record(json)
data =
json[:response][:data] &&
prepare_record_data(json[:response][:data].first)
build_base_hash(json).merge!(data: data)
end
def prepare_collection(json)
data =
json[:response][:data] &&
json[:response][:data].map { |record_data| prepare_record_data(record_data) }
build_base_hash(json).merge!(data: data)
end
def build_base_hash(json, include_errors = false)
{
metadata: Metadata.new(
prepare_messages(json),
prepare_script_results(json),
prepare_data_info(json)
).freeze,
errors: include_errors ? prepare_errors(json) : {}
}
end
def prepare_messages(json)
return [] unless json[:messages]
json[:messages].map { |m| OpenStruct.new(m).freeze }.freeze
end
def prepare_script_results(json)
results = {}
[:prerequest, :presort].each do |s|
if json[:response][:"scriptError.#{s}"]
results[s] = ScriptResult.new(
json[:response][:"scriptResult.#{s}"],
json[:response][:"scriptError.#{s}"]
).freeze
end
end
if json[:response][:scriptError]
results[:after] = ScriptResult.new(
json[:response][:scriptResult],
json[:response][:scriptError]
).freeze
end
results.present? ? OpenStruct.new(results).freeze : nil
end
def prepare_data_info(json)
data_info = json[:response] && json[:response][:dataInfo]
return nil unless data_info.present?
DataInfo.new(data_info).freeze
end
def prepare_errors(json)
return {} if json[:messages][0][:code].to_i == 0
json[:messages].each_with_object(base: []) do |message, hash|
next unless VALIDATION_ERROR_RANGE.include?(message[:code].to_i)
hash[:base] << "#{message[:message]} (#{message[:code]})"
end
end
def prepare_record_data(json_data)
out = { __record_id: json_data[:recordId], __mod_id: json_data[:modId] }
out.merge!(json_data[:fieldData])
out.merge!(prepare_portal_data(json_data[:portalData])) if json_data[:portalData]
out
end
def prepare_portal_data(json_portal_data)
json_portal_data.each_with_object({}) do |(portal_name, portal_records), out|
portal_options = @model.portal_options[portal_name.to_s] || {}
portal_builder = portal_options[:name] && @model.associations[portal_options[:name].to_sym]
portal_class = portal_builder && portal_builder.klass
portal_attributes = (portal_class && portal_class.mapped_attributes.values) || []
out[portal_name] =
portal_records.map do |portal_fields|
attributes = { __record_id: portal_fields[:recordId] }
attributes[:__mod_id] = portal_fields[:modId] if portal_fields[:modId]
qualifier = portal_options[:attribute_prefix] || portal_name
qualifier_matcher = /\A#{qualifier}::/
portal_fields.each do |k, v|
next if :recordId == k || :modId == k
stripped_field_name = k.to_s.gsub(qualifier_matcher, "")
if portal_attributes.include?(stripped_field_name)
attributes[stripped_field_name.to_sym] = v
else
attributes[k.to_sym] = v
end
end
attributes
end
end
end
def single_record_request?(env)
env.method == :get && env.url.path.match(SINGLE_RECORD_RE)
end
def multiple_records_request?(env)
env.method == :get && env.url.path.match(MULTIPLE_RECORDS_RE)
end
def find_request?(env)
env.method == :post && env.url.path.match(FIND_RECORDS_RE)
end
def update_request?(env)
env.method == :patch && env.url.path.match(SINGLE_RECORD_RE)
end
def create_request?(env)
env.method == :post && env.url.path.match(MULTIPLE_RECORDS_RE)
end
def container_upload_request?(env)
env.method == :post && env.url.path.match(CONTAINER_RE)
end
def delete_request?(env)
env.method == :delete && env.url.path.match(SINGLE_RECORD_RE)
end
def execute_script_request?(env)
env.method == :get && env.url.path.match(SCRIPT_REQUEST_RE)
end
end
end
end