jsonapi-consumer 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +27 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +6 -4
  5. data/README.md +9 -38
  6. data/Rakefile +17 -6
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/jsonapi-consumer.gemspec +10 -11
  10. data/lib/jsonapi/consumer/associations/base_association.rb +26 -0
  11. data/lib/jsonapi/consumer/associations/belongs_to.rb +30 -0
  12. data/lib/jsonapi/consumer/associations/has_many.rb +26 -0
  13. data/lib/jsonapi/consumer/associations/has_one.rb +19 -0
  14. data/lib/jsonapi/consumer/connection.rb +36 -0
  15. data/lib/jsonapi/consumer/error_collector.rb +91 -0
  16. data/lib/jsonapi/consumer/errors.rb +34 -76
  17. data/lib/jsonapi/consumer/formatter.rb +145 -0
  18. data/lib/jsonapi/consumer/helpers/callbacks.rb +27 -0
  19. data/lib/jsonapi/consumer/helpers/dirty.rb +71 -0
  20. data/lib/jsonapi/consumer/helpers/dynamic_attributes.rb +83 -0
  21. data/lib/jsonapi/consumer/helpers/uri.rb +9 -0
  22. data/lib/jsonapi/consumer/implementation.rb +12 -0
  23. data/lib/jsonapi/consumer/included_data.rb +49 -0
  24. data/lib/jsonapi/consumer/linking/links.rb +22 -0
  25. data/lib/jsonapi/consumer/linking/top_level_links.rb +39 -0
  26. data/lib/jsonapi/consumer/meta_data.rb +19 -0
  27. data/lib/jsonapi/consumer/middleware/json_request.rb +26 -0
  28. data/lib/jsonapi/consumer/middleware/parse_json.rb +22 -23
  29. data/lib/jsonapi/consumer/middleware/status.rb +41 -0
  30. data/lib/jsonapi/consumer/paginating/paginator.rb +89 -0
  31. data/lib/jsonapi/consumer/parsers/parser.rb +113 -0
  32. data/lib/jsonapi/consumer/query/builder.rb +212 -0
  33. data/lib/jsonapi/consumer/query/requestor.rb +67 -0
  34. data/lib/jsonapi/consumer/relationships/relations.rb +56 -0
  35. data/lib/jsonapi/consumer/relationships/top_level_relations.rb +30 -0
  36. data/lib/jsonapi/consumer/resource.rb +514 -54
  37. data/lib/jsonapi/consumer/result_set.rb +25 -0
  38. data/lib/jsonapi/consumer/schema.rb +153 -0
  39. data/lib/jsonapi/consumer/utils.rb +28 -0
  40. data/lib/jsonapi/consumer/version.rb +1 -1
  41. data/lib/jsonapi/consumer.rb +59 -34
  42. metadata +51 -111
  43. data/.rspec +0 -2
  44. data/CHANGELOG.md +0 -36
  45. data/lib/jsonapi/consumer/middleware/raise_error.rb +0 -21
  46. data/lib/jsonapi/consumer/middleware/request_headers.rb +0 -20
  47. data/lib/jsonapi/consumer/middleware/request_timeout.rb +0 -9
  48. data/lib/jsonapi/consumer/middleware.rb +0 -5
  49. data/lib/jsonapi/consumer/parser.rb +0 -75
  50. data/lib/jsonapi/consumer/query/base.rb +0 -34
  51. data/lib/jsonapi/consumer/query/create.rb +0 -9
  52. data/lib/jsonapi/consumer/query/delete.rb +0 -10
  53. data/lib/jsonapi/consumer/query/find.rb +0 -16
  54. data/lib/jsonapi/consumer/query/new.rb +0 -15
  55. data/lib/jsonapi/consumer/query/update.rb +0 -11
  56. data/lib/jsonapi/consumer/query.rb +0 -5
  57. data/lib/jsonapi/consumer/resource/association_concern.rb +0 -203
  58. data/lib/jsonapi/consumer/resource/attributes_concern.rb +0 -70
  59. data/lib/jsonapi/consumer/resource/connection_concern.rb +0 -99
  60. data/lib/jsonapi/consumer/resource/finders_concern.rb +0 -28
  61. data/lib/jsonapi/consumer/resource/object_build_concern.rb +0 -28
  62. data/lib/jsonapi/consumer/resource/serializer_concern.rb +0 -63
  63. data/spec/fixtures/.gitkeep +0 -0
  64. data/spec/fixtures/resources.rb +0 -45
  65. data/spec/fixtures/responses.rb +0 -64
  66. data/spec/jsonapi/consumer/associations_spec.rb +0 -166
  67. data/spec/jsonapi/consumer/attributes_spec.rb +0 -27
  68. data/spec/jsonapi/consumer/connection_spec.rb +0 -147
  69. data/spec/jsonapi/consumer/error_handling_spec.rb +0 -37
  70. data/spec/jsonapi/consumer/object_build_spec.rb +0 -20
  71. data/spec/jsonapi/consumer/parser_spec.rb +0 -39
  72. data/spec/jsonapi/consumer/resource_spec.rb +0 -62
  73. data/spec/jsonapi/consumer/serializer_spec.rb +0 -41
  74. data/spec/spec_helper.rb +0 -97
  75. data/spec/support/.gitkeep +0 -0
  76. data/spec/support/load_fixtures.rb +0 -4
