From 1e5a12d25c842c217a65acf6a6a6de8608c0e4da Mon Sep 17 00:00:00 2001 From: Shota Iguchi Date: Sat, 24 Feb 2018 04:34:34 +0900 Subject: [PATCH 1/3] Add nested get query parameter expandation --- mustermann/lib/mustermann/expander.rb | 43 +++++++++++++++++++++++++-- mustermann/spec/expander_spec.rb | 3 ++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/mustermann/lib/mustermann/expander.rb b/mustermann/lib/mustermann/expander.rb index fe8cc29..0720475 100644 --- a/mustermann/lib/mustermann/expander.rb +++ b/mustermann/lib/mustermann/expander.rb @@ -194,8 +194,7 @@ 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(?&) }" + [uri, QueryStringBuilder.new(values).build].join('?') end def map_values(values) @@ -205,5 +204,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..4959dda 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[]=b' } + example { expander.expand(a: ?a, b: {c: ?c}).should be == '/a?b[c]=c' } + example { expander.expand(a: ?a, b: {c: [1, 2]}).should be == '/a?b[c][]=1&b[c][]=2' } example { expect { expander.expand(b: ?b) }.to raise_error(Mustermann::ExpandError) } end end From f17ecc3db823e01d41fcb3b6bdf8a35cb119c22b Mon Sep 17 00:00:00 2001 From: Shota Iguchi Date: Mon, 30 Apr 2018 01:10:04 +0800 Subject: [PATCH 2/3] Ensure url encode --- mustermann/lib/mustermann/expander.rb | 3 ++- mustermann/spec/expander_spec.rb | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mustermann/lib/mustermann/expander.rb b/mustermann/lib/mustermann/expander.rb index 0720475..236ad36 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,7 +195,7 @@ def slice(hash, keys) def append(uri, values) return uri unless values and values.any? - [uri, QueryStringBuilder.new(values).build].join('?') + Addressable::URI.encode([uri, QueryStringBuilder.new(values).build].join('?')) end def map_values(values) diff --git a/mustermann/spec/expander_spec.rb b/mustermann/spec/expander_spec.rb index 4959dda..0748556 100644 --- a/mustermann/spec/expander_spec.rb +++ b/mustermann/spec/expander_spec.rb @@ -70,9 +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[]=b' } - example { expander.expand(a: ?a, b: {c: ?c}).should be == '/a?b[c]=c' } - example { expander.expand(a: ?a, b: {c: [1, 2]}).should be == '/a?b[c][]=1&b[c][]=2' } + 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 From 2addf75b1bd100ee167195e96f7d4494d3497a5c Mon Sep 17 00:00:00 2001 From: Shota Iguchi Date: Mon, 30 Apr 2018 01:17:16 +0800 Subject: [PATCH 3/3] Add to check uri string includes ? --- mustermann/lib/mustermann/expander.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mustermann/lib/mustermann/expander.rb b/mustermann/lib/mustermann/expander.rb index 236ad36..f9d5c1a 100644 --- a/mustermann/lib/mustermann/expander.rb +++ b/mustermann/lib/mustermann/expander.rb @@ -195,7 +195,9 @@ def slice(hash, keys) def append(uri, values) return uri unless values and values.any? - Addressable::URI.encode([uri, QueryStringBuilder.new(values).build].join('?')) + Addressable::URI.encode( + [uri, uri.include?('?') ? '&' : '?', QueryStringBuilder.new(values).build].join + ) end def map_values(values)