Hash.from_xml が `type="date"` の空白付与値で Date::Error を起こすバグを修正
Hash.from_xml は XML の type="date" 要素で、要素内容に前後の空白(改行・スペース)があると Date::Error をスローし、パース全体が失敗していました。今回の PR は、日付パーサーに strip を適用して空白を除去し、他の型キャストと同様の寛容性を持たせた修正です。
背景
Hash.from_xml が type="date" のノードで空白を含む文字列を処理すると、内部で ::Date.strptime(date, "%Y-%m-%d") が呼び出され、strptime が空白を許容しないため Date::Error: invalid date が発生していました。整数型(integer)や真偽値型(boolean)はそれぞれ to_i や to_s.strip で前後空白を除去しているため、日付だけが例外的に扱われていた点が不整合の原因でした。この不整合は、インデントされた XML や整形済み XML を人が読むケースで頻出し、実務上の障壁となっていました。
技術的な変更
日付パーサーに文字列化と空白除去を追加しました。activesupport/lib/active_support/xml_mini.rb の PARSING ハッシュ内で、"date" キーの Proc を次のように変更しています。
"date" => Proc.new { |date| ::Date.strptime(date.to_s.strip, "%Y-%m-%d") },
変更前は ::Date.strptime(date, "%Y-%m-%d") で、date がそのまま渡されていました。to_s.strip を付与するだけのシンプルな差分で、既存コードへの影響は最小限に抑えられます。空白がなくても to_s は無害に機能し、後方互換性は維持されます。
テストケースを追加し、修正の効果を検証しました。activesupport/test/core_ext/hash_ext_test.rb に新たに test_date_type_from_xml_with_surrounding_whitespace が導入され、インデントされた XML を渡した場合に正しく Date オブジェクトが生成されることを確認しています。また、activesupport/test/xml_mini_test.rb では既存の test_date が assert_raises(ArgumentError) に更新され、非文字列入力が to_s 後に ArgumentError として扱われることを明示しました。
def test_date_type_from_xml_with_surrounding_whitespace
xml = <<-EOT
<event>
<starts-on type="date">
2020-01-01
</starts-on>
</event>
EOT
assert_equal({ "starts_on" => Date.new(2020, 1, 1) }, Hash.from_xml(xml)["event"])
end
設計判断
一行の変更で型キャストの一貫性を確保するという方針が採られました。boolean パーサーが既に to_s.strip を使用している点から、日付パーサーでも同様の処理を導入することで API の振る舞いを統一し、利用者が期待する「空白は無視される」挙動を全型に対して提供しています。代替案として新しい設定キーや専用ラッパー関数の導入が議論された可能性がありますが、最小限の変更で既存コードとテストに影響を与えない選択が最も保守的で実用的と判断されました。
まとめ
この PR は Hash.from_xml の日付型パーサーに to_s.strip を追加し、インデントや改行を含む XML でも例外なくパースできるようにした修正です。既存の型キャストと振る舞いを揃えることで API の一貫性が向上し、利用者は余計な前処理なしに XML を安全に扱えるようになります。