@@ -0,0 +1,145 @@
1
+ # Taken form jsonapi_resources formatter
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module JSONAPI::Consumer
6
+ class Formatter
7
+ class << self
8
+ def format(arg)
9
+ arg.to_s
10
+ end
11
+
12
+ def unformat(arg)
13
+ # We call to_s() here so that unformat consistently returns a string
14
+ # (instead of a symbol) regardless which Formatter subclass it is called on
15
+ arg.to_s
16
+ end
17
+
18
+ def formatter_for(format)
19
+ formatter_class_name = "JSONAPI::Consumer::#{format.to_s.camelize}Formatter"
20
+ formatter_class_name.safe_constantize
21
+ end
22
+ end
23
+ end
24
+
25
+ class KeyFormatter < Formatter
26
+ class << self
27
+ def format(key)
28
+ super
29
+ end
30
+
31
+ def format_keys(hash)
32
+ Hash[
33
+ hash.map do |key, value|
34
+ [format(key).to_sym, value]
35
+ end
36
+ ]
37
+ end
38
+
39
+ def unformat(formatted_key)
40
+ super
41
+ end
42
+ end
43
+ end
44
+
45
+ class RouteFormatter < Formatter
46
+ class << self
47
+ def format(route)
48
+ super
49
+ end
50
+
51
+ def unformat(formatted_route)
52
+ super
53
+ end
54
+ end
55
+ end
56
+
57
+ class ValueFormatter < Formatter
58
+ class << self
59
+ def format(raw_value)
60
+ super(raw_value)
61
+ end
62
+
63
+ def unformat(value)
64
+ super(value)
65
+ end
66
+
67
+ def value_formatter_for(type)
68
+ formatter_name = "#{type.to_s.camelize}Value"
69
+ formatter_for(formatter_name)
70
+ end
71
+ end
72
+ end
73
+
74
+ class UnderscoredKeyFormatter < KeyFormatter
75
+ end
76
+
77
+ class CamelizedKeyFormatter < KeyFormatter
78
+ class << self
79
+ def format(key)
80
+ super.camelize(:lower)
81
+ end
82
+
83
+ def unformat(formatted_key)
84
+ formatted_key.to_s.underscore
85
+ end
86
+ end
87
+ end
88
+
89
+ class DasherizedKeyFormatter < KeyFormatter
90
+ class << self
91
+ def format(key)
92
+ super.dasherize
93
+ end
94
+
95
+ def unformat(formatted_key)
96
+ formatted_key.to_s.underscore
97
+ end
98
+ end
99
+ end
100
+
101
+ class DefaultValueFormatter < ValueFormatter
102
+ class << self
103
+ def format(raw_value)
104
+ raw_value
105
+ end
106
+ end
107
+ end
108
+
109
+ class IdValueFormatter < ValueFormatter
110
+ class << self
111
+ def format(raw_value)
112
+ return if raw_value.nil?
113
+ raw_value.to_s
114
+ end
115
+ end
116
+ end
117
+
118
+ class UnderscoredRouteFormatter < RouteFormatter
119
+ end
120
+
121
+ class CamelizedRouteFormatter < RouteFormatter
122
+ class << self
123
+ def format(route)
124
+ super.camelize(:lower)
125
+ end
126
+
127
+ def unformat(formatted_route)
128
+ formatted_route.to_s.underscore
129
+ end
130
+ end
131
+ end
132
+
133
+ class DasherizedRouteFormatter < RouteFormatter
134
+ class << self
135
+ def format(route)
136
+ super.dasherize
137
+ end
138
+
139
+ def unformat(formatted_route)
140
+ formatted_route.to_s.underscore
141
+ end
142
+ end
143
+ end
144
+
145
+ end
@@ -0,0 +1,27 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module Callbacks
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ extend ActiveModel::Callbacks
8
+ define_model_callbacks :save, :destroy, :create, :update
9
+ end
10
+
11
+ def save
12
+ run_callbacks :save do
13
+ run_callbacks (persisted? ? :update : :create) do
14
+ super
15
+ end
16
+ end
17
+ end
18
+
19
+ def destroy
20
+ run_callbacks :destroy do
21
+ super
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module Dirty
4
+
5
+ def changed?
6
+ changed_attributes.present?
7
+ end
8
+
9
+ def changed
10
+ changed_attributes.keys
11
+ end
12
+
13
+ def changed_attributes
14
+ @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
15
+ end
16
+
17
+ def clear_changes_information
18
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
19
+ end
20
+
21
+ def set_all_attributes_dirty
22
+ attributes.each do |k, v|
23
+ set_attribute_was(k, v)
24
+ end
25
+ end
26
+
27
+ def attribute_will_change!(attr)
28
+ return if attribute_changed?(attr)
29
+ set_attribute_was(attr, attributes[attr])
30
+ end
31
+
32
+ def set_attribute_was(attr, value)
33
+ begin
34
+ value = value.duplicable? ? value.clone : value
35
+ changed_attributes[attr] = value
36
+ rescue TypeError, NoMethodError
37
+ end
38
+ end
39
+
40
+ def attribute_was(attr) # :nodoc:
41
+ attribute_changed?(attr) ? changed_attributes[attr] : attributes[attr]
42
+ end
43
+
44
+ def attribute_changed?(attr)
45
+ changed.include?(attr.to_s)
46
+ end
47
+
48
+ def attribute_change(attr)
49
+ [changed_attributes[attr], attributes[attr]] if attribute_changed?(attr)
50
+ end
51
+
52
+ protected
53
+
54
+ def method_missing(method, *args, &block)
55
+ if method.to_s =~ /^(.*)_changed\?$/
56
+ has_attribute?($1) ? attribute_changed?($1) : nil
57
+ elsif method.to_s =~ /^(.*)_was$/
58
+ has_attribute?($1) ? attribute_was($1) : nil
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ def set_attribute(name, value)
65
+ attribute_will_change!(name) if value != attributes[name] || !attributes.has_key?(name)
66
+ super
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,83 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module DynamicAttributes
4
+
5
+ def attributes
6
+ @attributes
7
+ end
8
+
9
+ def attributes=(attrs = {})
10
+ @attributes ||= ActiveSupport::HashWithIndifferentAccess.new
11
+
12
+ return @attributes unless attrs.present?
13
+ attrs.each do |key, value|
14
+ send("#{key}=", value)
15
+ end
16
+ end
17
+
18
+ def [](key)
19
+ read_attribute(key)
20
+ end
21
+
22
+ def []=(key, value)
23
+ set_attribute(key, value)
24
+ end
25
+
26
+ def respond_to_missing?(method, include_private = false)
27
+ if has_attribute?(method) || method.to_s.end_with?('=')
28
+ true
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def has_attribute?(attr_name)
35
+ attributes.has_key?(attr_name)
36
+ end
37
+
38
+ protected
39
+
40
+ def method_missing(method, *args, &block)
41
+ if has_attribute?(method)
42
+ self.class.class_eval do
43
+ define_method(method) do
44
+ attributes[method]
45
+ end
46
+ end
47
+ return send(method)
48
+ end
49
+
50
+ normalized_method = safe_key_formatter.unformat(method.to_s)
51
+
52
+ if normalized_method.end_with?('=')
53
+ set_attribute(normalized_method[0..-2], args.first)
54
+ else
55
+ super
56
+ end
57
+ end
58
+
59
+ def read_attribute(name)
60
+ attributes.fetch(name, nil)
61
+ end
62
+
63
+ def set_attribute(name, value)
64
+ attributes[name] = value
65
+ end
66
+
67
+ def safe_key_formatter
68
+ @safe_key_formatter ||= (key_formatter || DefaultKeyFormatter.new)
69
+ end
70
+
71
+ def key_formatter
72
+ self.class.respond_to?(:key_formatter) && self.class.key_formatter
73
+ end
74
+
75
+ class DefaultKeyFormatter
76
+ def unformat(method)
77
+ method.to_s
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,9 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module URI
4
+ def encode_part(part)
5
+ Addressable::URI.encode_component(part, Addressable::URI::CharacterClasses::UNRESERVED)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module JSONAPI::Consumer
2
+ class Implementation
3
+ attr_reader :version, :meta
4
+
5
+ def initialize(data)
6
+ # If the version member is not present, clients should assume the server implements at least version 1.0 of the specification.
7
+ @version = data.fetch("version", "1.0")
8
+
9
+ @meta = MetaData.new(data.fetch("meta", {}))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ module JSONAPI::Consumer
2
+ class IncludedData
3
+ attr_reader :data
4
+
5
+ def initialize(result_set, data)
6
+ record_class = result_set.record_class
7
+ grouped_data = data.group_by{|datum| datum["type"]}
8
+ @data = grouped_data.inject({}) do |h, (type, records)|
9
+ klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify)
10
+ h[type] = records.map do |datum|
11
+ params = klass.parser.parameters_from_resource(datum)
12
+ resource = klass.load(params)
13
+ resource.last_result_set = result_set
14
+ resource
15
+ end.index_by(&:id)
16
+ h
17
+ end
18
+ end
19
+
20
+ def data_for(method_name, definition)
21
+ # this method only returns an array. It's up to the caller to decide if it's going to return
22
+ # just the first element if it's a has_one relationship.
23
+ # If data is defined, pull the record from the included data
24
+ defined_data = definition["data"]
25
+ return nil unless defined_data
26
+ [defined_data].flatten.map do |link_def|
27
+ # should return a resource record of some type for this linked document
28
+ # even if there's no matching record included.
29
+ if data[link_def["type"]]
30
+ record_for(link_def)
31
+ else
32
+ # if there's no matching record in included then go and get it given the data
33
+ link_def["type"].underscore.classify.constantize.find(link_def["id"]).first
34
+ end
35
+ end
36
+ end
37
+
38
+ def has_link?(name)
39
+ data.has_key?(name.to_s)
40
+ end
41
+
42
+ private
43
+
44
+ # should return a resource record of some type for this linked document
45
+ def record_for(link_def)
46
+ data[link_def["type"]][link_def["id"]]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ module JSONAPI::Consumer
2
+ module Linking
3
+ class Links
4
+ include Helpers::DynamicAttributes
5
+
6
+ def initialize(links)
7
+ self.attributes = links
8
+ end
9
+
10
+ def present?
11
+ attributes.present?
12
+ end
13
+
14
+ protected
15
+
16
+ def set_attribute(name, value)
17
+ attributes[name] = value
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module JSONAPI::Consumer
2
+ module Linking
3
+ class TopLevelLinks
4
+
5
+ attr_reader :links, :record_class
6
+
7
+ def initialize(record_class, links)
8
+ @links = links
9
+ @record_class = record_class
10
+ end
11
+
12
+ def respond_to_missing?(method, include_private = false)
13
+ links.has_key?(method.to_s) || super
14
+ end
15
+
16
+ def method_missing(method, *args)
17
+ if respond_to_missing?(method)
18
+ fetch_link(method)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def link_url_for(link_name)
25
+ link_definition = links.fetch(link_name.to_s)
26
+ if link_definition.is_a?(Hash)
27
+ link_definition["href"]
28
+ else
29
+ link_definition
30
+ end
31
+ end
32
+
33
+ def fetch_link(link_name)
34
+ return unless respond_to_missing?(link_name)
35
+ record_class.requestor.linked(link_url_for(link_name))
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ module JSONAPI::Consumer
2
+ class MetaData
3
+ include Helpers::DynamicAttributes
4
+
5
+ attr_accessor :record_class
6
+
7
+ def initialize(data, record_class = nil)
8
+ self.record_class = record_class
9
+ self.attributes = data
10
+ end
11
+
12
+ protected
13
+
14
+ def key_formatter
15
+ record_class && record_class.key_formatter
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module JSONAPI::Consumer
2
+ module Middleware
3
+ class JsonRequest < Faraday::Middleware
4
+ def call(environment)
5
+ accept_header = update_accept_header(environment[:request_headers])
6
+
7
+ environment[:request_headers]["Content-Type"] = 'application/vnd.api+json'
8
+ environment[:request_headers]["Accept"] = accept_header
9
+ @app.call(environment)
10
+ end
11
+
12
+ private
13
+
14
+ def update_accept_header(headers)
15
+ return 'application/vnd.api+json' if headers["Accept"].nil?
16
+ accept_params = headers["Accept"].split(",")
17
+
18
+ unless accept_params.include?('application/vnd.api+json')
19
+ accept_params.unshift('application/vnd.api+json')
20
+ end
21
+
22
+ accept_params.join(",")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,32 +1,31 @@
1
- module JSONAPI::Consumer::Middleware
2
- # Credit to chingor13/json_api_client for middleware.
3
- #
4
- class ParseJson < Faraday::Response::Middleware
1
+ module JSONAPI::Consumer
2
+ module Middleware
3
+ class ParseJson < Faraday::Middleware
5
4
 
