trailer_vote-api 0.5.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 (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +29 -0
  4. data/CHANGELOG.md +3 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +77 -0
  7. data/README.md +184 -0
  8. data/Rakefile +12 -0
  9. data/bin/console +15 -0
  10. data/bin/setup +8 -0
  11. data/lib/trailer_vote/api/autoload.rb +15 -0
  12. data/lib/trailer_vote/api/composable/common.rb +61 -0
  13. data/lib/trailer_vote/api/composable/get.rb +54 -0
  14. data/lib/trailer_vote/api/configuration.rb +69 -0
  15. data/lib/trailer_vote/api/errors.rb +62 -0
  16. data/lib/trailer_vote/api/links.rb +51 -0
  17. data/lib/trailer_vote/api/place/create.rb +65 -0
  18. data/lib/trailer_vote/api/place/find.rb +49 -0
  19. data/lib/trailer_vote/api/place.rb +19 -0
  20. data/lib/trailer_vote/api/product/create.rb +56 -0
  21. data/lib/trailer_vote/api/product/find.rb +37 -0
  22. data/lib/trailer_vote/api/product/image/create.rb +66 -0
  23. data/lib/trailer_vote/api/product/image/find.rb +39 -0
  24. data/lib/trailer_vote/api/product/image/urls.rb +57 -0
  25. data/lib/trailer_vote/api/product/image.rb +31 -0
  26. data/lib/trailer_vote/api/product/lookup.rb +81 -0
  27. data/lib/trailer_vote/api/product/place/link.rb +64 -0
  28. data/lib/trailer_vote/api/product/place.rb +31 -0
  29. data/lib/trailer_vote/api/product/update.rb +72 -0
  30. data/lib/trailer_vote/api/product/video/create.rb +66 -0
  31. data/lib/trailer_vote/api/product/video/find.rb +39 -0
  32. data/lib/trailer_vote/api/product/video/urls.rb +57 -0
  33. data/lib/trailer_vote/api/product/video.rb +31 -0
  34. data/lib/trailer_vote/api/product.rb +25 -0
  35. data/lib/trailer_vote/api/type_registry.rb +93 -0
  36. data/lib/trailer_vote/api/version.rb +7 -0
  37. data/lib/trailer_vote/api.rb +42 -0
  38. data/trailer_vote-api.gemspec +35 -0
  39. metadata +234 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8c83428f8cdf27b0b7be61b7f1b39f67cae7a721
4
+ data.tar.gz: 1d14fcbd1e8d7759a19a7feb6d05ed1b5f5f8d7f
5
+ SHA512:
6
+ metadata.gz: e74475a1590286c488e70f83338f43fe8c001e81fef23fab18dd66ddfcdcaa5774ab3d6a0db8a69ca84c9e2dce92688d622f6d40ec73560390aa45bd1d984f37
7
+ data.tar.gz: a2028156d2b770043cebca99a536a128161dfb60e18565ea36994ca770a83f51b54397bc1a4aeeb781bfa455c595dd32a7f84118b001a623b7b1e4882d83eceb
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /test/reports/
8
+ /tmp/
9
+
10
+ /bin/c
11
+ /.byebug_history
12
+ /.rakeTasks
13
+
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ AllCops:
2
+ Include:
3
+ - '**/Rakefile'
4
+ - 'lib/**/*.rb'
5
+ Exclude:
6
+ - 'Gemfile'
7
+ - 'bin/**/*'
8
+ TargetRubyVersion: 2.3
9
+
10
+ Layout/EmptyLinesAroundClassBody:
11
+ Enabled: false
12
+
13
+ Layout/EndOfLine:
14
+ Enabled: false
15
+
16
+ Metrics/LineLength:
17
+ Max: 120
18
+
19
+ Metrics/MethodLength:
20
+ Max: 15
21
+
22
+ Style/Documentation:
23
+ Enabled: false
24
+
25
+ Style/EmptyMethod:
26
+ EnforcedStyle: expanded
27
+
28
+ Style/IfUnlessModifier:
29
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.5.0
2
+
3
+ :baby: Initial public release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in trailer_vote-api.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,77 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ trailer_vote-api (0.5.0)
5
+ http
6
+ oj (~> 3.6)
7
+ trailer_vote-media_types (~> 0.6.1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.5.2)
13
+ public_suffix (>= 2.0.2, < 4.0)
14
+ ansi (1.5.0)
15
+ awesome_print (1.8.0)
16
+ builder (3.2.3)
17
+ crack (0.4.3)
18
+ safe_yaml (~> 1.0.0)
19
+ docile (1.3.1)
20
+ domain_name (0.5.20180417)
21
+ unf (>= 0.0.5, < 1.0.0)
22
+ hashdiff (0.3.7)
23
+ http (3.3.0)
24
+ addressable (~> 2.3)
25
+ http-cookie (~> 1.0)
26
+ http-form_data (~> 2.0)
27
+ http_parser.rb (~> 0.6.0)
28
+ http-cookie (1.0.3)
29
+ domain_name (~> 0.5)
30
+ http-form_data (2.1.1)
31
+ http_parser.rb (0.6.0)
32
+ json (2.1.0)
33
+ media_types (0.6.0)
34
+ minitest (5.11.3)
35
+ minitest-ci (3.4.0)
36
+ minitest (>= 5.0.6)
37
+ minitest-reporters (1.3.5)
38
+ ansi
39
+ builder
40
+ minitest (>= 5.0)
41
+ ruby-progressbar
42
+ oj (3.6.11)
43
+ public_suffix (3.0.3)
44
+ rake (10.5.0)
45
+ ruby-progressbar (1.10.0)
46
+ safe_yaml (1.0.4)
47
+ simplecov (0.16.1)
48
+ docile (~> 1.1)
49
+ json (>= 1.8, < 3)
50
+ simplecov-html (~> 0.10.0)
51
+ simplecov-html (0.10.2)
52
+ trailer_vote-media_types (0.6.1)
53
+ media_types (~> 0.6.0)
54
+ unf (0.1.4)
55
+ unf_ext
56
+ unf_ext (0.0.7.5-x64-mingw32)
57
+ webmock (3.4.2)
58
+ addressable (>= 2.3.6)
59
+ crack (>= 0.3.2)
60
+ hashdiff
61
+
62
+ PLATFORMS
63
+ x64-mingw32
64
+
65
+ DEPENDENCIES
66
+ awesome_print (~> 1.8)
67
+ bundler (~> 1.16)
68
+ minitest (~> 5.11)
69
+ minitest-ci (~> 3.4)
70
+ minitest-reporters (~> 1.3)
71
+ rake (~> 10.5)
72
+ simplecov (~> 0.16)
73
+ trailer_vote-api!
74
+ webmock (~> 3.4)
75
+
76
+ BUNDLED WITH
77
+ 1.16.5
data/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # TrailerVote::Api
2
+
3
+ The TrailerVote Api gem is the official interface to communicate with the TrailerVote product service. It allows you to
4
+ keep your code simple and not deal with the HTTP suite.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'trailer_vote-api'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install trailer_vote-api
21
+
22
+ ## Usage
23
+
24
+ By default only the configuration api calls are available and you need to require which ever calls you want to make. A call always loads it dependencies. If you want to load everything you can `require 'trailer_vote/api/autoload'`.
25
+
26
+ In order to call any of the APIs, you need to configure the api first:
27
+ ```Ruby
28
+ key = '<TrailerVote API Client key>'
29
+ secret = '<TrailerVote API Client secret>'
30
+ url = '<TrailerVote API environment url>'
31
+
32
+ configuration = TrailerVote::Api.configure(url: url, key: key, secret: secret)
33
+ # => TrailerVote::Api::Configuration
34
+ ```
35
+
36
+ The TrailerVote API Client for Ruby uses a lazy chainable API and does **NOT** make any requests until it's absolutely necessary.
37
+ It is recommended that you re-use the configuration instance, as well as other results, in order to reduce network requests.
38
+
39
+ The common interface is:
40
+ - `#call(*args)`: Make the call and return an object that has results
41
+ - `#data`: Return the inner data (`#to_h`) without the wrapping key
42
+ - `#to_i`: Return the HTTP status code
43
+ - `#to_h`: Return the response (`#call`), parsed (if parsable, like JSON) and validated (according to the media type)
44
+ - `#etag`: Return the HTTP ETag header value, if any
45
+ - `#links`: Return the HTTP Link header / inner `_links` as `Links` object
46
+
47
+ Unless data is being posted, `#call` is not necessary:
48
+
49
+ ```Ruby
50
+ configuration = TrailerVote::Api.configure(url: url, key: key, secret: secret)
51
+ configuration.links
52
+ # => TrailerVote::Api::Links # Makes the HTTP call if necessary
53
+ ```
54
+
55
+ In case of an error, a `TrailerVote::Api::Error` is raised, with subclasses defining what went wrong. If the API gives back an error (HTTP status `(400..599)`), the error is parsed and turned into a `ErrorsResponse < Error` error.
56
+
57
+ ## Interface
58
+
59
+ Only the call available to the current link in the chain, if `required` are available on each object.
60
+
61
+ ### `configuration`
62
+ You don't need to require this, it's always loaded.
63
+
64
+ ```Ruby
65
+ require 'trailer_vote/api/configuration'
66
+
67
+ configuration = TrailerVote::Api.configure(key: '', secret: '', url: '')
68
+ # => TrailerVote::Api::Configuration
69
+ ```
70
+
71
+ ### `configuration.product.lookup`
72
+ Used to lookup products by authority:identifier pairs. If found, returns the actual `Product::Find`.
73
+
74
+ The `data` argument is wrapped in `{ product_identifiers: data }` and then needs to match `application/vnd.trailervote.product.lookup.v1+json`
75
+
76
+ ```Ruby
77
+ require 'trailer_vote/api/product/lookup'
78
+
79
+ lookup = configuration.product.lookup
80
+ # => TrailerVote::Api::Product::Lookup
81
+
82
+ lookup.call(data: [{ authority: 'imdb', identifier: 'tt01010101' }, { authority: 'tmdb', identifier: '12345678' }])
83
+ # => TrailerVote::Api::Product::Find
84
+ ```
85
+
86
+ ### `configuration.product.update`
87
+ Used to update a product. Is only available on `Product::Find`, because we require updates to be non-stale, that is you need to ensure that you are the last one updating the product, so merging data can be done correctly.
88
+
89
+ The `data` argument is wrapped in `{ product: data }` and then needs to match `application/vnd.trailervote.product.v2+json`
90
+
91
+ ```Ruby
92
+ require 'trailer_vote/api/product/lookup'
93
+ require 'trailer_vote/api/product/update'
94
+
95
+ lookup = configuration.product.lookup
96
+ product = lookup.call(data: [{ authority: 'imdb', identifier: 'tt01010101' }])
97
+ # => TrailerVote::Api::Product::Find
98
+
99
+ current_product_data = product.data
100
+ next_product_data = make_changes_to_product_data(current_product_data)
101
+ product.update.call(data: next_product_data)
102
+ # => TrailerVote::Api::Product::Find
103
+ ```
104
+
105
+ If the update call fails with a `409 Conflict` or `412 Precondition Failed`, it means the product was updated in the meanwhile. You want to fetch the product again by doing another lookup and then running your strategy again.
106
+
107
+ ### `configuration.product.create`
108
+ Creating a product is similar to looking up a product. If successful, returns an actual `Product::Find`.
109
+
110
+ The `data` argument is wrapped in `{ product: data }` and then needs to match `application/vnd.trailervote.product.v2.create+json`
111
+
112
+ ```Ruby
113
+ require 'trailer_vote/api/product/create'
114
+
115
+ configuration.product.create(data: { title: 'My product', ... })
116
+ # => TrailerVote::Api::Product::Find
117
+ ```
118
+
119
+ ### `configuration.product.<>.video.create`
120
+ To attach an video to a product, you first need to find the product. This can be done by:
121
+ - `configuration.product.create`: creating a new product
122
+ - `configuration.product.lookup`: looking up an existing product
123
+
124
+ Once you have it, the video operations are available on the result.
125
+
126
+ The `data` argument is wrapped in `{ product_video: data }` and then needs to match `application/vnd.trailervote.product.video.v1.create+json`
127
+
128
+ ```Ruby
129
+ require 'trailer_vote/api/product/lookup'
130
+ require 'trailer_vote/api/product/video/create'
131
+
132
+ product = configuration.product.lookup.call(data: [{ authority: 'imdb', identifier: 'tt01010101' }])
133
+ # => TrailerVote::Api::Product::Find
134
+
135
+ product.video.create(data: { source_url: '', ... })
136
+ # => TrailerVote::Api::Product::Video::Find
137
+ ```
138
+
139
+ You can see in this example the `product` result is cached in a variable, so that if you want to create *many* videos, the product isn't looked up each call.
140
+
141
+ ### `configuration.product.<>.image.urls`
142
+ Gets all the image urls for a product
143
+
144
+ ### `configuration.product.<>.video.create`
145
+ To attach an video to a product, you first need to find the product. This can be done by:
146
+ - `configuration.product.create`: creating a new product
147
+ - `configuration.product.lookup`: looking up an existing product
148
+
149
+ Once you have it, the video operations are available on the result.
150
+
151
+ The `data` argument is wrapped in `{ product_video: data }` and then needs to match `application/vnd.trailervote.product.video.v1.create+json`
152
+
153
+ ```Ruby
154
+ require 'trailer_vote/api/product/lookup'
155
+ require 'trailer_vote/api/product/video/create'
156
+
157
+ product = configuration.product.lookup.call(data: [{ authority: 'imdb', identifier: 'tt01010101' }])
158
+ # => TrailerVote::Api::Product::Find
159
+
160
+ product.video.create(data: { source_url: '', ... })
161
+ # => TrailerVote::Api::Product::Video::Find
162
+ ```
163
+
164
+ You can see in this example the `product` result is cached in a variable, so that if you want to create *many* videos, the product isn't looked up each call.
165
+
166
+ ### `configuration.product.<>.video.urls`
167
+ Gets all the image urls for a product
168
+
169
+ ### `configuration.product.<>.place.link`
170
+ Links a place to a product
171
+
172
+ ## Development
173
+
174
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can
175
+ also run `bin/console` for an interactive prompt that will allow you to experiment.
176
+
177
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
178
+ version number in `version.rb`, and then run `bundle update trailer_vote-api` in any repository that depends on
179
+ this gem. If you have push rights, you may call `bundle exec rake release` to create a new git tag, push
180
+ git commits and tags, and push the `.gem` file to the rubygems gem server.
181
+
182
+ ## Contributing
183
+
184
+ Bug reports and pull requests are welcome on GitHub at [TrailerVote/trailervote-api-clients](https://github.com/trailervote/trailervote-api-clients)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ end
11
+
12
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'trailer_vote/api'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'trailer_vote/api'
4
+ require 'trailer_vote/api/place'
5
+ require 'trailer_vote/api/place/create'
6
+ require 'trailer_vote/api/place/find'
7
+ require 'trailer_vote/api/product'
8
+ require 'trailer_vote/api/product/create'
9
+ require 'trailer_vote/api/product/find'
10
+ require 'trailer_vote/api/product/lookup'
11
+ require 'trailer_vote/api/product/image/create'
12
+ require 'trailer_vote/api/product/image/find'
13
+ require 'trailer_vote/api/product/place/link'
14
+ require 'trailer_vote/api/product/video/create'
15
+ require 'trailer_vote/api/product/video/find'
@@ -0,0 +1,61 @@
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
+ def call(**_opts)
17
+ raise format('Missing implementation of #args in %<name>s', self.class.name)
18
+ end
19
+
20
+ private
21
+
22
+ def resolve_client
23
+ configuration.client
24
+ end
25
+
26
+ def forward_klazz
27
+ self.class
28
+ end
29
+
30
+ def redirect_klazz
31
+ self.class
32
+ end
33
+
34
+ def branch(result, data: nil)
35
+ raise_on_error(result)
36
+ forward(result, data: data) || redirect(result)
37
+ end
38
+
39
+ def raise_on_error(result)
40
+ return unless result.status.client_error? || result.status.server_error?
41
+ TrailerVote::Api.raise_error result
42
+ end
43
+
44
+ def forward(result, data:)
45
+ return unless [307, 308].include?(result.status)
46
+ forward_klazz.new(configuration: configuration)
47
+ .call(data: data, url: redirected_url(result))
48
+ end
49
+
50
+ def redirect(result)
51
+ redirect_klazz.new(configuration: configuration, result: result)
52
+ .call(url: redirected_url(result))
53
+ end
54
+
55
+ def redirected_url(result)
56
+ result['Location'] || result['Content-Location']
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,54 @@
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
+ def links
18
+ # TODO: or headers
19
+ @links ||= Links.new(data['_links'])
20
+ end
21
+
22
+ def to_h
23
+ @to_h ||= TrailerVote::Api.decode(call.result)
24
+ end
25
+
26
+ def to_i
27
+ call.result.status
28
+ end
29
+
30
+ def etag
31
+ call.result[Headers::ETAG]
32
+ end
33
+
34
+ def data
35
+ raise format('Missing implementation of #data in %<name>s', self.class.name)
36
+ end
37
+
38
+ private
39
+
40
+ def ok?
41
+ result&.status == 200
42
+ end
43
+
44
+ def redirecting?
45
+ [301, 302, 303, 307, 308].include?(result&.status)
46
+ end
47
+
48
+ def redirect_to
49
+ self.class
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'trailer_vote/api/composable/get'
4
+
5
+ module TrailerVote
6
+ module Api
7
+
8
+ module_function
9
+
10
+ def configure(url:, key:, secret:)
11
+ client = Api.default_client(key, secret)
12
+ Configuration.new(client: client, url: url)
13
+ end
14
+
15
+ class Configuration
16
+ include Composable::Get
17
+
18
+ SUCCESS = MediaTypes::Configuration.to_constructable.version(2)
19
+ FAILURE = MediaTypes::Errors.to_constructable.version(1)
20
+
21
+ ACCEPT = [SUCCESS.to_s, FAILURE.to_s(0.1)].join(', ').freeze
22
+
23
+ attr_accessor :client
24
+
25
+ def initialize(client:, url: nil, result: nil)
26
+ self.client = client
27
+ self.result = result
28
+ self.url = url
29
+ end
30
+
31
+ def data
32
+ to_h['configuration']
33
+ end
34
+
35
+ def call(url: resolve_url)
36
+ return self if ok? || !url
37
+ merge(resolve_client.headers(Headers::ACCEPT => ACCEPT).get(url))
38
+ # TODO: result.raise_for_status
39
+ end
40
+
41
+ private
42
+
43
+ attr_accessor :url
44
+
45
+ def ok?
46
+ result&.status == 200
47
+ end
48
+
49
+ def redirecting?(result)
50
+ [301, 302, 303, 307, 308].include?(result.status)
51
+ end
52
+
53
+ alias resolve_url url
54
+ alias resolve_client client
55
+
56
+ def merge(result)
57
+ raise_on_error(result)
58
+ self.result = redirecting?(result) ? redirect(result).result : result
59
+ self
60
+ end
61
+
62
+ def redirect(result)
63
+ call(url: result['Location'] || result['Content-Location'])
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ TrailerVote::MediaTypes::Configuration.register
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrailerVote
4
+ module Api
5
+
6
+ class Error < RuntimeError; end
7
+
8
+ class EncodeError < Error
9
+ def initialize(media_type:, source:)
10
+ super format(
11
+ 'Failed to encode data for %<media_type>s. Reason: %<reason>s',
12
+ media_type: media_type,
13
+ reason: source.message
14
+ )
15
+
16
+ self.source = source
17
+ end
18
+
19
+ attr_accessor :source
20
+ end
21
+
22
+ class DecodeError < Error
23
+ def initialize(media_type:, source:)
24
+ super format(
25
+ 'Failed to decode data for %<media_type>s. Reason: %<reason>s',
26
+ media_type: media_type,
27
+ reason: source.message
28
+ )
29
+
30
+ self.source = source
31
+ end
32
+
33
+ attr_accessor :source
34
+ end
35
+
36
+ class ErrorsResponse < Error
37
+ attr_accessor :result
38
+
39
+ def initialize(result)
40
+ self.result = result
41
+ super messages
42
+ end
43
+
44
+ def messages
45
+ result.status.reason
46
+ end
47
+
48
+ def data
49
+ @data ||= TrailerVote::Api.decode(result)
50
+ end
51
+ end
52
+
53
+ module_function
54
+
55
+ def raise_error(result)
56
+ raise ErrorsResponse, result
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ TrailerVote::MediaTypes::Errors.register
@@ -0,0 +1,51 @@
1
+ module TrailerVote
2
+ module Api
3
+ class Links
4
+ def initialize(links)
5
+ self.links = links
6
+ end
7
+
8
+ def respond_to_missing?(method_name, include_private = false)
9
+ links.key?(String(method_name)) || super
10
+ end
11
+
12
+ def method_missing(method_name, *arguments)
13
+ if links.key?(String(method_name))
14
+ return call(String(method_name), *arguments)
15
+ end
16
+
17
+ if /[a-z_]+/.match? method_name
18
+ raise ArgumentError, format(
19
+ 'Unknown link %<link>s. Available: %<links>s',
20
+ link: method_name,
21
+ links: links.keys
22
+ )
23
+ end
24
+
25
+ super
26
+ end
27
+
28
+ def [](key)
29
+ links.fetch(key)
30
+ end
31
+
32
+ private
33
+
34
+ attr_accessor :links
35
+
36
+ def call(link, **arguments)
37
+ if arguments.length.zero?
38
+ return self[link]['href']
39
+ end
40
+
41
+ fill_template(self[link]['href'], **arguments)
42
+ end
43
+
44
+ def fill_template(templated, **arguments)
45
+ arguments.each_with_object(templated) do |(variable, value), result|
46
+ result.gsub!("{#{variable}}", value)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end