jsonapi-consumer 0.1.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.circleci/config.yml +27 -0
- data/.gitignore +1 -0
- data/Gemfile +6 -4
- data/README.md +9 -38
- data/Rakefile +17 -6
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/jsonapi-consumer.gemspec +10 -11
- data/lib/jsonapi/consumer/associations/base_association.rb +26 -0
- data/lib/jsonapi/consumer/associations/belongs_to.rb +30 -0
- data/lib/jsonapi/consumer/associations/has_many.rb +26 -0
- data/lib/jsonapi/consumer/associations/has_one.rb +19 -0
- data/lib/jsonapi/consumer/connection.rb +36 -0
- data/lib/jsonapi/consumer/error_collector.rb +91 -0
- data/lib/jsonapi/consumer/errors.rb +34 -76
- data/lib/jsonapi/consumer/formatter.rb +145 -0
- data/lib/jsonapi/consumer/helpers/callbacks.rb +27 -0
- data/lib/jsonapi/consumer/helpers/dirty.rb +71 -0
- data/lib/jsonapi/consumer/helpers/dynamic_attributes.rb +83 -0
- data/lib/jsonapi/consumer/helpers/uri.rb +9 -0
- data/lib/jsonapi/consumer/implementation.rb +12 -0
- data/lib/jsonapi/consumer/included_data.rb +49 -0
- data/lib/jsonapi/consumer/linking/links.rb +22 -0
- data/lib/jsonapi/consumer/linking/top_level_links.rb +39 -0
- data/lib/jsonapi/consumer/meta_data.rb +19 -0
- data/lib/jsonapi/consumer/middleware/json_request.rb +26 -0
- data/lib/jsonapi/consumer/middleware/parse_json.rb +22 -23
- data/lib/jsonapi/consumer/middleware/status.rb +41 -0
- data/lib/jsonapi/consumer/paginating/paginator.rb +89 -0
- data/lib/jsonapi/consumer/parsers/parser.rb +113 -0
- data/lib/jsonapi/consumer/query/builder.rb +212 -0
- data/lib/jsonapi/consumer/query/requestor.rb +67 -0
- data/lib/jsonapi/consumer/relationships/relations.rb +56 -0
- data/lib/jsonapi/consumer/relationships/top_level_relations.rb +30 -0
- data/lib/jsonapi/consumer/resource.rb +514 -54
- data/lib/jsonapi/consumer/result_set.rb +25 -0
- data/lib/jsonapi/consumer/schema.rb +153 -0
- data/lib/jsonapi/consumer/utils.rb +28 -0
- data/lib/jsonapi/consumer/version.rb +1 -1
- data/lib/jsonapi/consumer.rb +59 -34
- metadata +51 -111
- data/.rspec +0 -2
- data/CHANGELOG.md +0 -36
- data/lib/jsonapi/consumer/middleware/raise_error.rb +0 -21
- data/lib/jsonapi/consumer/middleware/request_headers.rb +0 -20
- data/lib/jsonapi/consumer/middleware/request_timeout.rb +0 -9
- data/lib/jsonapi/consumer/middleware.rb +0 -5
- data/lib/jsonapi/consumer/parser.rb +0 -75
- data/lib/jsonapi/consumer/query/base.rb +0 -34
- data/lib/jsonapi/consumer/query/create.rb +0 -9
- data/lib/jsonapi/consumer/query/delete.rb +0 -10
- data/lib/jsonapi/consumer/query/find.rb +0 -16
- data/lib/jsonapi/consumer/query/new.rb +0 -15
- data/lib/jsonapi/consumer/query/update.rb +0 -11
- data/lib/jsonapi/consumer/query.rb +0 -5
- data/lib/jsonapi/consumer/resource/association_concern.rb +0 -203
- data/lib/jsonapi/consumer/resource/attributes_concern.rb +0 -70
- data/lib/jsonapi/consumer/resource/connection_concern.rb +0 -99
- data/lib/jsonapi/consumer/resource/finders_concern.rb +0 -28
- data/lib/jsonapi/consumer/resource/object_build_concern.rb +0 -28
- data/lib/jsonapi/consumer/resource/serializer_concern.rb +0 -63
- data/spec/fixtures/.gitkeep +0 -0
- data/spec/fixtures/resources.rb +0 -45
- data/spec/fixtures/responses.rb +0 -64
- data/spec/jsonapi/consumer/associations_spec.rb +0 -166
- data/spec/jsonapi/consumer/attributes_spec.rb +0 -27
- data/spec/jsonapi/consumer/connection_spec.rb +0 -147
- data/spec/jsonapi/consumer/error_handling_spec.rb +0 -37
- data/spec/jsonapi/consumer/object_build_spec.rb +0 -20
- data/spec/jsonapi/consumer/parser_spec.rb +0 -39
- data/spec/jsonapi/consumer/resource_spec.rb +0 -62
- data/spec/jsonapi/consumer/serializer_spec.rb +0 -41
- data/spec/spec_helper.rb +0 -97
- data/spec/support/.gitkeep +0 -0
- 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,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
|
2
|
-
|
3
|
-
|
4
|
-
class ParseJson < Faraday::Response::Middleware
|
1
|
+
module JSONAPI::Consumer
|
2
|
+
module Middleware
|
3
|
+
class ParseJson < Faraday::Middleware
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
+
private
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def parse(body)
|
17
|
+
::JSON.parse(body) unless body.strip.empty?
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
28
|
-
|
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
|