6
- def call(environment)
7
- @app.call(environment).on_complete do |env|
8
- if process_response_type?(response_type(env))
9
- env[:raw_body] = env[:body]
10
- env[:body] = parse(env[:body])
5
+ def call(environment)
6
+ @app.call(environment).on_complete do |env|
7
+ if process_response_type?(response_type(env))
8
+ env[:raw_body] = env[:body]
9
+ env[:body] = parse(env[:body])
10
+ end
11
11
  end
12
12
  end
13
- end
14
13
 
15
- private
14
+ private
16
15
 
17
- def parse(body)
18
- ::JSON.parse(body) unless body.strip.empty?
19
- end
16
+ def parse(body)
17
+ ::JSON.parse(body) unless body.strip.empty?
18
+ end
20
19
 
21
- def response_type(env)
22
- type = env[:response_headers]['Content-Type'].to_s
23
- type = type.split(';', 2).first if type.index(';')
24
- type
25
- end
20
+ def response_type(env)
21
+ type = env[:response_headers]['Content-Type'].to_s
22
+ type = type.split(';', 2).first if type.index(';')
23
+ type
24
+ end
26
25
 
27
- def process_response_type?(type)
28
- !!type.match(/\bjson$/)
26
+ def process_response_type?(type)
27
+ !!type.match(/\bjson$/)
28
+ end
29
29
  end
