trailer_vote-api 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +29 -0
  4. data/CHANGELOG.md +56 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +77 -0
  7. data/README.md +188 -0
  8. data/Rakefile +12 -0
  9. data/bin/console +15 -0
  10. data/bin/setup +8 -0
  11. data/lib/trailer_vote/api.rb +67 -0
  12. data/lib/trailer_vote/api/autoload.rb +28 -0
  13. data/lib/trailer_vote/api/composable/common.rb +91 -0
  14. data/lib/trailer_vote/api/composable/get.rb +66 -0
  15. data/lib/trailer_vote/api/configuration.rb +69 -0
  16. data/lib/trailer_vote/api/errors.rb +113 -0
  17. data/lib/trailer_vote/api/fallback_content_types.rb +50 -0
  18. data/lib/trailer_vote/api/issue.rb +29 -0
  19. data/lib/trailer_vote/api/issue/create.rb +72 -0
  20. data/lib/trailer_vote/api/issue/find.rb +42 -0
  21. data/lib/trailer_vote/api/links.rb +54 -0
  22. data/lib/trailer_vote/api/place.rb +22 -0
  23. data/lib/trailer_vote/api/place/children.rb +33 -0
  24. data/lib/trailer_vote/api/place/children/urls.rb +67 -0
  25. data/lib/trailer_vote/api/place/create.rb +83 -0
  26. data/lib/trailer_vote/api/place/find.rb +60 -0
  27. data/lib/trailer_vote/api/place/lookup.rb +72 -0
  28. data/lib/trailer_vote/api/product.rb +31 -0
  29. data/lib/trailer_vote/api/product/create.rb +63 -0
  30. data/lib/trailer_vote/api/product/find.rb +55 -0
  31. data/lib/trailer_vote/api/product/image.rb +36 -0
  32. data/lib/trailer_vote/api/product/image/create.rb +83 -0
  33. data/lib/trailer_vote/api/product/image/find.rb +54 -0
  34. data/lib/trailer_vote/api/product/image/urls.rb +66 -0
  35. data/lib/trailer_vote/api/product/lookup.rb +95 -0
  36. data/lib/trailer_vote/api/product/place.rb +38 -0
  37. data/lib/trailer_vote/api/product/place/link.rb +75 -0
  38. data/lib/trailer_vote/api/product/update.rb +80 -0
  39. data/lib/trailer_vote/api/product/video.rb +36 -0
  40. data/lib/trailer_vote/api/product/video/create.rb +80 -0
  41. data/lib/trailer_vote/api/product/video/find.rb +56 -0
  42. data/lib/trailer_vote/api/product/video/urls.rb +66 -0
  43. data/lib/trailer_vote/api/push_recipe_android.rb +37 -0
  44. data/lib/trailer_vote/api/push_recipe_ios.rb +37 -0
  45. data/lib/trailer_vote/api/type_registry.rb +95 -0
  46. data/lib/trailer_vote/api/version.rb +7 -0
  47. data/lib/trailer_vote/api/vista_config.rb +37 -0
  48. data/trailer_vote-api.gemspec +45 -0
  49. metadata +261 -0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'trailer_vote/api'
