diff --git a/lib/resource_registry/setting.rb b/lib/resource_registry/setting.rb index 10faa38..6ca8eda 100644 --- a/lib/resource_registry/setting.rb +++ b/lib/resource_registry/setting.rb @@ -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 diff --git a/spec/resource_registry/setting_spec.rb b/spec/resource_registry/setting_spec.rb index 50094d8..97b9516 100644 --- a/spec/resource_registry/setting_spec.rb +++ b/spec/resource_registry/setting_spec.rb @@ -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) { {} } @@ -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 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