30
30
  end
31
- end
32
-
31
+ end
@@ -0,0 +1,41 @@
1
+ module JSONAPI::Consumer
2
+ module Middleware
3
+ class Status < Faraday::Middleware
4
+ def call(environment)
5
+ @app.call(environment).on_complete do |env|
6
+ handle_status(env[:status], env)
7
+
8
+ # look for meta[:status]
9
+ if env[:body].is_a?(Hash)
10
+ code = env[:body].fetch("meta", {}).fetch("status", 200).to_i
11
+ handle_status(code, env)
12
+ end
13
+ end
14
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError
15
+ raise Errors::ConnectionError, environment
16
+ end
17
+
18
+ protected
19
+
20
+ def handle_status(code, env)
21
+ case code
22
+ when 200..399
23
+ when 401
24
+ raise Errors::NotAuthorized, env
25
+ when 403
26
+ raise Errors::AccessDenied, env
27
+ when 404
28
+ raise Errors::NotFound, env[:url]
29
+ when 409
30
+ raise Errors::Conflict, env
31
+ when 400..499
32
+ # some other error
33
+ when 500..599
34
+ raise Errors::ServerError, env
35
+ else
36
+ raise Errors::UnexpectedStatus.new(code, env[:url])
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,89 @@
1
+ module JSONAPI::Consumer
2
+ module Paginating
3
+ class Paginator
4
+ class_attribute :page_param,
5
+ :per_page_param
6
+
7
+ self.page_param = "page"
8
+ self.per_page_param = "per_page"
9
+
10
+ attr_reader :params, :result_set, :links
11
+
12
+ def initialize(result_set, data)
13
+ @params = params_for_uri(result_set.uri)
14
+ @result_set = result_set
15
+ @links = data["links"]
16
+ end
17
+
18
+ def next
19
+ result_set.links.fetch_link("next")
20
+ end
21
+
22
+ def prev
23
+ result_set.links.fetch_link("prev")
24
+ end
25
+
26
+ def first
27
+ result_set.links.fetch_link("first")
28
+ end
29
+
30
+ def last
31
+ result_set.links.fetch_link("last")
32
+ end
33
+
34
+ def total_pages
35
+ if links["last"]
36
+ uri = result_set.links.link_url_for("last")
37
+ last_params = params_for_uri(uri)
38
+ last_params.fetch(page_param) do
39
+ current_page
40
+ end.to_i
41
+ else
42
+ current_page
43
+ end
44
+ end
45
+
46
+ # this number may be off
47
+ def total_entries
48
+ per_page * total_pages
49
+ end
50
+ def total_count; total_entries; end
51
+
52
+ def offset
53
+ per_page * (current_page - 1)
54
+ end
55
+
56
+ def per_page
57
+ params.fetch(per_page_param) do
58
+ result_set.length
59
+ end.to_i
60
+ end
61
+
62
+ def current_page
63
+ params.fetch(page_param, 1).to_i
64
+ end
65
+
66
+ def out_of_bounds?
67
+ current_page > total_pages
68
+ end
69
+
70
+ def previous_page
71
+ current_page > 1 ? (current_page - 1) : nil
72
+ end
73
+
74
+ def next_page
75
+ current_page < total_pages ? (current_page + 1) : nil
76
+ end
77
+
78
+ alias limit_value per_page
79
+
80
+ protected
81
+
82
+ def params_for_uri(uri)
83
+ return {} unless uri
84
+ uri = Addressable::URI.parse(uri)
85
+ uri.query_values || {}
86
+ end
87
+ end
88
+ end
89
+ end