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 +4 -4
- data/README.md +16 -0
- data/lib/haveapi/cli/cli.rb +9 -2
- data/lib/haveapi/client/action.rb +10 -0
- data/lib/haveapi/client/action_state.rb +2 -1
- data/lib/haveapi/client/authentication/token.rb +2 -2
- data/lib/haveapi/client/client.rb +37 -1
- data/lib/haveapi/client/communicator.rb +34 -9
- data/lib/haveapi/client/exceptions.rb +8 -2
- data/lib/haveapi/client/i18n.rb +103 -0
- data/lib/haveapi/client/locales/cs.yml +45 -0
- data/lib/haveapi/client/locales/en.yml +44 -0
- data/lib/haveapi/client/parameters/resource.rb +8 -3
- data/lib/haveapi/client/parameters/typed.rb +11 -7
- data/lib/haveapi/client/params.rb +5 -1
- data/lib/haveapi/client/version.rb +1 -1
- data/lib/haveapi/client.rb +2 -0
- data/spec/cli_i18n_spec.rb +63 -0
- data/spec/i18n_spec.rb +36 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64e208c9297b87f784abefa3ad21ec1ee176265c7c1b0c84323f5ea93cf54dab
|
|
4
|
+
data.tar.gz: c8d7fd4e762307fa85751ca16ab79836009d102250a7e1d57a01c61ab06e373f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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`.
|
data/lib/haveapi/cli/cli.rb
CHANGED
|
@@ -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, '
|
|
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
|
|
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, '
|
|
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 || '
|
|
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
|
-
**{
|
|
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 <<
|
|
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 <<
|
|
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('
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 << '
|
|
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 << '
|
|
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 << '
|
|
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 << '
|
|
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 << '
|
|
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 << '
|
|
162
|
+
@errors << message('validation.invalid_integer')
|
|
163
163
|
nil
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
def invalid_float
|
|
167
|
-
@errors << '
|
|
167
|
+
@errors << message('validation.invalid_float')
|
|
168
168
|
nil
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
def invalid_boolean
|
|
172
|
-
@errors << '
|
|
172
|
+
@errors << message('validation.invalid_boolean')
|
|
173
173
|
nil
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
def invalid_datetime
|
|
177
|
-
@errors << '
|
|
177
|
+
@errors << message('validation.invalid_datetime')
|
|
178
178
|
nil
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
def invalid_string
|
|
182
|
-
@errors << '
|
|
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, '
|
|
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
|
|
data/lib/haveapi/client.rb
CHANGED
|
@@ -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.
|
|
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
|