carwow-json_api_client 1.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +706 -0
  4. data/Rakefile +32 -0
  5. data/lib/json_api_client.rb +30 -0
  6. data/lib/json_api_client/associations.rb +8 -0
  7. data/lib/json_api_client/associations/base_association.rb +33 -0
  8. data/lib/json_api_client/associations/belongs_to.rb +31 -0
  9. data/lib/json_api_client/associations/has_many.rb +8 -0
  10. data/lib/json_api_client/associations/has_one.rb +16 -0
  11. data/lib/json_api_client/connection.rb +41 -0
  12. data/lib/json_api_client/error_collector.rb +91 -0
  13. data/lib/json_api_client/errors.rb +107 -0
  14. data/lib/json_api_client/formatter.rb +145 -0
  15. data/lib/json_api_client/helpers.rb +9 -0
  16. data/lib/json_api_client/helpers/associatable.rb +88 -0
  17. data/lib/json_api_client/helpers/callbacks.rb +27 -0
  18. data/lib/json_api_client/helpers/dirty.rb +75 -0
  19. data/lib/json_api_client/helpers/dynamic_attributes.rb +78 -0
  20. data/lib/json_api_client/helpers/uri.rb +9 -0
  21. data/lib/json_api_client/implementation.rb +12 -0
  22. data/lib/json_api_client/included_data.rb +58 -0
  23. data/lib/json_api_client/linking.rb +6 -0
  24. data/lib/json_api_client/linking/links.rb +22 -0
  25. data/lib/json_api_client/linking/top_level_links.rb +39 -0
  26. data/lib/json_api_client/meta_data.rb +19 -0
  27. data/lib/json_api_client/middleware.rb +7 -0
  28. data/lib/json_api_client/middleware/json_request.rb +26 -0
  29. data/lib/json_api_client/middleware/parse_json.rb +31 -0
  30. data/lib/json_api_client/middleware/status.rb +67 -0
  31. data/lib/json_api_client/paginating.rb +6 -0
  32. data/lib/json_api_client/paginating/nested_param_paginator.rb +140 -0
  33. data/lib/json_api_client/paginating/paginator.rb +89 -0
  34. data/lib/json_api_client/parsers.rb +5 -0
  35. data/lib/json_api_client/parsers/parser.rb +102 -0
  36. data/lib/json_api_client/query.rb +6 -0
  37. data/lib/json_api_client/query/builder.rb +239 -0
  38. data/lib/json_api_client/query/requestor.rb +73 -0
  39. data/lib/json_api_client/relationships.rb +6 -0
  40. data/lib/json_api_client/relationships/relations.rb +55 -0
  41. data/lib/json_api_client/relationships/top_level_relations.rb +30 -0
  42. data/lib/json_api_client/request_params.rb +57 -0
  43. data/lib/json_api_client/resource.rb +643 -0
  44. data/lib/json_api_client/result_set.rb +25 -0
  45. data/lib/json_api_client/schema.rb +154 -0
  46. data/lib/json_api_client/utils.rb +48 -0
  47. data/lib/json_api_client/version.rb +3 -0
  48. metadata +213 -0
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'JsonApiClient'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,30 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'json'
4
+ require 'addressable/uri'
5
+ require 'json_api_client/formatter'
6
+
7
+ module JsonApiClient
8
+ autoload :Associations, 'json_api_client/associations'
9
+ autoload :Attributes, 'json_api_client/attributes'
10
+ autoload :Connection, 'json_api_client/connection'
11
+ autoload :Errors, 'json_api_client/errors'
12
+ autoload :ErrorCollector, 'json_api_client/error_collector'
13
+ autoload :Helpers, 'json_api_client/helpers'
14
+ autoload :Implementation, 'json_api_client/implementation'
15
+ autoload :IncludedData, 'json_api_client/included_data'
16
+ autoload :Linking, 'json_api_client/linking'
17
+ autoload :Relationships, 'json_api_client/relationships'
18
+ autoload :LinkDefinition, 'json_api_client/link_definition'
19
+ autoload :MetaData, 'json_api_client/meta_data'
20
+ autoload :Middleware, 'json_api_client/middleware'
21
+ autoload :Paginating, 'json_api_client/paginating'
22
+ autoload :Parsers, 'json_api_client/parsers'
23
+ autoload :Query, 'json_api_client/query'
24
+ autoload :RequestParams, 'json_api_client/request_params'
25
+ autoload :Resource, 'json_api_client/resource'
26
+ autoload :ResultSet, 'json_api_client/result_set'
27
+ autoload :Schema, 'json_api_client/schema'
28
+ autoload :Utils, 'json_api_client/utils'
29
+ autoload :VERSION, 'json_api_client/version'
30
+ end
@@ -0,0 +1,8 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ autoload :BaseAssociation, 'json_api_client/associations/base_association'
4
+ autoload :BelongsTo, 'json_api_client/associations/belongs_to'
5
+ autoload :HasMany, 'json_api_client/associations/has_many'
6
+ autoload :HasOne, 'json_api_client/associations/has_one'
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ class BaseAssociation
4
+ attr_reader :attr_name, :klass, :options
5
+ def initialize(attr_name, klass, options = {})
6
+ @attr_name = attr_name
7
+ @klass = klass
8
+ @options = options
9
+ end
10
+
11
+ def association_class
12
+ @association_class ||= Utils.compute_type(klass, options.fetch(:class_name) do
13
+ attr_name.to_s.classify
14
+ end)
15
+ end
16
+
17
+ def data(url)
18
+ from_result_set(association_class.requestor.linked(url))
19
+ end
20
+
21
+ def from_result_set(result_set)
22
+ result_set.to_a
23
+ end
24
+
25
+ def load_records(data)
26
+ data.map do |d|
27
+ record_class = Utils.compute_type(klass, klass.key_formatter.unformat(d["type"]).classify)
28
+ record_class.load id: d["id"]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ module BelongsTo
4
+ class Association < BaseAssociation
5
+ include Helpers::URI
6
+
7
+ attr_reader :param
8
+
9
+ def initialize(attr_name, klass, options = {})
10
+ super
11
+ @param = options.fetch(:param, :"#{attr_name}_id").to_sym
12
+ @shallow_path = options.fetch(:shallow_path, false)
13
+ end
14
+
15
+ def shallow_path?
16
+ @shallow_path
17
+ end
18
+
19
+ def to_prefix_path(formatter)
20
+ "#{formatter.format(attr_name.to_s.pluralize)}/%{#{param}}"
21
+ end
22
+
23
+ def set_prefix_path(attrs, formatter)
24
+ return if shallow_path? && !attrs[param]
25
+ attrs[param] = encode_part(attrs[param]) if attrs.key?(param)
26
+ to_prefix_path(formatter) % attrs
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ module HasMany
4
+ class Association < BaseAssociation
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ module HasOne
4
+ class Association < BaseAssociation
5
+ def from_result_set(result_set)
6
+ result_set.first
7
+ end
8
+
9
+ def load_records(data)
10
+ record_class = Utils.compute_type(klass, klass.key_formatter.unformat(data["type"]).classify)
11
+ record_class.load id: data["id"]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ module JsonApiClient
2
+ class Connection
3
+
4
+ attr_reader :faraday
5
+
6
+ def initialize(options = {})
7
+ site = options.fetch(:site)
8
+ connection_options = options.slice(:proxy, :ssl, :request, :headers, :params)
9
+ adapter_options = Array(options.fetch(:adapter, Faraday.default_adapter))
10
+ status_middleware_options = {}
11
+ status_middleware_options[:custom_handlers] = options[:status_handlers] if options[:status_handlers].present?
12
+ @faraday = Faraday.new(site, connection_options) do |builder|
13
+ builder.request :json
14
+ builder.use Middleware::JsonRequest
15
+ builder.use Middleware::Status, status_middleware_options
16
+ builder.use Middleware::ParseJson
17
+ builder.use ::FaradayMiddleware::Gzip
18
+ builder.adapter(*adapter_options)
19
+ end
20
+ yield(self) if block_given?
21
+ end
22
+
23
+ # insert middleware before ParseJson - middleware executed in reverse order -
24
+ # inserted middleware will run after json parsed
25
+ def use(middleware, *args, &block)
26
+ return if faraday.builder.locked?
27
+ faraday.builder.insert_before(Middleware::ParseJson, middleware, *args, &block)
28
+ end
29
+
30
+ def delete(middleware)
31
+ faraday.builder.delete(middleware)
32
+ end
33
+
34
+ def run(request_method, path, params: nil, headers: {}, body: nil)
35
+ faraday.run_request(request_method, path, body, headers) do |request|
36
+ request.params.update(params) if params
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,91 @@
1
+ module JsonApiClient
2
+ class ErrorCollector < Array
3
+ class Error
4
+ delegate :[], to: :attrs
5
+
6
+ def initialize(attrs = {})
7
+ @attrs = (attrs || {}).with_indifferent_access
8
+ end
9
+
10
+ def id
11
+ attrs[:id]
12
+ end
13
+
14
+ def about
15
+ res = attrs.fetch(:links, {})
16
+ res ? res[:about] : {}
17
+ end
18
+
19
+ def status
20
+ attrs[:status]
21
+ end
22
+
23
+ def code
24
+ attrs[:code]
25
+ end
26
+
27
+ def title
28
+ attrs[:title]
29
+ end
30
+
31
+ def detail
32
+ attrs[:detail]
33
+ end
34
+
35
+ def source_parameter
36
+ source[:parameter]
37
+ end
38
+
39
+ def source_pointer
40
+ source[:pointer]
41
+ end
42
+
43
+ def error_key
44
+ if source_pointer && source_pointer != "/data"
45
+ source_pointer.split("/").last
46
+ else
47
+ "base"
48
+ end
49
+ end
50
+
51
+ def error_msg
52
+ msg = title || detail || "invalid"
53
+ if source_parameter
54
+ "#{source_parameter} #{msg}"
55
+ else
56
+ msg
57
+ end
58
+ end
59
+
60
+ def source
61
+ res = attrs.fetch(:source, {})
62
+ res ? res : {}
63
+ end
64
+
65
+ def meta
66
+ MetaData.new(attrs.fetch(:meta, {}))
67
+ end
68
+
69
+ protected
70
+
71
+ attr_reader :attrs
72
+ end
73
+
74
+ def initialize(error_data)
75
+ super(error_data.map do |data|
76
+ Error.new(data)
77
+ end)
78
+ end
79
+
80
+ def full_messages
81
+ map(&:title)
82
+ end
83
+
84
+ def [](source)
85
+ map do |error|
86
+ error.error_key == source
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,107 @@
1
+ require 'rack'
2
+
3
+ module JsonApiClient
4
+ module Errors
5
+ class ApiError < StandardError
6
+ attr_reader :env
7
+
8
+ def initialize(env, msg = nil)
9
+ @env = env
10
+ # Try to fetch json_api errors from response
11
+ msg = track_json_api_errors(msg)
12
+
13
+ super msg
14
+ end
15
+
16
+ private
17
+
18
+ # Try to fetch json_api errors from response
19
+ def track_json_api_errors(msg)
20
+ return msg unless env.try(:body).kind_of?(Hash) || env.body.key?('errors')
21
+
22
+ errors_msg = env.body['errors'].map { |e| e['title'] }.compact.join('; ').presence
23
+ return msg unless errors_msg
24
+
25
+ msg.nil? ? errors_msg : "#{msg} (#{errors_msg})"
26
+ # Just to be sure that it is back compatible
27
+ rescue StandardError
28
+ msg
29
+ end
30
+ end
31
+
32
+ class ClientError < ApiError
33
+ end
34
+
35
+ class ResourceImmutableError < StandardError
36
+ def initialize(msg = 'Resource immutable')
37
+ super msg
38
+ end
39
+ end
40
+
41
+ class AccessDenied < ClientError
42
+ end
43
+
44
+ class NotAuthorized < ClientError
45
+ end
46
+
47
+ class NotFound < ClientError
48
+ attr_reader :uri
49
+ def initialize(uri)
50
+ @uri = uri
51
+
52
+ msg = "Couldn't find resource at: #{uri.to_s}"
53
+ super nil, msg
54
+ end
55
+ end
56
+
57
+ class RequestTimeout < ClientError
58
+ end
59
+
60
+ class Conflict < ClientError
61
+ def initialize(env, msg = 'Resource already exists')
62
+ super env, msg
63
+ end
64
+ end
65
+
66
+ class TooManyRequests < ClientError
67
+ end
68
+
69
+ class ConnectionError < ApiError
70
+ end
71
+
72
+ class ServerError < ApiError
73
+ def initialize(env, msg = nil)
74
+ msg ||= begin
75
+ status = env.status
76
+ message = ::Rack::Utils::HTTP_STATUS_CODES[status]
77
+ "#{status} #{message}"
78
+ end
79
+
80
+ super env, msg
81
+ end
82
+ end
83
+
84
+ class InternalServerError < ServerError
85
+ end
86
+
87
+ class BadGateway < ServerError
88
+ end
89
+
90
+ class ServiceUnavailable < ServerError
91
+ end
92
+
93
+ class GatewayTimeout < ServerError
94
+ end
95
+
96
+ class UnexpectedStatus < ServerError
97
+ attr_reader :code, :uri
98
+ def initialize(code, uri)
99
+ @code = code
100
+ @uri = uri
101
+
102
+ msg = "Unexpected response status: #{code} from: #{uri.to_s}"
103
+ super nil, msg
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,145 @@
1
+ # Taken form jsonapi_resources formatter
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module JsonApiClient
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 = "JsonApiClient::#{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