diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index 6fd0f7c..f8567f7 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -282,6 +282,43 @@ def registry # :nodoc: # attr_reader :fragment + # + # Deconstructs the URI into a hash for pattern matching. + # + # Returns a hash with keys: +:scheme+, +:userinfo+, +:host+, +:port+, + # +:path+, +:query+, +:fragment+, and +:params+. + # + # The +:params+ key contains the parsed query string as a hash with + # symbol keys, and is only computed when explicitly requested to avoid + # unnecessary parsing. + # + # uri = URI("http://example.com/path?foo=bar&baz=qux") + # uri.deconstruct_keys(nil) + # # => {:scheme=>"http", :userinfo=>nil, :host=>"example.com", :port=>80, + # # :path=>"/path", :query=>"foo=bar&baz=qux", :fragment=>nil, + # # :params=>{:foo=>"bar", :baz=>"qux"}} + # + # case uri + # in scheme: "http", host: "example.com", params: { foo: "bar" } + # # matches + # end + # + def deconstruct_keys(keys) + h = { + scheme: @scheme, + userinfo: userinfo, + host: @host, + port: @port, + path: @path, + query: @query, + fragment: @fragment + } + if @query && (keys.nil? || keys.include?(:params)) + h[:params] = URI.decode_www_form(@query).to_h { |k, v| [k.to_sym, v] } + end + h + end + # Returns the parser to be used. # # Unless the +parser+ is defined, DEFAULT_PARSER is used. diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb index 94eea71..93d90af 100644 --- a/test/uri/test_generic.rb +++ b/test/uri/test_generic.rb @@ -1116,4 +1116,59 @@ def with_proxy_env_case_sensitive(h, &b) yield h end + def test_deconstruct_keys + uri = URI("http://example.com:8080/path?foo=bar&baz=qux#fragment") + keys = uri.deconstruct_keys(nil) + assert_equal "http", keys[:scheme] + assert_equal "example.com", keys[:host] + assert_equal 8080, keys[:port] + assert_equal "/path", keys[:path] + assert_equal "foo=bar&baz=qux", keys[:query] + assert_equal "fragment", keys[:fragment] + assert_equal({ foo: "bar", baz: "qux" }, keys[:params]) + end + + def test_deconstruct_keys_no_params + uri = URI("http://example.com/path") + keys = uri.deconstruct_keys([:scheme, :host, :path]) + assert_equal "http", keys[:scheme] + assert_equal "example.com", keys[:host] + assert_equal "/path", keys[:path] + refute keys.key?(:params) + end + + def test_pattern_matching_scheme_and_host + begin + uri = URI("https://example.com/path") + result = instance_eval <<~RUBY, __FILE__, __LINE__ + 1 + case uri + in scheme: "https", host: "example.com" + "matched" + else + "no match" + end + RUBY + assert_equal "matched", result + rescue SyntaxError + omit "Pattern matching not supported in Ruby < 2.7" + end + end + + def test_pattern_matching_with_params + begin + uri = URI("http://example.com/search?q=ruby&lang=en") + result = instance_eval <<~RUBY, __FILE__, __LINE__ + 1 + case uri + in scheme: "http", path: "/search", params: { q: "ruby" } + "search query" + else + "no match" + end + RUBY + assert_equal "search query", result + rescue SyntaxError + omit "Pattern matching not supported in Ruby < 2.7" + end + end + end