haveapi-client 0.29.4 → 0.29.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd152485887e7737693110495d6bdd627db3d43aa5cb81ffb96619d59473f704
4
- data.tar.gz: 9914768b6f5b145929fe6c257de001b9c6f26a9d8fabac689790f5037092a621
3
+ metadata.gz: b2a413b63db6d6f00c9664d93b6a2bb7d6958f7cbe5375206173ad1449b35123
4
+ data.tar.gz: ea5a9393d531ee3503b6ce0274926aae67d9419c8c98d3c85ed2b1fe83d23499
5
5
  SHA512:
6
- metadata.gz: db8e0e7a6c327e7b9ad96350051fc62d0bf89bd26a5c38ffb7551ec36daac13b34b349e3c03dd209511da015bd5042ad8bcfc4ae9fa75617f3e8c9e2038258df
7
- data.tar.gz: dedb09777b253bb7076c46660c3399bc1a352184fc9956bc41ae8168173fe415acc3566ae0af177bbc8ee45e4a3450c4a6157f45d5ca5a2c0f16ac214aa219dd
6
+ metadata.gz: 947f1336247d07ad353055d7fc5cc68708b4a698927a789b80e91878526dc04b33a9ab2a1ab509e4c8ba2846bcde5cb9710a18b05e61f243c2c82d311b93cc68
7
+ data.tar.gz: 8bf5a0cae801ca319dcb18fd441a3b03d3fad11de8eda64b483a078886f72d9fbbe81187aada825b6abcf4e81e2d788b9777ed96faab76439eaaa28310df18ae
data/README.md CHANGED
@@ -120,6 +120,14 @@ client-side validation errors:
120
120
  api = HaveAPI::Client::Client.new('https://your.api.tld', language: 'cs')
