carwow-json_api_client 1.19.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.
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