diff --git a/mustermann/lib/mustermann/expander.rb b/mustermann/lib/mustermann/expander.rb index fe8cc29..f9d5c1a 100644 --- a/mustermann/lib/mustermann/expander.rb +++ b/mustermann/lib/mustermann/expander.rb @@ -2,6 +2,7 @@ require 'mustermann/ast/expander' require 'mustermann/caster' require 'mustermann' +require 'addressable/uri' module Mustermann # Allows fine-grained control over pattern expansion. @@ -194,8 +195,9 @@ def slice(hash, keys) def append(uri, values) return uri unless values and values.any? - entries = values.map { |pair| pair.map { |e| @api_expander.escape(e, also_escape: /[\/\?#\&\=%]/) }.join(?=) } - "#{ uri }#{ uri[??]??&:?? }#{ entries.join(?&) }" + Addressable::URI.encode( + [uri, uri.include?('?') ? '&' : '?', QueryStringBuilder.new(values).build].join + ) end def map_values(values) @@ -205,5 +207,45 @@ def map_values(values) end private :with_rest, :slice, :append, :caster, :map_values, :split_values + + class QueryStringBuilder # :nodoc: + attr_reader :query + + def initialize(query = {}) + @query = query + end + + def build + query.map { |key, value| + if value.is_a?(Array) + build_array_query(key, value) + elsif value.is_a?(Hash) + build_hash_query(key, value) + else + "#{key}=#{value}" + end + }.join('&') + end + + private + + def build_array_query(key, values) + query_key = "#{key}[]" + values.map { |v| [query_key, v].join('=') }.join('&') + end + + def build_hash_query(key, values) + values.map { |h_key, h_value| + query_key = "#{key}[#{h_key}]" + if h_value.is_a?(Hash) + build_hash_query(query_key, h_value) + elsif h_value.is_a?(Array) + build_array_query(query_key, h_value) + else + [query_key, h_value].join('=') + end + }.join('&') + end + end end end diff --git a/mustermann/spec/expander_spec.rb b/mustermann/spec/expander_spec.rb index 342ed94..0748556 100644 --- a/mustermann/spec/expander_spec.rb +++ b/mustermann/spec/expander_spec.rb @@ -70,6 +70,9 @@ subject(:expander) { Mustermann::Expander.new('/:a', additional_values: :append) } example { expander.expand(a: ?a).should be == '/a' } example { expander.expand(a: ?a, b: ?b).should be == '/a?b=b' } + example { expander.expand(a: ?a, b: [?b]).should be == '/a?b%5B%5D=b' } + example { expander.expand(a: ?a, b: {c: ?c}).should be == '/a?b%5Bc%5D=c' } + example { expander.expand(a: ?a, b: {c: [1, 2]}).should be == '/a?b%5Bc%5D%5B%5D=1&b%5Bc%5D%5B%5D=2' } example { expect { expander.expand(b: ?b) }.to raise_error(Mustermann::ExpandError) } end end