121
121
  ```
122
122
 
123
+ Change the request language on an existing client with `set_opts`. Read the
124
+ configured value through `opts`:
125
+
126
+ ```ruby
127
+ api.set_opts(language: 'en')
128
+ p api.opts[:language]
129
+ ```
130
+
123
131
  The value is sent in `Accept-Language` by default. Use `language_header` when
124
132
  the API is configured with a custom language header. Set the language before
125
133
  the client fetches the API description so translated validator descriptions
@@ -90,24 +90,8 @@ class HaveAPI::Client::Client
90
90
  # @param opts [Hash] options
91
91
  def set_opts(opts)
92
92
  @opts.update(opts)
93
- self.language = opts[:language] if opts.has_key?(:language)
94
- self.language_header = opts[:language_header] if opts.has_key?(:language_header)
95
- end
96
-
97
- def language
98
- @api.language
99
- end
100
-
101
- def language=(value)
102
- @api.language = value
103
- end
104
-
105
- def language_header
106
- @api.language_header
107
- end
108
-
109
- def language_header=(value)
110
- @api.language_header = value
93
+ @api.language = opts[:language] if opts.has_key?(:language)
94
+ @api.language_header = opts[:language_header] if opts.has_key?(:language_header)
111
95
  end
112
96
 
113
97
  def client_message(key, **values)
@@ -205,10 +205,17 @@ module HaveAPI::Client
205
205
  def find_association(res_desc, res_val)
206
206
  return nil unless res_val
207
207
 
208
- tmp = @client
208
+ resource = @client
209
+ path = []
209
210
 
210
- res_desc[:resource].each do |r|
211
- tmp = tmp.method(r).call
211
+ res_desc[:resource].each do |name|
212
+ path << name
213
+
214
+ begin
215
+ resource = resource.resources.fetch(name.to_sym)
216
+ rescue KeyError
217
+ raise ProtocolError, "associated resource '#{path.join('.')}' not found"
218
+ end
212
219
  end
213
220
 
214
221
  # FIXME: read _meta namespace from description
@@ -217,8 +224,8 @@ module HaveAPI::Client
217
224
  ResourceInstance.new(
218
225
  @client,
219
226
  @api,
220
- tmp,
221
- action: tmp.actions[:show],
227
+ resource,
228
+ action: resource.actions[:show],
222
229
  resolved: meta[:resolved],
223
230
  response: meta[:resolved] ? res_val : nil,
224
231
  meta: meta
@@ -6,7 +6,7 @@ module HaveAPI::Client
6
6
 
7
7
  def valid?
8
8
  if opts[:values].is_a?(::Hash)
9
- opts[:values].keys.include?(value)
9
+ opts[:values].keys.any? { |v| v.to_s == value.to_s }
10
10
 
11
11
  else
12
12
  opts[:values].include?(value)
@@ -1,6 +1,6 @@
1
1
  module HaveAPI
2
2
  module Client
3
3
  PROTOCOL_VERSION = '2.0'.freeze
4
- VERSION = '0.29.4'.freeze
4
+ VERSION = '0.29.6'.freeze
5
5
  end
6
6
  end
@@ -80,6 +80,10 @@ RSpec.describe HaveAPI::Client::Client do
80
80
  actions: {},
81
81
  resources: {}
82
82
  },
83
+ language: {
84
+ actions: {},
85
+ resources: {}
86
+ },
83
87
  users: {
84
88
  actions: {},
85
89
  resources: {}
@@ -94,8 +98,9 @@ RSpec.describe HaveAPI::Client::Client do
94
98
  expect(client.method(:setup).owner).to eq(described_class)
95
99
  expect(client.authenticate(:token, token: 'secret-token')).to eq(:authenticated)
96
100
  expect(api.authenticate_calls).to eq(2)
101
+ expect(client.language).to be_a(HaveAPI::Client::Resource)
97
102
  expect(client.users).to be_a(HaveAPI::Client::Resource)
98
- expect(client.resources.keys).to contain_exactly(:authenticate, :setup, :users)
103
+ expect(client.resources.keys).to contain_exactly(:authenticate, :setup, :language, :users)
99
104
  end
100
105
 
101
106
  it 'does not let actions or nested resources replace resource methods' do
data/spec/i18n_spec.rb CHANGED
@@ -24,6 +24,18 @@ RSpec.describe HaveAPI::Client::Client do
24
24
  expect(client.communicator.language_headers).to eq('X-Language' => 'cs-CZ')
25
25
  end
26
26
 
27
+ it 'updates and reports language options through the option interface' do
28
+ client = described_class.new(TEST_SERVER.base_url)
29
+
30
+ client.set_opts(language: 'cs-CZ', language_header: 'X-Language')
31
+
32
+ expect(client.opts(:language, :language_header)).to eq(
33
+ language: 'cs-CZ',
34
+ language_header: 'X-Language'
35
+ )
36
+ expect(client.communicator.language_headers).to eq('X-Language' => 'cs-CZ')
37
+ end
38
+
27
39
  it 'localizes local validation errors' do
28
40
  client = described_class.new(TEST_SERVER.base_url, language: 'cs')
29
41
 
@@ -33,4 +45,17 @@ RSpec.describe HaveAPI::Client::Client do
33
45
  expect(err.errors[:i]).to include('není platné celé číslo')
34
46
  end
35
47
  end
48
+
49
+ it 'accepts a value from localized choice metadata' do
50
+ client = described_class.new(TEST_SERVER.base_url, language: 'cs')
51
+ values = client.test.actions[:echo_choice]
52
+ .input_params[:format]
53
+ .dig(:validators, :include, :values)
54
+
55
+ expect(values).to eq(archive: 'Archiv', stream: 'Proud')
56
+
57
+ response = client.test.echo_choice(format: 'archive')
58
+
59
+ expect(response[:format]).to eq('archive')
60
+ end
36
61
  end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ class ResourceAssociationTestCommunicator
6
+ attr_reader :calls
7
+
8
+ def initialize(resolved:, association_path: ['language'])
9
+ @resolved = resolved
10
+ @association_path = association_path
11
+ @calls = []
12
+ end
13
+
14
+ def describe_api(_version)
15
+ {
16
+ resources: {
17
+ language: resource_description(
18
+ show: action_description(
19
+ namespace: 'language',
20
+ path: '/v1/languages/{language_id}',
21
+ parameters: {
22
+ id: { type: 'Integer' },
23
+ code: { type: 'String' }
24
+ }
25
+ )
26
+ ),
27
+ user: resource_description(
28
+ current: action_description(
29
+ namespace: 'user',
30
+ path: '/v1/users/current',
31
+ parameters: {
32
+ id: { type: 'Integer' },
33
+ language: {
34
+ type: 'Resource',
35
+ resource: @association_path,
36
+ value_id: 'id',
37
+ value_label: 'code'
38
+ }
39
+ }
40
+ )
41
+ )
42
+ }
43
+ }
44
+ end
45
+
46
+ def call(action, _params)
47
+ @calls << action.name
48
+
49
+ case action.name
50
+ when :current
51
+ response(
52
+ user: {
53
+ id: 1,
54
+ language: {
55
+ id: 1,
56
+ code: 'cs',
57
+ _meta: {
58
+ resolved: @resolved,
59
+ path_params: [1]
60
+ }
61
+ }
62
+ }
63
+ )
64
+ when :show
65
+ response(
66
+ language: {
67
+ id: 1,
68
+ code: 'cs'
69
+ }
70
+ )
71
+ else
72
+ raise "unexpected action #{action.name}"
73
+ end
74
+ end
75
+
76
+ def url
77
+ 'https://api.example'
78
+ end
79
+
80
+ protected
81
+
82
+ def resource_description(actions)
83
+ {
84
+ actions: actions.transform_values { |v| v },
85
+ resources: {}
86
+ }
87
+ end
88
+
89
+ def action_description(namespace:, path:, parameters:)
90
+ {
91
+ auth: false,
92
+ description: 'test action',
93
+ aliases: [],
94
+ blocking: false,
95
+ input: {
96
+ parameters: {},
97
+ layout: 'object',
98
+ namespace: namespace
99
+ },
100
+ output: {
101
+ parameters: parameters,
102
+ layout: 'object',
103
+ namespace: namespace
104
+ },
105
+ meta: {
106
+ object: nil,
107
+ global: nil
108
+ },
109
+ examples: [],
110
+ scope: "#{namespace}#show",
111
+ path: path,
112
+ method: 'GET',
113
+ help: "#{path}?method=GET"
114
+ }
115
+ end
116
+
117
+ def response(values)
118
+ {
119
+ status: true,
120
+ response: values.merge(_meta: {}),
121
+ message: nil,
122
+ errors: nil
123
+ }
124
+ end
125
+ end
126
+
127
+ RSpec.describe HaveAPI::Client::ResourceInstance do
128
+ def client_for(...)
129
+ communicator = ResourceAssociationTestCommunicator.new(...)
130
+ client = HaveAPI::Client::Client.new(
131
+ 'https://api.example',
132
+ communicator: communicator
133
+ )
134
+
135
+ [client, communicator]
136
+ end
137
+
138
+ it 'materializes a resolved association through the resource registry' do
139
+ client, communicator = client_for(resolved: true)
140
+
141
+ user = client.user.current
142
+
143
+ expect(user.language_id).to eq(1)
144
+ expect(user.language.code).to eq('cs')
145
+ expect(communicator.calls).to eq([:current])
146
+ end
147
+
148
+ it 'resolves a lazy association through the resource registry' do
149
+ client, communicator = client_for(resolved: false)
150
+
151
+ user = client.user.current
152
+
153
+ expect(user.language.code).to eq('cs')
154
+ expect(communicator.calls).to eq(%i[current show])
155
+ end
156
+
157
+ it 'reports an invalid associated resource path as a protocol error' do
158
+ client, = client_for(resolved: true, association_path: ['missing'])
159
+
160
+ expect { client.user.current }
161
+ .to raise_error(
162
+ HaveAPI::Client::ProtocolError,
163
+ "associated resource 'missing' not found"
164
+ )
165
+ end
166
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe HaveAPI::Client::Validators::Inclusion do
6
+ subject(:validator) do
7
+ described_class.new(
8
+ { values: values, message: 'not included' },
9
+ value,
10
+ other_params
11
+ )
12
+ end
13
+
14
+ let(:other_params) { Struct.new(:params).new({}) }
15
+
16
+ context 'with an array of values' do
17
+ let(:values) { %w[archive stream] }
18
+
19
+ it 'uses exact membership' do
20
+ expect(described_class.new(
21
+ { values: values, message: 'not included' },
22
+ 'archive',
23
+ other_params
24
+ )).to be_valid
25
+ expect(described_class.new(
26
+ { values: values, message: 'not included' },
27
+ :archive,
28
+ other_params
29
+ )).not_to be_valid
30
+ end
31
+ end
32
+
33
+ context 'with a map of labeled values' do
34
+ let(:values) { { archive: 'Archive', stream: 'Stream' } }
35
+
36
+ context 'with a matching string value' do
37
+ let(:value) { 'archive' }
38
+
39
+ it { is_expected.to be_valid }
40
+ end
41
+
42
+ context 'with a missing string value' do
43
+ let(:value) { 'incremental_stream' }
44
+
45
+ it { is_expected.not_to be_valid }
46
+ end
47
+ end
48
+
49
+ context 'with a numeric map key parsed from JSON' do
50
+ let(:values) { { '1': 'One', '2': 'Two' } }
51
+ let(:value) { 1 }
52
+
53
+ it { is_expected.to be_valid }
54
+ end
55
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haveapi-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.29.4
4
+ version: 0.29.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Skokan
@@ -162,8 +162,10 @@ files:
162
162
  - spec/integration/client_spec.rb
163
163
  - spec/integration/typed_input_spec.rb
164
164
  - spec/load_spec.rb
165
+ - spec/resource_instance_association_spec.rb
165
166
  - spec/spec_helper.rb
166
167
  - spec/support/test_server.rb
168
+ - spec/validators/inclusion_spec.rb
167
169
  homepage: ''
168
170
  licenses:
169
171
  - MIT