haveapi-client 0.28.4 → 0.29.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f5dc4eff2452e31341f2a99d63e6a2d36f7c92ed4acbbb2a5ae7e03d69090b4
4
- data.tar.gz: b2ce49aca22bf9c602607b802784a1b599b80929b3aabd60084f91ff7d43b54c
3
+ metadata.gz: 64e208c9297b87f784abefa3ad21ec1ee176265c7c1b0c84323f5ea93cf54dab
4
+ data.tar.gz: c8d7fd4e762307fa85751ca16ab79836009d102250a7e1d57a01c61ab06e373f
5
5
  SHA512:
6
- metadata.gz: 27ef732cc9493e701b47ee931bc876fa31dbef0ed3539e070f353968b76a9f24c37bdad9c2053fac33d40ba9f7b9390deeda91c811cc7dd61485cbd0e86efca2
7
- data.tar.gz: 8307517fc9646f7ea38fd5e94732ec74f2d3542b0cdc07a776e39ac293a48bc6902a5716eaa61293ee90b6638e50c0ed7d5dbbee9a2153daaa3ef175cd810e90
6
+ metadata.gz: f4fbf3e4c7ebb8a719291c0e676e7d88cbc04e16c700c8aa4fd6f52b9c6839c1fd6fb380cd90fe714575d642e63697da65f625993654fb3340d9b44848d267f6
7
+ data.tar.gz: cf35c27d7e8ac429349847ad8ba084c0f09e82c38857d94c0b08cf18649a9b99b6e684acdb726d30517d0caf8b5e8c32d8470f541bfb0d4cc59986f576b73221
data/README.md CHANGED
@@ -110,3 +110,19 @@ user.role = 'user'
110
110
  user.save
111
111
  p user.id
