Skip to content
50 changes: 50 additions & 0 deletions lib/resource_registry/setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,56 @@ class Setting < Dry::Struct
# @return [ResourceRegistry::Meta]
attribute :meta, ResourceRegistry::Meta.optional.meta(omittable: true)

# Override accessor to normalize date range strings
def item
convert_range_strings(self[:item])
end

private

def convert_range_strings(value)
return value unless value.is_a?(String)
return value unless value.include?('..')

range, success = attempt_to_convert_to_date_range(value)
success ? range : value
end

def attempt_to_convert_to_date_range(value)
split_result = value.split('..', 2).map(&:strip)
return [value, false] if split_result.size != 2

begin_str, end_str = split_result
return [value, false] unless looks_like_date?(begin_str) && looks_like_date?(end_str)

begin_date = parse_date(begin_str)
end_date = parse_date(end_str)
return [value, false] unless begin_date && end_date

[(begin_date..end_date), true]
end

DATE_PATTERNS = [
/^\d{4}-\d{1,2}-\d{1,2}$/,
/^\d{4}\/\d{1,2}\/\d{1,2}$/,
/^\d{1,2}\/\d{1,2}\/\d{4}$/
].freeze

def looks_like_date?(str)
DATE_PATTERNS.any? { |re| str.match?(re) }
end

def parse_date(str)
case str
when /^\d{4}-\d{1,2}-\d{1,2}$/
Date.strptime(str, "%Y-%m-%d")
when /^\d{4}\/\d{1,2}\/\d{1,2}$/
Date.strptime(str, "%Y/%m/%d")
when /^\d{1,2}\/\d{1,2}\/\d{4}$/
Date.strptime(str, "%m/%d/%Y")
else
nil
end
end
end
end
69 changes: 68 additions & 1 deletion spec/resource_registry/setting_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def call(params)
let(:optional_params) { { meta: meta, options: options } }
let(:all_params) { required_params.merge(optional_params) }


context "Validation with invalid input" do
context "Given hash params are nissing required attributes" do
let(:error_hash) { {} }
Expand Down Expand Up @@ -68,4 +67,72 @@ def call(params)
expect(setting[:item].call(setting[:options])).to eq greet_message
end
end

context "#item date range normalization" do
it "parses YYYY-MM-DD..YYYY-MM-DD as a date range" do
setting = described_class.new(key: :period, item: "2025-1-1..2025-12-1")
expect(setting.item).to eq(Date.new(2025, 1, 1)..Date.new(2025, 12, 1))
end

it "parses YYYY/MM/DD..YYYY/MM/DD as a date range" do
setting = described_class.new(key: :period, item: "2025/01/01..2025/12/1")
expect(setting.item).to eq(Date.new(2025, 1, 1)..Date.new(2025, 12, 1))
end

it "parses MM/DD/YYYY..MM/DD/YYYY as a date range" do
setting = described_class.new(key: :period, item: "01/01/2025..12/01/2025")
expect(setting.item).to eq(Date.new(2025, 1, 1)..Date.new(2025, 12, 1))
end

it "returns nil for non-date ranges" do
setting = described_class.new(key: :period, item: "1..10")
expect(setting.item).to eq("1..10")
end

it "returns a Range<Date> as-is" do
range = Date.new(2025, 1, 1)..Date.new(2025, 12, 1)
setting = described_class.new(key: :period, item: range)
expect(setting.item).to eq(range)
end

it "returns non-range, non-date string as-is" do
setting = described_class.new(key: :period, item: "not a range")
expect(setting.item).to eq("not a range")
end

it "handles strings with multiple '..' safely" do
setting = described_class.new(key: :bad_range, item: "2025-01-01..2025-12-01..oops")
expect(setting.item).to eq("2025-01-01..2025-12-01..oops")
end

it "returns original string if one side of the range is missing" do
setting = described_class.new(key: :partial_range, item: "2025-01-01..")
expect(setting.item).to eq("2025-01-01..")
end

it "returns original string if both sides are empty" do
setting = described_class.new(key: :empty_range, item: "..")
expect(setting.item).to eq("..")
end

it "parses date range with extra whitespace around range" do
setting = described_class.new(key: :whitespace, item: " 2025-01-01 .. 2025-12-01 ")
expect(setting.item).to eq(Date.new(2025, 1, 1)..Date.new(2025, 12, 1))
end

it "returns nil as-is when item is nil" do
setting = described_class.new(key: :period, item: nil)
expect(setting.item).to be_nil
end

it "returns non-string value that does not respond to include? as-is" do
setting = described_class.new(key: :period, item: 12345)
expect(setting.item).to eq(12345)
end

it "returns original string if range has more than two parts" do
setting = described_class.new(key: :bad_range, item: "2025-01-01..2025-12-01..2025-12-31")
expect(setting.item).to eq("2025-01-01..2025-12-01..2025-12-31")
end
end
end