4
+
5
+ require 'trailer_vote/api/issue'
6
+ require 'trailer_vote/api/issue/create'
7
+ require 'trailer_vote/api/issue/find'
8
+ require 'trailer_vote/api/place'
9
+ require 'trailer_vote/api/place/children'
10
+ require 'trailer_vote/api/place/children/urls'
11
+ require 'trailer_vote/api/place/create'
12
+ require 'trailer_vote/api/place/find'
13
+ require 'trailer_vote/api/place/lookup'
14
+ require 'trailer_vote/api/product'
15
+ require 'trailer_vote/api/product/create'
16
+ require 'trailer_vote/api/product/find'
17
+ require 'trailer_vote/api/product/lookup'
18
+ require 'trailer_vote/api/product/image'
19
+ require 'trailer_vote/api/product/image/create'
20
+ require 'trailer_vote/api/product/image/find'
21
+ require 'trailer_vote/api/product/place'
22
+ require 'trailer_vote/api/product/place/link'
23
+ require 'trailer_vote/api/product/video'
24
+ require 'trailer_vote/api/product/video/create'
25
+ require 'trailer_vote/api/product/video/find'
26
+ require 'trailer_vote/api/push_recipe_ios'
27
+ require 'trailer_vote/api/push_recipe_android'
28
+ require 'trailer_vote/api/vista_config'
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrailerVote
4
+ module Api
5
+ module Composable
6
+ module Common
7
+
8
+ def self.included(base)
9
+ base.class_eval do
10
+ private
11
+
12
+ attr_accessor :configuration
13
+ end
14
+ end
15
+
16
+ # Execute the call (and all dependent calls)
17
+ #
18
+ # @see #forward_klazz
19
+ # @see #redirect_klazz
20
+ def call(**_opts)
21
+ raise format('Missing implementation of #args in %<name>s', self.class.name)
22
+ end
23
+
24
+ private
25
+
26
+ def resolve_client
27
+ configuration.client
28
+ end
29
+
30
+ # Class used to branch forward to in case of a HTTP 307 or 308.
31
+ #
32
+ # @see #branch
33
+ # @see #forward
34
+ #
35
+ # @private
36
+ # @return [Class]
37
+ def forward_klazz
38
+ self.class
39
+ end
40
+
41
+ # Class used to branch redirect to in case of a HTTP 200, 201, 204, 301, 302, 303 or 304.
42
+ #
43
+ # When the status does not allow for redirecting, instead sends the retrieved result to the redirected class.
44
+ #
45
+ # @see #branch
46
+ # @see #redirect
47
+ #
48
+ # @private
49
+ # @return [Class]
50
+ def redirect_klazz
51
+ self.class
52
+ end
53
+
54
+ def guard_network_errors
55
+ yield
56
+ rescue HTTP::ConnectionError => err
57
+ raise ConnectionError, err
58
+ rescue HTTP::TimeoutError => err
59
+ raise TimeoutError, err
60
+ rescue HTTP::Error => err
61
+ raise NetworkError, err
62
+ end
63
+
64
+ def branch(result, data: nil)
65
+ raise_on_error(result)
66
+ forward(result, data: data) || redirect(result)
67
+ end
68
+
69
+ def raise_on_error(result)
70
+ return unless result.status.client_error? || result.status.server_error?
71
+ TrailerVote::Api.raise_error result
72
+ end
73
+
74
+ def forward(result, data:)
75
+ return unless [307, 308].include?(result.status)
76
+ forward_klazz.new(configuration: configuration)
77
+ .call(data: data, url: redirected_url(result))
78
+ end
79
+
80
+ def redirect(result)
81
+ redirect_klazz.new(configuration: configuration, result: result)
82
+ .call(url: redirected_url(result))
83
+ end
84
+
85
+ def redirected_url(result)
86
+ result['Location'] || result['Content-Location']
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'trailer_vote/api/composable/common'
4
+ require 'trailer_vote/api/links'
5
+
6
+ module TrailerVote
7
+ module Api
8
+ module Composable
9
+ module Get
10
+ def self.included(base)
11
+ base.include Common
12
+ base.class_eval do
13
+ attr_accessor :result
14
+ end
15
+ end
16
+
17
+ # Return the {Links} for the result
18
+ # @return [Links] the links
19
+ def links
20
+ # TODO: or headers
21
+ @links ||= Links.new(data[:_links])
22
+ end
23
+
24
+ # Decode the result via {TypeRegistry}. This also takes care of MediaTypes validation.
25
+ #
26
+ # @see #decode
27
+ # @return [Hash, Array] the decoded response
28
+ def to_h
29
+ @to_h ||= TrailerVote::Api.decode(call.result)
30
+ end
31
+
32
+ # Return the HTTP status
33
+ # @return [Numeric] the HTTP status
34
+ def to_i
35
+ call.result.status
36
+ end
37
+
38
+ # Return the ETag value
39
+ # @return [String, NilClass] the etag or nil
40
+ def etag
41
+ call.result[Headers::ETAG]
42
+ end
43
+
44
+ # Return the decoded result inner object
45
+ # @return [Object] the inner object
46
+ def data
47
+ raise format('Missing implementation of #data in %<name>s', self.class.name)
48
+ end
49
+
50
+ private
51
+
52
+ def ok?
53
+ result&.status == 200
54
+ end
55
+
56
+ def redirecting?
57
+ [301, 302, 303, 307, 308].include?(result&.status)
58
+ end
59
+
60
+ def redirect_to
61
+ self.class
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'trailer_vote/media_types'
4
+ require 'trailer_vote/api/composable/get'
5
+
6
+ module TrailerVote
7
+ module Api
8
+
9
+ module_function
10
+
11
+ def configure(url:, key:, secret:)
12
+ client = Api.default_client(key, secret)
13
+ Configuration.new(client: client, url: url)
14
+ end
15
+
16
+ class Configuration
17
+ include Composable::Get
18
+
19
+ SUCCESS = MediaTypes::Configuration.to_constructable.version(5)
20
+ FAILURE = MediaTypes::Errors.to_constructable.version(1)
21
+
22
+ ACCEPT = [SUCCESS.to_s, FAILURE.to_s(0.1)].join(', ').freeze
23
+
24
+ attr_accessor :client
25
+
26
+ def initialize(client:, url: nil, result: nil)
27
+ self.client = client
28
+ self.result = result
29
+ self.url = url
30
+ end
31
+
32
+ def data
33
+ to_h[:configuration]
34
+ end
35
+
36
+ def call(url: resolve_url)
37
+ return self if ok? || !url
38
+ guard_network_errors do
39
+ merge(resolve_client.headers(Headers::ACCEPT => ACCEPT).get(url))
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ attr_accessor :url
46
+
47
+ def ok?
48
+ result&.status == 200
49
+ end
50
+
51
+ def redirecting?(result)
52
+ [301, 302, 303, 307, 308].include?(result.status)
53
+ end
54
+
55
+ alias resolve_url url
56
+ alias resolve_client client
57
+
58
+ def merge(result)
59
+ raise_on_error(result)
60
+ self.result = redirecting?(result) ? redirect(result).result : result
61
+ self
62
+ end
63
+
64
+ def redirect(result)
65
+ call(url: result['Location'] || result['Content-Location'])
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'trailer_vote/media_types'
4
+
5
+ module TrailerVote
6
+ module Api
7
+
8
+ class Error < RuntimeError; end
9
+ class UnknownMediaType < Error; end
10
+ class NetworkError < Error; end
11
+ class ConnectionError < NetworkError; end
12
+ class TimeoutError < NetworkError; end
13
+
14
+ class EncodeError < Error
15
+ def initialize(media_type:, source:)
16
+ super format(
17
+ 'Failed to encode data for %<media_type>s. Reason: %<reason>s',
18
+ media_type: media_type,
19
+ reason: source.message
20
+ )
21
+
22
+ self.source = source
23
+ end
24
+
25
+ attr_accessor :source
26
+ end
27
+
28
+ class DecodeError < Error
29
+ def initialize(media_type:, source:)
30
+ super format(
31
+ 'Failed to decode data for %<media_type>s. Reason: %<reason>s',
32
+ media_type: media_type,
33
+ reason: source.message
34
+ )
35
+
36
+ self.source = source
37
+ end
38
+
39
+ attr_accessor :source
40
+ end
41
+
42
+ class ErrorsResponse < Error
43
+ attr_accessor :result
44
+
45
+ def initialize(result)
46
+ self.result = result
47
+ super messages
48
+ end
49
+
50
+ def messages
51
+ Array(formatted_data[:errors]).map { |error| error[:message] }.join(', ')
52
+ end
53
+
54
+ def data
55
+ @data ||= TrailerVote::Api.decode(result)
56
+ rescue DecodeError, UnknownMediaType
57
+ # noinspection RubyStringKeysInHashInspection
58
+ @data = { errors: [{ message: result.status.reason }] }
59
+ end
60
+
61
+ def status
62
+ result.status.to_i
63
+ end
64
+
65
+ def inspect
66
+ format('[%<status>s] %<klazz>s' + "\n" + '%<messages>s', status: status, klazz: self.class.name, messages: messages)
67
+ end
68
+
69
+ alias to_i status
70
+ alias to_s inspect
71
+
72
+ private
73
+
74
+ def formatted_data
75
+ return data if data.is_a?(::Hash)
76
+
77
+ { errors: [{ message: String(data) }] }
78
+ end
79
+ end
80
+
81
+ class ClientError < ErrorsResponse; end
82
+ class BadRequest < ClientError; end
83
+ class Unauthorized < ClientError; end
84
+ class Forbidden < ClientError; end
85
+ class NotFound < ClientError; end
86
+ class Conflict < ClientError; end
87
+ class Gone < ClientError; end
88
+ class PreconditionFailed < ClientError; end
89
+ class UnprocessableEntity < ClientError; end
90
+ class TooManyRequests < ClientError; end
91
+
92
+ class ServerError < ErrorsResponse; end
93
+
94
+ ERROR_MAPPING = Hash.new { |_, key| key < 500 ? ClientError : ServerError }.merge(
95
+ 400 => BadRequest,
96
+ 401 => Unauthorized,
97
+ 403 => Forbidden,
98
+ 404 => NotFound,
99
+ 409 => Conflict,
100
+ 410 => Gone,
101
+ 412 => PreconditionFailed,
102
+ 422 => UnprocessableEntity,
103
+ 429 => TooManyRequests
104
+ ).freeze
105
+
106
+ module_function
107
+
108
+ def raise_error(result)
109
+ raise ERROR_MAPPING[result.status], result
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+ require 'trailer_vote/api/type_registry'
5
+
6
+ module TrailerVote
7
+ module Api
8
+ module JsonTypeAdapter
9
+ module_function
10
+
11
+ def encode(obj)
12
+ Oj.dump(obj, mode: :compat)
13
+ rescue Oj::Error => err
14
+ raise EncodeError.new(media_type: 'application/json', source: err)
15
+ end
16
+
17
+ def decode(obj)
18
+ Oj.load(obj, mode: :strict, symbol_keys: true)
19
+ rescue Oj::Error => err
20
+ raise DecodeError.new(media_type: 'application/json', source: err)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ TrailerVote::Api::TypeRegistry['application/json'] = TrailerVote::Api::JsonTypeAdapter
27
+ TrailerVote::Api::TypeRegistry.shortcut('application/json', :json)
28
+ TrailerVote::Api::TypeRegistry.shortcut('application/json', 'text/json')
29
+
30
+ module TrailerVote
31
+ module Api
32
+ module HtmlTypeAdapter
33
+ module_function
34
+
35
+ def encode(obj)
36
+ return obj if obj.is_a?(String)
37
+ raise EncodeError.new(
38
+ media_type: 'text/html',
39
+ source: ArgumentError.new('HTML must be passed in as a HTML string')
40
+ )
41
+ end
42
+
43
+ def decode(str)
44
+ str
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ TrailerVote::Api::TypeRegistry['text/html'] = TrailerVote::Api::HtmlTypeAdapter
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'trailer_vote/media_types'
4
+ require 'trailer_vote/api/configuration'
5
+
6
+ module TrailerVote
7
+ module Api
8
+ class Configuration
9
+ # @return [TrailerVote::Api::Issue::Find] issues attached to the credentials
10
+ def issue
11
+ Issue.new(configuration: self)
12
+ end
13
+ end
14
+
15
+ class Issue
16
+ def initialize(configuration:)
17
+ self.configuration = configuration
18
+ end
19
+
20
+ def back
21
+ configuration
22
+ end
23
+
24
+ private
25
+
26
+ attr_accessor :configuration
27
+ end
28
+ end
29
+ end