112
112
  ```
113
+
114
+ ## Localization
115
+
116
+ Pass `language` to request translated API messages and to translate local
117
+ client-side validation errors:
118
+
119
+ ```ruby
120
+ api = HaveAPI::Client::Client.new('https://your.api.tld', language: 'cs')
121
+ ```
122
+
123
+ The value is sent in `Accept-Language` by default. Use `language_header` when
124
+ the API is configured with a custom language header. Set the language before
125
+ the client fetches the API description so translated validator descriptions
126
+ are loaded from the server.
127
+
128
+ The CLI accepts the same request language with `--language cs`.
@@ -114,7 +114,7 @@ module HaveAPI::CLI
114
114
  begin
115
115
  ret = action.execute(@input_params)
116
116
  rescue HaveAPI::Client::ValidationError => e
117
- format_errors(action, 'input parameters not valid', e.errors)
117
+ format_errors(action, @api.client_message('errors.input_parameters_not_valid'), e.errors)
118
118
  exit(false)
119
119
  end
120
120
 
@@ -164,6 +164,7 @@ module HaveAPI::CLI
164
164
  block: true,
165
165
  verbose: false
166
166
  }
167
+ @opts = options
167
168
 
168
169
  @global_opt = OptionParser.new do |opts|
169
170
  opts.banner = "Usage: #{$0} [options] <resource> <action> [objects ids] [-- [parameters]]"
@@ -206,6 +207,11 @@ module HaveAPI::CLI
206
207
  options[:version] = v
207
208
  end
208
209
 
210
+ opts.on('--language LANGUAGE', 'Language to request from API') do |v|
211
+ options[:language] = v
212
+ @api.language = v if @api
213
+ end
214
+
209
215
  opts.on('-c', '--columns', 'Print output in columns') do
210
216
  options[:layout] = :columns
211
217
  end
@@ -683,7 +689,8 @@ module HaveAPI::CLI
683
689
  @api = HaveAPI::Client::Communicator.new(
684
690
  url || api_url,
685
691
  version || (@opts && @opts[:version]),
686
- verify_ssl: verify_ssl
692
+ verify_ssl: verify_ssl,
693
+ language: @opts && @opts[:language]
687
694
  )
688
695
  @api.identity = $0.split('/').last
689
696
  end
@@ -18,6 +18,16 @@ module HaveAPI::Client
18
18
  "#<#{self.class.name} @name=#{@name}>"
19
19
  end
20
20
 
21
+ def client_message(key, **values)
22
+ if @client.respond_to?(:client_message)
23
+ @client.client_message(key, **values)
24
+ elsif @api.respond_to?(:client_message)
25
+ @api.client_message(key, **values)
26
+ else
27
+ I18n.t(nil, key, values)
28
+ end
29
+ end
30
+
21
31
  def execute(data)
22
32
  params_arg = {}
23
33
 
@@ -15,6 +15,7 @@ module HaveAPI::Client
15
15
 
16
16
  def initialize(response)
17
17
  @data = response.response
18
+ @client = response.action.client
18
19
 
19
20
  @progress = Progress.new(@data[:current], @data[:total], @data[:unit])
20
21
  end
@@ -40,7 +41,7 @@ module HaveAPI::Client
40
41
  # is used only if the cancel operation is blocking.
41
42
  def cancel(&block)
42
43
  unless can_cancel?
43
- raise "action ##{@data[:id]} (#{label}) cannot be cancelled"
44
+ raise @client.client_message('errors.uncancelable_action', id: @data[:id])
44
45
  end
45
46
 
46
47
  @cancel = true
@@ -82,7 +82,7 @@ module HaveAPI::Client::Authentication
82
82
  return if cont == :done
83
83
 
84
84
  if @block.nil?
85
- raise AuthenticationFailed, 'implement multi-factor authentication'
85
+ raise AuthenticationFailed, @communicator.client_message('authentication.mfa_required')
86
86
  end
87
87
 
88
88
  loop do
@@ -106,7 +106,7 @@ module HaveAPI::Client::Authentication
106
106
  resp = HaveAPI::Client::Response.new(a, a.execute(input))
107
107
 
108
108
  if resp.failed?
109
- raise AuthenticationFailed, resp.message || 'invalid credentials'
109
+ raise AuthenticationFailed, resp.message || @communicator.client_message('authentication.invalid_credentials')
110
110
  end
111
111
 
112
112
  if resp[:complete]
@@ -15,6 +15,8 @@ class HaveAPI::Client::Client
15
15
  # @option opts [Integer] block_interval
16
16
  # @option opts [Integer] block_timeout
17
17
  # @option opts [Boolean] verify_ssl
18
+ # @option opts [String] language value sent in Accept-Language
19
+ # @option opts [String] language_header HTTP header used for language
18
20
  def initialize(url, opts = {})
19
21
  @setup = false
20
22
  @opts = opts
@@ -29,10 +31,17 @@ class HaveAPI::Client::Client
29
31
  @api = HaveAPI::Client::Communicator.new(
30
32
  url,
31
33
  @version,
32
- **{ verify_ssl: opts[:verify_ssl] }.compact
34
+ **{
35
+ verify_ssl: opts[:verify_ssl],
36
+ language: opts[:language],
37
+ language_header: opts[:language_header]
38
+ }.compact
33
39
  )
34
40
  @api.identity = @opts[:identity]
35
41
  end
42
+
43
+ @api.language = @opts[:language] if @opts.has_key?(:language)
44
+ @api.language_header = @opts[:language_header] if @opts.has_key?(:language_header)
36
45
  end
37
46
 
38
47
  def inspect
@@ -81,6 +90,33 @@ class HaveAPI::Client::Client
81
90
  # @param opts [Hash] options
82
91
  def set_opts(opts)
83
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
111
+ end
112
+
113
+ def client_message(key, **values)
114
+ if @api.respond_to?(:client_message)
115
+ @api.client_message(key, **values)
116
+ else
117
+ lang = @api.language if @api.respond_to?(:language)
118
+ HaveAPI::Client::I18n.t(lang || @opts[:language], key, values)
119
+ end
84
120
  end
85
121
 
86
122
  # @return [Hash] client options
@@ -15,16 +15,36 @@ module HaveAPI::Client
15
15
  end
16
16
  end
17
17
 
18
- attr_reader :url, :auth, :verify_ssl
18
+ attr_reader :url, :auth, :verify_ssl, :language, :language_header
19
19
  attr_accessor :identity
20
20
 
21
- def initialize(url, v = nil, verify_ssl: true)
21
+ def initialize(url, v = nil, verify_ssl: true, language: nil, language_header: nil)
22
22
  @url = url
23
23
  @auth = Authentication::NoAuth.new(self, {}, {})
24
24
  @rest = RestClient::Resource.new(@url, verify_ssl: verify_ssl)
25
25
  @version = v
26
26
  @verify_ssl = verify_ssl
27
27
  @identity = 'haveapi-client-ruby'
28
+ @language = language
29
+ @language_header = language_header || I18n::DEFAULT_LANGUAGE_HEADER
30
+ end
31
+
32
+ def language=(value)
33
+ I18n.assert_header_value!(value.to_s) unless value.nil? || value.to_s.empty?
34
+ @language = value
35
+ end
36
+
37
+ def language_header=(value)
38
+ I18n.assert_header_name!(value)
39
+ @language_header = value
40
+ end
41
+
42
+ def language_headers
43
+ I18n.request_headers(@language, @language_header)
44
+ end
45
+
46
+ def client_message(key, **values)
47
+ I18n.t(@language, key, values)
28
48
  end
29
49
 
30
50
  def inspect
@@ -134,7 +154,7 @@ module HaveAPI::Client
134
154
  ns.update(@auth.request_payload)
135
155
 
136
156
  args << ns.to_json
137
- args << { content_type: :json, accept: :json, user_agent: @identity }.update(@auth.request_headers)
157
+ args << base_headers(content_type: :json).update(@auth.request_headers)
138
158
 
139
159
  elsif %w[GET DELETE].include?(action.http_method)
140
160
  get_params = {}
@@ -150,18 +170,20 @@ module HaveAPI::Client
150
170
  end
151
171
  end
152
172
 
153
- args << { params: get_params.update(@auth.request_query_params), accept: :json, user_agent: @identity }.update(@auth.request_headers)
173
+ args << base_headers(
174
+ params: get_params.update(@auth.request_query_params)
175
+ ).update(@auth.request_headers)
154
176
  end
155
177
 
156
178
  begin
157
179
  response = parse(@rest[action.prepared_path].method(action.http_method.downcase.to_sym).call(*args))
158
180
  rescue RestClient::Forbidden
159
- return error('Access forbidden. Bad user name or password? Not authorized?')
181
+ return error(client_message('errors.access_forbidden'))
160
182
  rescue RestClient::ResourceNotFound,
161
183
  RestClient::BadRequest => e
162
184
  response = parse(e.http_body)
163
185
  rescue StandardError => e
164
- return error("Fatal API error: #{e.inspect}")
186
+ return error(client_message('errors.fatal_api_error', error: e.inspect))
165
187
  end
166
188
 
167
189
  if response[:status]
@@ -193,9 +215,8 @@ module HaveAPI::Client
193
215
 
194
216
  def description_for(path, query_params = {})
195
217
  ret = parse(@rest[path].get_options({
196
- params: @auth.request_payload.update(@auth.request_query_params).update(query_params),
197
- user_agent: @identity
198
- }.update(@auth.request_headers)))
218
+ params: @auth.request_payload.update(@auth.request_query_params).update(query_params)
219
+ }.update(base_headers).update(@auth.request_headers)))
199
220
 
200
221
  @proto_version = ret[:version]
201
222
  p_v = HaveAPI::Client::PROTOCOL_VERSION
@@ -222,5 +243,9 @@ module HaveAPI::Client
222
243
  def parse(str)
223
244
  JSON.parse(str, symbolize_names: true)
224
245
  end
246
+
247
+ def base_headers(headers = {})
248
+ { accept: :json, user_agent: @identity }.update(language_headers).update(headers)
249
+ end
225
250
  end
226
251
  end
@@ -6,7 +6,12 @@ module HaveAPI::Client
6
6
 
7
7
  def initialize(response)
8
8
  if response.respond_to?(:action)
9
- super("#{response.action.name} failed: #{response.message}")
9
+ msg = response.action.client_message(
10
+ 'errors.action_failed',
11
+ action: response.action.name,
12
+ message: response.message
13
+ )
14
+ super(msg)
10
15
  else
11
16
  super(response.to_s)
12
17
  end
@@ -19,7 +24,8 @@ module HaveAPI::Client
19
24
  attr_reader :errors
20
25
 
21
26
  def initialize(action, errors)
22
- super("#{action.name} failed: input parameters not valid")
27
+ message = action.client_message('errors.input_parameters_not_valid')
28
+ super(action.client_message('errors.action_failed', action: action.name, message: message))
23
29
 
24
30
  @action = action
25
31
  @errors = errors
@@ -0,0 +1,103 @@
1
+ require 'yaml'
2
+
3
+ module HaveAPI::Client
4
+ module I18n
5
+ DEFAULT_LOCALE = :en
6
+ DEFAULT_LANGUAGE_HEADER = 'Accept-Language'.freeze
7
+ HEADER_NAME = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/
8
+ HEADER_VALUE_UNSAFE = /[\x00\r\n]/
9
+
10
+ class << self
11
+ def t(language, key, values = {})
12
+ message = lookup(locale_for(language), key) || lookup(DEFAULT_LOCALE, key) || key.to_s
13
+
14
+ interpolate(message, values)
15
+ end
16
+
17
+ def request_headers(language, language_header = DEFAULT_LANGUAGE_HEADER)
18
+ return {} if language.nil? || language.to_s.empty?
19
+
20
+ assert_header_name!(language_header)
21
+ assert_header_value!(language)
22
+
23
+ { language_header => language.to_s }
24
+ end
25
+
26
+ def locale_for(language)
27
+ return DEFAULT_LOCALE if language.nil? || language.to_s.empty?
28
+
29
+ parse_accept_language(language).each do |tag|
30
+ normalized = normalize_locale(tag)
31
+ return normalized if normalized && messages.has_key?(normalized)
32
+ end
33
+
34
+ DEFAULT_LOCALE
35
+ end
36
+
37
+ def assert_header_name!(header)
38
+ return if header.is_a?(String) && header.match?(HEADER_NAME)
39
+
40
+ raise ArgumentError, "invalid language HTTP header name: #{header.inspect}"
41
+ end
42
+
43
+ def assert_header_value!(value)
44
+ return if value.is_a?(String) && !value.match?(HEADER_VALUE_UNSAFE)
45
+
46
+ raise ArgumentError, "invalid language HTTP header value: #{value.inspect}"
47
+ end
48
+
49
+ private
50
+
51
+ def lookup(locale, key)
52
+ key.to_s.sub(/\Ahaveapi_client\./, '').split('.').reduce(messages[locale]) do |data, part|
53
+ break unless data.is_a?(Hash)
54
+
55
+ data[part]
56
+ end
57
+ end
58
+
59
+ def interpolate(message, values)
60
+ values.reduce(message.dup) do |ret, (key, value)|
61
+ ret.gsub("%{#{key}}", value.to_s)
62
+ end
63
+ end
64
+
65
+ def parse_accept_language(header)
66
+ header.to_s.split(',').filter_map do |part|
67
+ tag, *params = part.split(';')
68
+ q = 1.0
69
+
70
+ params.each do |param|
71
+ name, value = param.split('=', 2).map(&:strip)
72
+ q = value.to_f if name == 'q'
73
+ end
74
+
75
+ next if tag.strip.empty? || q <= 0
76
+
77
+ [tag.strip, q]
78
+ end.sort_by { |(_tag, q)| -q }.map(&:first)
79
+ end
80
+
81
+ def normalize_locale(tag)
82
+ normalized = tag.to_s.strip.tr('_', '-').sub(/\..*\z/, '').downcase
83
+ return if normalized.empty?
84
+
85
+ primary = normalized.split('-', 2).first.to_sym
86
+ primary if messages.has_key?(primary)
87
+ end
88
+
89
+ def messages
90
+ @messages ||= begin
91
+ dir = File.expand_path('locales', __dir__)
92
+
93
+ Dir[File.join(dir, '*.yml')].to_h do |file|
94
+ locale = File.basename(file, '.yml').to_sym
95
+ data = YAML.safe_load_file(file, aliases: true) || {}
96
+
97
+ [locale, data.fetch(locale.to_s).fetch('haveapi_client')]
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,45 @@
1
+ # This file is generated from i18n/haveapi.yml.
2
+ # Do not edit it manually; manual changes will be overwritten.
3
+ # Update i18n/haveapi.yml and run bundle exec rake i18n:update.
4
+ ---
5
+ cs:
6
+ haveapi_client:
7
+ authentication:
8
+ callback_failed: "%{callback} selhal: %{error}"
9
+ callback_invalid_return: callback musí vrátit pole nebo 'stop'
10
+ callback_required: Implementujte callback %{callback}
11
+ invalid_credentials: neplatné přihlašovací údaje
12
+ invalid_oauth2_pkce_verifier: Neplatný OAuth2 PKCE verifier
13
+ invalid_oauth2_state: Neplatný OAuth2 state
14
+ mfa_required: implementujte vícefaktorové ověření
15
+ multistep_callback_required: přidejte callback pro zpracování vícefázového ověření
16
+ oauth2_access_token_required: Volba access_token musí být zadána
17
+ oauth2_not_configured: OAuth2 ověření není nastaveno
18
+ oauth2_revoke_failed: Přístupový token nelze zrušit, HTTP %{status}
19
+ token_request_failed: 'Token nelze vyžádat: %{message}'
20
+ token_revoke_failed: 'Token nelze zrušit: %{message}'
21
+ token_step_failed: 'Krok ověření ''%{action}'' selhal: %{message}'
22
+ unsupported_auth_action: Nepodporovaná ověřovací akce '%{action}'
23
+ errors:
24
+ access_forbidden: Přístup odepřen. Chybné uživatelské jméno nebo heslo? Nejste
25
+ oprávněni?
26
+ action_failed: "%{action} selhala: %{message}"
27
+ fatal_api_error: 'Fatální chyba API: %{error}'
28
+ input_parameters_not_valid: vstupní parametry nejsou platné
29
+ input_parameters_not_valid_for_action: Vstupní parametry pro akci '%{action}'
30
+ nejsou platné
31
+ invalid_input_parameters: neplatné vstupní parametry
32
+ uncancelable_action: 'Stav akce #%{id} nelze zrušit'
33
+ unresolved_arguments: 'Akci ''%{action}'' nelze spustit: chybí argumenty'
34
+ validation:
35
+ cannot_be_null: nesmí být null
36
+ invalid_boolean: neplatná pravdivostní hodnota
37
+ invalid_datetime: není ve formátu ISO 8601
38
+ invalid_float: neplatné desetinné číslo
39
+ invalid_input_layout: neplatná struktura vstupu
40
+ invalid_integer: neplatné celé číslo
41
+ invalid_resource_id: neplatné ID prostředku
42
+ invalid_string: neplatný řetězec
43
+ required_parameter_missing: povinný parametr chybí
44
+ validation_failed: validace selhala
45
+ validation_failed_with_errors: 'validace selhala: %{errors}'
@@ -0,0 +1,44 @@
1
+ # This file is generated from i18n/haveapi.yml.
2
+ # Do not edit it manually; manual changes will be overwritten.
3
+ # Update i18n/haveapi.yml and run bundle exec rake i18n:update.
4
+ ---
5
+ en:
6
+ haveapi_client:
7
+ authentication:
8
+ callback_failed: "%{callback} failed: %{error}"
9
+ callback_invalid_return: callback has to return an array or 'stop'
10
+ callback_required: Implement callback %{callback}
11
+ invalid_credentials: invalid credentials
12
+ invalid_oauth2_pkce_verifier: Invalid OAuth2 PKCE verifier
13
+ invalid_oauth2_state: Invalid OAuth2 state
14
+ mfa_required: implement multi-factor authentication
15
+ multistep_callback_required: add callback to handle multi-step authentication
16
+ oauth2_access_token_required: Option access_token must be provided
17
+ oauth2_not_configured: OAuth2 authentication is not configured
18
+ oauth2_revoke_failed: Unable to revoke access token, HTTP %{status}
19
+ token_request_failed: 'Unable to request token: %{message}'
20
+ token_revoke_failed: 'Unable to revoke token: %{message}'
21
+ token_step_failed: 'Failed at authentication step ''%{action}'': %{message}'
22
+ unsupported_auth_action: Unsupported authentication action '%{action}'
23
+ errors:
24
+ access_forbidden: Access forbidden. Bad user name or password? Not authorized?
25
+ action_failed: "%{action} failed: %{message}"
26
+ fatal_api_error: 'Fatal API error: %{error}'
27
+ input_parameters_not_valid: input parameters not valid
28
+ input_parameters_not_valid_for_action: Input parameters not valid for action
29
+ '%{action}'
30
+ invalid_input_parameters: invalid input parameters
31
+ uncancelable_action: 'Action state #%{id} cannot be cancelled'
32
+ unresolved_arguments: 'Unable to execute action ''%{action}'': unresolved arguments'
33
+ validation:
34
+ cannot_be_null: cannot be null
35
+ invalid_boolean: not a valid boolean
36
+ invalid_datetime: not in ISO 8601 format
37
+ invalid_float: not a valid float
38
+ invalid_input_layout: invalid input layout
39
+ invalid_integer: not a valid integer
40
+ invalid_resource_id: not a valid resource id
41
+ invalid_string: not a valid string
42
+ required_parameter_missing: required parameter missing
43
+ validation_failed: validation failed
44
+ validation_failed_with_errors: 'validation failed: %{errors}'
@@ -3,6 +3,7 @@ module HaveAPI::Client
3
3
  attr_reader :errors
4
4
 
5
5
  def initialize(params, desc, value)
6
+ @params = params
6
7
  @errors = []
7
8
  @desc = desc
8
9
  @value = coerce(value)
@@ -22,24 +23,28 @@ module HaveAPI::Client
22
23
  if v.nil?
23
24
  return nil if @desc[:nullable]
24
25
 
25
- @errors << 'cannot be null'
26
+ @errors << message('validation.cannot_be_null')
26
27
  return nil
27
28
  end
28
29
 
29
30
  if v.is_a?(::String) && v.strip.empty?
30
31
  return nil if @desc[:nullable]
31
32
 
32
- @errors << 'not a valid resource id'
33
+ @errors << message('validation.invalid_resource_id')
33
34
  return nil
34
35
  end
35
36
 
36
37
  if !v.is_a?(::Integer) && /\A\d+\z/ !~ v
37
- @errors << 'not a valid resource id'
38
+ @errors << message('validation.invalid_resource_id')
38
39
  nil
39
40
 
40
41
  else
41
42
  v.to_i
42
43
  end
43
44
  end
45
+
46
+ def message(key, **values)
47
+ @params.send(:message, key, **values)
48
+ end
44
49
  end
45
50
  end
@@ -35,7 +35,7 @@ module HaveAPI::Client
35
35
  if raw.nil?
36
36
  return nil if @desc[:nullable]
37
37
 
38
- @errors << 'cannot be null'
38
+ @errors << message('validation.cannot_be_null')
39
39
  return nil
40
40
  end
41
41
 
@@ -92,7 +92,7 @@ module HaveAPI::Client
92
92
  value = raw.to_f
93
93
  return value if value.finite?
94
94
 
95
- @errors << 'not a valid float'
95
+ @errors << message('validation.invalid_float')
96
96
  nil
97
97
 
98
98
  elsif raw.is_a?(::String)
@@ -159,28 +159,32 @@ module HaveAPI::Client
159
159
  end
160
160
 
161
161
  def invalid_integer
162
- @errors << 'not a valid integer'
162
+ @errors << message('validation.invalid_integer')
163
163
  nil
164
164
  end
165
165
 
166
166
  def invalid_float
167
- @errors << 'not a valid float'
167
+ @errors << message('validation.invalid_float')
168
168
  nil
169
169
  end
170
170
 
171
171
  def invalid_boolean
172
- @errors << 'not a valid boolean'
172
+ @errors << message('validation.invalid_boolean')
173
173
  nil
174
174
  end
175
175
 
176
176
  def invalid_datetime
177
- @errors << 'not in ISO 8601 format'
177
+ @errors << message('validation.invalid_datetime')
178
178
  nil
179
179
  end
180
180
 
181
181
  def invalid_string
182
- @errors << 'not a valid string'
182
+ @errors << message('validation.invalid_string')
183
183
  nil
184
184
  end
185
+
186
+ def message(key, **values)
187
+ @params.send(:message, key, **values)
188
+ end
185
189
  end
186
190
  end
@@ -33,7 +33,7 @@ module HaveAPI::Client
33
33
  p[:validators][:presence] || p[:validators][:present] || p[:validators][:required]
34
34
 
35
35
  if presence_validator && @params[name].nil?
36
- error(name, 'required parameter missing')
36
+ error(name, message('validation.required_parameter_missing'))
37
37
  next
38
38
  elsif @params[name].nil?
39
39
  next
@@ -61,6 +61,10 @@ module HaveAPI::Client
61
61
 
62
62
  protected
63
63
 
64
+ def message(key, **values)
65
+ @action.client_message(key, **values)
66
+ end
67
+
64
68
  def error(param, msg)
65
69
  @errors[param] ||= []
66
70
 
@@ -1,6 +1,6 @@
1
1
  module HaveAPI
2
2
  module Client
3
3
  PROTOCOL_VERSION = '2.0'.freeze
4
- VERSION = '0.28.4'.freeze
4
+ VERSION = '0.29.0'.freeze
5
5
  end
6
6
  end
@@ -1,6 +1,8 @@
1
1
  require 'require_all'
2
2
  require 'date'
3
3
 
4
+ require_relative 'client/i18n'
5
+
4
6
  module HaveAPI
5
7
  module Client
6
8
  # Shortcut to HaveAPI::Client::Client.new
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'haveapi/cli'
5
+
6
+ RSpec.describe HaveAPI::CLI::Cli do
7
+ def with_argv(*argv)
8
+ old_argv = ARGV.dup
9
+ ARGV.replace(argv)
10
+ yield
11
+ ensure
12
+ ARGV.replace(old_argv)
13
+ end
14
+
15
+ def fake_cli_api(language)
16
+ Class.new do
17
+ attr_accessor :language
18
+
19
+ def initialize(language)
20
+ @language = language
21
+ end
22
+
23
+ def describe_api(_version = nil)
24
+ { authentication: { fake: {} } }
25
+ end
26
+ end.new(language)
27
+ end
28
+
29
+ def parsed_cli_api(*argv)
30
+ cli = described_class.allocate
31
+ cli.instance_variable_set(:@config, {})
32
+
33
+ allow(cli).to receive(:connect_api) do
34
+ cli.instance_variable_set(
35
+ :@api,
36
+ fake_cli_api(cli.instance_variable_get(:@opts)&.[](:language))
37
+ )
38
+ end
39
+
40
+ with_argv(*argv) { cli.send(:options) }
41
+ cli.instance_variable_get(:@api)
42
+ end
43
+
44
+ it 'applies --language to no-auth CLI connections' do
45
+ expect(parsed_cli_api('--language', 'cs', 'test', 'fail').language).to eq('cs')
46
+ end
47
+
48
+ it 'keeps --language when auth is parsed before language' do
49
+ old_auth_methods = (described_class.auth_methods || {}).dup
50
+ fake_auth = Class.new do
51
+ def initialize(*); end
52
+ def options(_opts); end
53
+ end
54
+
55
+ described_class.auth_methods = old_auth_methods.merge(fake: fake_auth)
56
+
57
+ expect(
58
+ parsed_cli_api('--auth', 'fake', '--language', 'cs', 'test', 'fail').language
59
+ ).to eq('cs')
60
+ ensure
61
+ described_class.auth_methods = old_auth_methods
62
+ end
63
+ end
data/spec/i18n_spec.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe HaveAPI::Client::Client do
6
+ let(:valid_params) do
7
+ {
8
+ i: 1,
9
+ f: 1.0,
10
+ b: true,
11
+ dt: '2020-01-01T00:00:00Z',
12
+ s: 'x',
13
+ t: 'y'
14
+ }
15
+ end
16
+
17
+ it 'sends the configured language header' do
18
+ client = described_class.new(
19
+ TEST_SERVER.base_url,
20
+ language: 'cs-CZ',
21
+ language_header: 'X-Language'
22
+ )
23
+
24
+ expect(client.communicator.language_headers).to eq('X-Language' => 'cs-CZ')
25
+ end
26
+
27
+ it 'localizes local validation errors' do
28
+ client = described_class.new(TEST_SERVER.base_url, language: 'cs')
29
+
30
+ expect { client.test.echo(valid_params.merge(i: 'abc')) }
31
+ .to raise_error(HaveAPI::Client::ValidationError) do |err|
32
+ expect(err.message).to include('vstupní parametry nejsou platné')
33
+ expect(err.errors[:i]).to include('neplatné celé číslo')
34
+ end
35
+ end
36
+ 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.28.4
4
+ version: 0.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Skokan
@@ -130,7 +130,10 @@ files:
130
130
  - lib/haveapi/client/client.rb
131
131
  - lib/haveapi/client/communicator.rb
132
132
  - lib/haveapi/client/exceptions.rb
133
+ - lib/haveapi/client/i18n.rb
133
134
  - lib/haveapi/client/inflections.rb
135
+ - lib/haveapi/client/locales/cs.yml
136
+ - lib/haveapi/client/locales/en.yml
134
137
  - lib/haveapi/client/parameters/resource.rb
135
138
  - lib/haveapi/client/parameters/typed.rb
136
139
  - lib/haveapi/client/params.rb
@@ -152,8 +155,10 @@ files:
152
155
  - lib/restclient_ext/resource.rb
153
156
  - spec/action_security_spec.rb
154
157
  - spec/authentication_token_security_spec.rb
158
+ - spec/cli_i18n_spec.rb
155
159
  - spec/cli_security_spec.rb
156
160
  - spec/description_method_security_spec.rb
161
+ - spec/i18n_spec.rb
157
162
  - spec/integration/client_spec.rb
158
163
  - spec/integration/typed_input_spec.rb
159
164
  - spec/spec_helper.rb