mirror of
https://github.com/we-promise/sure.git
synced 2026-05-29 23:39:03 +00:00
fix: preserve wrapped rule import json values (#1358)
This commit is contained in:
@@ -289,9 +289,14 @@ class RuleImport < Import
|
||||
def parse_json_safely(json_string, field_name)
|
||||
return [] if json_string.blank?
|
||||
|
||||
# Clean up the JSON string - remove extra escaping that might come from CSV parsing
|
||||
cleaned = json_string.to_s.strip
|
||||
|
||||
# Most API-created rows already store valid JSON. Parse them as-is before
|
||||
# falling back to the legacy cleanup path for older malformed payloads.
|
||||
parse_json_payload(cleaned, normalize_legacy_strings: false)
|
||||
rescue JSON::ParserError
|
||||
# Clean up the JSON string - remove extra escaping that might come from CSV parsing
|
||||
|
||||
# Remove surrounding quotes if present (both single and double)
|
||||
cleaned = cleaned.gsub(/\A["']+|["']+\z/, "")
|
||||
|
||||
@@ -321,8 +326,46 @@ class RuleImport < Import
|
||||
end
|
||||
|
||||
# Try parsing
|
||||
JSON.parse(cleaned)
|
||||
parse_json_payload(cleaned, normalize_legacy_strings: true)
|
||||
rescue JSON::ParserError => e
|
||||
raise JSON::ParserError.new("Invalid JSON in #{field_name}: #{e.message}. Raw value: #{json_string.inspect}")
|
||||
end
|
||||
|
||||
def parse_json_payload(payload, normalize_legacy_strings:)
|
||||
parsed = JSON.parse(payload)
|
||||
parsed = JSON.parse(parsed) if wrapped_json_payload?(parsed)
|
||||
|
||||
normalize_json_values(parsed, normalize_legacy_strings:)
|
||||
end
|
||||
|
||||
def wrapped_json_payload?(value)
|
||||
return false unless value.is_a?(String)
|
||||
|
||||
stripped_value = value.strip
|
||||
stripped_value.start_with?("[", "{")
|
||||
end
|
||||
|
||||
def normalize_json_values(value, normalize_legacy_strings:)
|
||||
case value
|
||||
when Array
|
||||
value.map { |item| normalize_json_values(item, normalize_legacy_strings:) }
|
||||
when Hash
|
||||
value.transform_values { |item| normalize_json_values(item, normalize_legacy_strings:) }
|
||||
when String
|
||||
normalized = value
|
||||
.gsub(/\\u([0-9a-fA-F]{4})/i) { [ $1.to_i(16) ].pack("U") }
|
||||
.gsub('\\"', '"')
|
||||
|
||||
if normalize_legacy_strings
|
||||
normalized = normalized
|
||||
.gsub("\\n", "\n")
|
||||
.gsub("\\r", "\r")
|
||||
.gsub("\\t", "\t")
|
||||
end
|
||||
|
||||
normalized
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -233,4 +233,83 @@ class RuleImportTest < ActiveSupport::TestCase
|
||||
import.send(:import!)
|
||||
end
|
||||
end
|
||||
|
||||
test "imports valid JSON conditions whose values contain escaped quotes" do
|
||||
csv = CSV.generate do |out|
|
||||
out << %w[name resource_type active effective_date conditions actions]
|
||||
out << [
|
||||
"Quoted value rule",
|
||||
"transaction",
|
||||
true,
|
||||
"",
|
||||
[ { condition_type: "transaction_name", operator: "=", value: "ני\\u0022ע-קניה" } ].to_json,
|
||||
[ { action_type: "set_transaction_name", value: "Quoted transfer" } ].to_json
|
||||
]
|
||||
end
|
||||
|
||||
import = @family.imports.create!(type: "RuleImport", raw_file_str: csv, col_sep: ",")
|
||||
import.generate_rows_from_csv
|
||||
|
||||
assert_nothing_raised do
|
||||
import.send(:import!)
|
||||
end
|
||||
|
||||
rule = Rule.find_by!(family: @family, name: "Quoted value rule")
|
||||
condition = rule.conditions.first
|
||||
assert_equal "ני\"ע-קניה", condition.value
|
||||
end
|
||||
|
||||
test "imports wrapped JSON payloads from legacy rows" do
|
||||
wrapped_conditions = [ { condition_type: "transaction_name", operator: "like", value: "legacy grocery" } ].to_json.to_json
|
||||
wrapped_actions = [ { action_type: "set_transaction_category", value: "Groceries" } ].to_json.to_json
|
||||
|
||||
csv = CSV.generate do |out|
|
||||
out << %w[name resource_type active effective_date conditions actions]
|
||||
out << [
|
||||
"Wrapped payload rule",
|
||||
"transaction",
|
||||
true,
|
||||
"",
|
||||
wrapped_conditions,
|
||||
wrapped_actions
|
||||
]
|
||||
end
|
||||
|
||||
import = @family.imports.create!(type: "RuleImport", raw_file_str: csv, col_sep: ",")
|
||||
import.generate_rows_from_csv
|
||||
|
||||
assert_nothing_raised do
|
||||
import.send(:import!)
|
||||
end
|
||||
|
||||
rule = Rule.find_by!(family: @family, name: "Wrapped payload rule")
|
||||
condition = rule.conditions.first
|
||||
action = rule.actions.first
|
||||
|
||||
assert_equal "legacy grocery", condition.value
|
||||
assert_equal @category.id, action.value
|
||||
end
|
||||
|
||||
test "preserves literal backslash sequences in valid JSON values" do
|
||||
csv = CSV.generate do |out|
|
||||
out << %w[name resource_type active effective_date conditions actions]
|
||||
out << [
|
||||
"Literal backslash rule",
|
||||
"transaction",
|
||||
true,
|
||||
"",
|
||||
[ { condition_type: "transaction_name", operator: "=", value: 'C:\new\test' } ].to_json,
|
||||
[ { action_type: "set_transaction_name", value: "Path rule" } ].to_json
|
||||
]
|
||||
end
|
||||
|
||||
import = @family.imports.create!(type: "RuleImport", raw_file_str: csv, col_sep: ",")
|
||||
import.generate_rows_from_csv
|
||||
import.send(:import!)
|
||||
|
||||
rule = Rule.find_by!(family: @family, name: "Literal backslash rule")
|
||||
condition = rule.conditions.first
|
||||
|
||||
assert_equal 'C:\new\test', condition.value
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user