haveapi-go-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 +15 -0
- data/haveapi-go-client.gemspec +1 -1
- data/lib/haveapi/go_client/generator.rb +11 -2
- data/lib/haveapi/go_client/i18n_messages.yml +82 -0
- data/lib/haveapi/go_client/version.rb +1 -1
- data/spec/integration/generator_spec.rb +73 -0
- data/template/action.go.erb +7 -7
- data/template/authentication/oauth2.go.erb +5 -2
- data/template/authentication/token.go.erb +20 -6
- data/template/client.go.erb +6 -0
- data/template/i18n.go.erb +198 -0
- data/template/request.go.erb +3 -0
- data/template/types.go.erb +43 -4
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa106a3e9f534f8b38eb7dad77a01c8a93e0eec07cf2ff1a847e1310e4b66445
|
|
4
|
+
data.tar.gz: e1606903a5b5991eda7ce2ae2ac7df59e623f7b2fadad2f45ec2e6cb84568f6b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8850e3b1488d265ba0b20fc79a94cf4d9d9bf07c628d8538b4c00aae7a5f744536f637d3bd0f2ca4bb11afd56314b84e5971d624260f2230cf62d62b55fb85d2
|
|
7
|
+
data.tar.gz: e55cabd02c8f5751d9d7f81c46cba29e15b2c90ecb5036457bcb362091ca3c09e6a98e723d9a34bba4ef06a5208bd5ed3c5e7c9dd95cc4963a09baeb99cbba67
|
data/README.md
CHANGED
|
@@ -46,6 +46,7 @@ import (
|
|
|
46
46
|
|
|
47
47
|
func main() {
|
|
48
48
|
api := client.New("https://api.vpsfree.cz")
|
|
49
|
+
api.SetLanguage("cs")
|
|
49
50
|
api.SetBasicAuthentication("admin", "secret")
|
|
50
51
|
|
|
51
52
|
action := api.Cluster.PublicStats.Prepare()
|
|
@@ -62,3 +63,17 @@ func main() {
|
|
|
62
63
|
fmt.Printf("%+v\n", resp.Output)
|
|
63
64
|
}
|
|
64
65
|
```
|
|
66
|
+
|
|
67
|
+
## Localization
|
|
68
|
+
|
|
69
|
+
Generated clients include bundled HaveAPI client translations. Set the
|
|
70
|
+
language before making requests:
|
|
71
|
+
|
|
72
|
+
```go
|
|
73
|
+
api := client.New("https://api.vpsfree.cz")
|
|
74
|
+
api.SetLanguage("cs")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The value is sent in `Accept-Language` by default. Use
|
|
78
|
+
`SetLanguageHeader("X-Language")` when the API is configured with a custom
|
|
79
|
+
language header.
|
data/haveapi-go-client.gemspec
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
|
+
require 'yaml'
|
|
2
3
|
require 'haveapi/client'
|
|
3
4
|
require 'haveapi/go_client/utils'
|
|
4
5
|
|
|
@@ -50,12 +51,13 @@ module HaveAPI::GoClient
|
|
|
50
51
|
FileUtils.mkpath(dst)
|
|
51
52
|
end
|
|
52
53
|
|
|
53
|
-
%w[client authentication request response types].each do |v|
|
|
54
|
+
%w[client authentication request response types i18n].each do |v|
|
|
54
55
|
ErbTemplate.render_to_if_changed(
|
|
55
56
|
"#{v}.go",
|
|
56
57
|
{
|
|
57
58
|
package:,
|
|
58
|
-
api
|
|
59
|
+
api:,
|
|
60
|
+
i18n_messages:
|
|
59
61
|
},
|
|
60
62
|
File.join(dst, "#{v}.go")
|
|
61
63
|
)
|
|
@@ -74,5 +76,12 @@ module HaveAPI::GoClient
|
|
|
74
76
|
protected
|
|
75
77
|
|
|
76
78
|
attr_reader :api
|
|
79
|
+
|
|
80
|
+
def i18n_messages
|
|
81
|
+
@i18n_messages ||= YAML.safe_load_file(
|
|
82
|
+
File.expand_path('i18n_messages.yml', __dir__),
|
|
83
|
+
aliases: true
|
|
84
|
+
)
|
|
85
|
+
end
|
|
77
86
|
end
|
|
78
87
|
end
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
authentication:
|
|
7
|
+
callback_failed: "%{callback} failed: %{error}"
|
|
8
|
+
callback_invalid_return: callback has to return an array or 'stop'
|
|
9
|
+
callback_required: Implement callback %{callback}
|
|
10
|
+
invalid_credentials: invalid credentials
|
|
11
|
+
invalid_oauth2_pkce_verifier: Invalid OAuth2 PKCE verifier
|
|
12
|
+
invalid_oauth2_state: Invalid OAuth2 state
|
|
13
|
+
mfa_required: implement multi-factor authentication
|
|
14
|
+
multistep_callback_required: add callback to handle multi-step authentication
|
|
15
|
+
oauth2_access_token_required: Option access_token must be provided
|
|
16
|
+
oauth2_not_configured: OAuth2 authentication is not configured
|
|
17
|
+
oauth2_revoke_failed: Unable to revoke access token, HTTP %{status}
|
|
18
|
+
token_request_failed: 'Unable to request token: %{message}'
|
|
19
|
+
token_revoke_failed: 'Unable to revoke token: %{message}'
|
|
20
|
+
token_step_failed: 'Failed at authentication step ''%{action}'': %{message}'
|
|
21
|
+
unsupported_auth_action: Unsupported authentication action '%{action}'
|
|
22
|
+
errors:
|
|
23
|
+
access_forbidden: Access forbidden. Bad user name or password? Not authorized?
|
|
24
|
+
action_failed: "%{action} failed: %{message}"
|
|
25
|
+
fatal_api_error: 'Fatal API error: %{error}'
|
|
26
|
+
input_parameters_not_valid: input parameters not valid
|
|
27
|
+
input_parameters_not_valid_for_action: Input parameters not valid for action '%{action}'
|
|
28
|
+
invalid_input_parameters: invalid input parameters
|
|
29
|
+
uncancelable_action: 'Action state #%{id} cannot be cancelled'
|
|
30
|
+
unresolved_arguments: 'Unable to execute action ''%{action}'': unresolved arguments'
|
|
31
|
+
validation:
|
|
32
|
+
cannot_be_null: cannot be null
|
|
33
|
+
invalid_boolean: not a valid boolean
|
|
34
|
+
invalid_datetime: not in ISO 8601 format
|
|
35
|
+
invalid_float: not a valid float
|
|
36
|
+
invalid_input_layout: invalid input layout
|
|
37
|
+
invalid_integer: not a valid integer
|
|
38
|
+
invalid_resource_id: not a valid resource id
|
|
39
|
+
invalid_string: not a valid string
|
|
40
|
+
required_parameter_missing: required parameter missing
|
|
41
|
+
validation_failed: validation failed
|
|
42
|
+
validation_failed_with_errors: 'validation failed: %{errors}'
|
|
43
|
+
cs:
|
|
44
|
+
authentication:
|
|
45
|
+
callback_failed: "%{callback} selhal: %{error}"
|
|
46
|
+
callback_invalid_return: callback musí vrátit pole nebo 'stop'
|
|
47
|
+
callback_required: Implementujte callback %{callback}
|
|
48
|
+
invalid_credentials: neplatné přihlašovací údaje
|
|
49
|
+
invalid_oauth2_pkce_verifier: Neplatný OAuth2 PKCE verifier
|
|
50
|
+
invalid_oauth2_state: Neplatný OAuth2 state
|
|
51
|
+
mfa_required: implementujte vícefaktorové ověření
|
|
52
|
+
multistep_callback_required: přidejte callback pro zpracování vícefázového ověření
|
|
53
|
+
oauth2_access_token_required: Volba access_token musí být zadána
|
|
54
|
+
oauth2_not_configured: OAuth2 ověření není nastaveno
|
|
55
|
+
oauth2_revoke_failed: Přístupový token nelze zrušit, HTTP %{status}
|
|
56
|
+
token_request_failed: 'Token nelze vyžádat: %{message}'
|
|
57
|
+
token_revoke_failed: 'Token nelze zrušit: %{message}'
|
|
58
|
+
token_step_failed: 'Krok ověření ''%{action}'' selhal: %{message}'
|
|
59
|
+
unsupported_auth_action: Nepodporovaná ověřovací akce '%{action}'
|
|
60
|
+
errors:
|
|
61
|
+
access_forbidden: Přístup odepřen. Chybné uživatelské jméno nebo heslo? Nejste
|
|
62
|
+
oprávněni?
|
|
63
|
+
action_failed: "%{action} selhala: %{message}"
|
|
64
|
+
fatal_api_error: 'Fatální chyba API: %{error}'
|
|
65
|
+
input_parameters_not_valid: vstupní parametry nejsou platné
|
|
66
|
+
input_parameters_not_valid_for_action: Vstupní parametry pro akci '%{action}'
|
|
67
|
+
nejsou platné
|
|
68
|
+
invalid_input_parameters: neplatné vstupní parametry
|
|
69
|
+
uncancelable_action: 'Stav akce #%{id} nelze zrušit'
|
|
70
|
+
unresolved_arguments: 'Akci ''%{action}'' nelze spustit: chybí argumenty'
|
|
71
|
+
validation:
|
|
72
|
+
cannot_be_null: nesmí být null
|
|
73
|
+
invalid_boolean: neplatná pravdivostní hodnota
|
|
74
|
+
invalid_datetime: není ve formátu ISO 8601
|
|
75
|
+
invalid_float: neplatné desetinné číslo
|
|
76
|
+
invalid_input_layout: neplatná struktura vstupu
|
|
77
|
+
invalid_integer: neplatné celé číslo
|
|
78
|
+
invalid_resource_id: neplatné ID prostředku
|
|
79
|
+
invalid_string: neplatný řetězec
|
|
80
|
+
required_parameter_missing: povinný parametr chybí
|
|
81
|
+
validation_failed: validace selhala
|
|
82
|
+
validation_failed_with_errors: 'validace selhala: %{errors}'
|
|
@@ -170,6 +170,8 @@ RSpec.describe HaveAPI::GoClient::Generator do
|
|
|
170
170
|
|
|
171
171
|
import (
|
|
172
172
|
"math"
|
|
173
|
+
"net/http"
|
|
174
|
+
"strings"
|
|
173
175
|
"testing"
|
|
174
176
|
)
|
|
175
177
|
|
|
@@ -179,6 +181,21 @@ RSpec.describe HaveAPI::GoClient::Generator do
|
|
|
179
181
|
return c
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
func TestValidationErrorCompatibility(t *testing.T) {
|
|
185
|
+
verr := NewValidationError()
|
|
186
|
+
verr.Add("field", "broken")
|
|
187
|
+
if verr.Empty() {
|
|
188
|
+
t.Fatalf("expected validation error to be non-empty")
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
legacyLiteral := ValidationError{map[string][]string{
|
|
192
|
+
"field": []string{"broken"},
|
|
193
|
+
}}
|
|
194
|
+
if legacyLiteral.Empty() {
|
|
195
|
+
t.Fatalf("expected legacy literal validation error to be non-empty")
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
182
199
|
func TestEchoRejectsNaNFloat(t *testing.T) {
|
|
183
200
|
c := newValidationClient()
|
|
184
201
|
req := c.Test.Echo.Prepare()
|
|
@@ -205,6 +222,52 @@ RSpec.describe HaveAPI::GoClient::Generator do
|
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
|
|
225
|
+
func TestLanguageHeaderAndLocalizedValidation(t *testing.T) {
|
|
226
|
+
c := newValidationClient()
|
|
227
|
+
if err := c.SetLanguage("cs-CZ"); err != nil {
|
|
228
|
+
t.Fatalf("set language failed: %v", err)
|
|
229
|
+
}
|
|
230
|
+
if err := c.SetLanguageHeader("X-Language"); err != nil {
|
|
231
|
+
t.Fatalf("set language header failed: %v", err)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
httpReq, err := http.NewRequest("GET", "#{base_url}/v1/test", nil)
|
|
235
|
+
if err != nil {
|
|
236
|
+
t.Fatalf("new request failed: %v", err)
|
|
237
|
+
}
|
|
238
|
+
c.addLanguageHeader(httpReq)
|
|
239
|
+
if got := httpReq.Header.Get("X-Language"); got != "cs-CZ" {
|
|
240
|
+
t.Fatalf("expected language header cs-CZ, got %q", got)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
req := c.Test.Echo.Prepare()
|
|
244
|
+
in := req.NewInput()
|
|
245
|
+
in.SetI(1)
|
|
246
|
+
in.SetF(math.NaN())
|
|
247
|
+
in.SetB(true)
|
|
248
|
+
in.SetDt("2020-01-01T00:00:00Z")
|
|
249
|
+
in.SetS("x")
|
|
250
|
+
in.SetT("y")
|
|
251
|
+
|
|
252
|
+
_, err = req.Call()
|
|
253
|
+
if err == nil {
|
|
254
|
+
t.Fatalf("expected validation error, got nil")
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
verr, ok := err.(*ValidationError)
|
|
258
|
+
if !ok {
|
|
259
|
+
t.Fatalf("expected ValidationError, got %T: %v", err, err)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if got := strings.Join(verr.Errors["f"], " "); !strings.Contains(got, "neplatné desetinné číslo") {
|
|
263
|
+
t.Fatalf("expected Czech float error, got %q", got)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if !strings.Contains(err.Error(), "validace selhala") {
|
|
267
|
+
t.Fatalf("expected Czech validation summary, got %q", err.Error())
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
208
271
|
func TestEchoRejectsInvalidDatetime(t *testing.T) {
|
|
209
272
|
c := newValidationClient()
|
|
210
273
|
req := c.Test.Echo.Prepare()
|
|
@@ -422,6 +485,12 @@ RSpec.describe HaveAPI::GoClient::Generator do
|
|
|
422
485
|
c := New("http://unused.example")
|
|
423
486
|
c.SetHTTPClient(&http.Client{Transport: transport})
|
|
424
487
|
c.SetExistingOAuth2Auth(token)
|
|
488
|
+
if err := c.SetLanguage("cs-CZ"); err != nil {
|
|
489
|
+
t.Fatalf("set language failed: %v", err)
|
|
490
|
+
}
|
|
491
|
+
if err := c.SetLanguageHeader("X-Language"); err != nil {
|
|
492
|
+
t.Fatalf("set language header failed: %v", err)
|
|
493
|
+
}
|
|
425
494
|
|
|
426
495
|
if err := c.RevokeAccessToken(); err != nil {
|
|
427
496
|
t.Fatalf("revoke failed: %v", err)
|
|
@@ -443,6 +512,10 @@ RSpec.describe HaveAPI::GoClient::Generator do
|
|
|
443
512
|
t.Fatalf("expected OAuth2 header %q, got %q", token, got)
|
|
444
513
|
}
|
|
445
514
|
|
|
515
|
+
if got := transport.req.Header.Get("X-Language"); got != "cs-CZ" {
|
|
516
|
+
t.Fatalf("expected language header cs-CZ, got %q", got)
|
|
517
|
+
}
|
|
518
|
+
|
|
446
519
|
if got := transport.req.Header.Get("Content-Type"); got != "application/x-www-form-urlencoded" {
|
|
447
520
|
t.Fatalf("expected form content type, got %q", got)
|
|
448
521
|
}
|
data/template/action.go.erb
CHANGED
|
@@ -367,7 +367,7 @@ func (inv *<%= action.go_invocation_type %>) IsMetaParameterNil(param string) bo
|
|
|
367
367
|
<% end -%>
|
|
368
368
|
|
|
369
369
|
func (inv *<%= action.go_invocation_type %>) validate() error {
|
|
370
|
-
verr := NewValidationError()
|
|
370
|
+
verr := NewValidationError(inv.Action.Client)
|
|
371
371
|
<% if action.has_input? -%>
|
|
372
372
|
if inv.Input != nil {
|
|
373
373
|
<% action.input.parameters.each do |p| -%>
|
|
@@ -376,18 +376,18 @@ func (inv *<%= action.go_invocation_type %>) validate() error {
|
|
|
376
376
|
if !inv.IsParameterNil(<%= go_string_literal(p.go_name) %>) {
|
|
377
377
|
<% if p.type == 'Float' -%>
|
|
378
378
|
if !isFiniteFloat64(inv.Input.<%= p.go_name %>) {
|
|
379
|
-
verr.Add(<%= go_string_literal(p.name) %>, "
|
|
379
|
+
verr.Add(<%= go_string_literal(p.name) %>, t(inv.Action.Client, "validation.invalid_float", nil))
|
|
380
380
|
}
|
|
381
381
|
<% elsif p.type == 'Datetime' -%>
|
|
382
382
|
normalized, ok := normalizeAndCheckDatetimeString(inv.Input.<%= p.go_name %>)
|
|
383
383
|
if !ok {
|
|
384
|
-
verr.Add(<%= go_string_literal(p.name) %>, "
|
|
384
|
+
verr.Add(<%= go_string_literal(p.name) %>, t(inv.Action.Client, "validation.invalid_datetime", nil))
|
|
385
385
|
} else {
|
|
386
386
|
inv.Input.<%= p.go_name %> = normalized
|
|
387
387
|
}
|
|
388
388
|
<% elsif p.type == 'Resource' -%>
|
|
389
389
|
if inv.Input.<%= p.go_name %> < 0 {
|
|
390
|
-
verr.Add(<%= go_string_literal(p.name) %>, "
|
|
390
|
+
verr.Add(<%= go_string_literal(p.name) %>, t(inv.Action.Client, "validation.invalid_resource_id", nil))
|
|
391
391
|
}
|
|
392
392
|
<% end -%>
|
|
393
393
|
}
|
|
@@ -404,18 +404,18 @@ func (inv *<%= action.go_invocation_type %>) validate() error {
|
|
|
404
404
|
if !inv.IsMetaParameterNil(<%= go_string_literal(p.go_name) %>) {
|
|
405
405
|
<% if p.type == 'Float' -%>
|
|
406
406
|
if !isFiniteFloat64(inv.MetaInput.<%= p.go_name %>) {
|
|
407
|
-
verr.Add(<%= go_string_literal(p.name) %>, "
|
|
407
|
+
verr.Add(<%= go_string_literal(p.name) %>, t(inv.Action.Client, "validation.invalid_float", nil))
|
|
408
408
|
}
|
|
409
409
|
<% elsif p.type == 'Datetime' -%>
|
|
410
410
|
normalized, ok := normalizeAndCheckDatetimeString(inv.MetaInput.<%= p.go_name %>)
|
|
411
411
|
if !ok {
|
|
412
|
-
verr.Add(<%= go_string_literal(p.name) %>, "
|
|
412
|
+
verr.Add(<%= go_string_literal(p.name) %>, t(inv.Action.Client, "validation.invalid_datetime", nil))
|
|
413
413
|
} else {
|
|
414
414
|
inv.MetaInput.<%= p.go_name %> = normalized
|
|
415
415
|
}
|
|
416
416
|
<% elsif p.type == 'Resource' -%>
|
|
417
417
|
if inv.MetaInput.<%= p.go_name %> < 0 {
|
|
418
|
-
verr.Add(<%= go_string_literal(p.name) %>, "
|
|
418
|
+
verr.Add(<%= go_string_literal(p.name) %>, t(inv.Action.Client, "validation.invalid_resource_id", nil))
|
|
419
419
|
}
|
|
420
420
|
<% end -%>
|
|
421
421
|
}
|
|
@@ -28,7 +28,7 @@ func (client *Client) SetExistingOAuth2Auth(accessToken string) {
|
|
|
28
28
|
func (client *Client) RevokeAccessToken() error {
|
|
29
29
|
auth, ok := client.Authentication.(*OAuth2Auth)
|
|
30
30
|
if !ok {
|
|
31
|
-
return fmt.Errorf("
|
|
31
|
+
return fmt.Errorf("%s", t(client, "authentication.oauth2_not_configured", nil))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
form := url.Values{}
|
|
@@ -46,6 +46,7 @@ func (client *Client) RevokeAccessToken() error {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
49
|
+
client.addLanguageHeader(req)
|
|
49
50
|
auth.Authenticate(req)
|
|
50
51
|
|
|
51
52
|
resp, err := client.do(req)
|
|
@@ -57,7 +58,9 @@ func (client *Client) RevokeAccessToken() error {
|
|
|
57
58
|
defer resp.Body.Close()
|
|
58
59
|
|
|
59
60
|
if resp.StatusCode != 200 {
|
|
60
|
-
return fmt.Errorf("
|
|
61
|
+
return fmt.Errorf("%s", t(client, "authentication.oauth2_revoke_failed", map[string]string{
|
|
62
|
+
"status": fmt.Sprint(resp.StatusCode),
|
|
63
|
+
}))
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
client.Authentication = nil
|
|
@@ -65,7 +65,9 @@ func (client *Client) SetNewTokenAuth(options *TokenAuthOptions) error {
|
|
|
65
65
|
if err != nil {
|
|
66
66
|
return err
|
|
67
67
|
} else if !resp.Status {
|
|
68
|
-
return fmt.Errorf("
|
|
68
|
+
return fmt.Errorf("%s", t(client, "authentication.token_request_failed", map[string]string{
|
|
69
|
+
"message": resp.Message,
|
|
70
|
+
}))
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
if resp.Output.Complete {
|
|
@@ -101,11 +103,16 @@ func (auth *TokenAuth) nextAuthenticationStep(options *TokenAuthOptions, action
|
|
|
101
103
|
input.SetToken(token)
|
|
102
104
|
|
|
103
105
|
if options.<%= "#{a.go_name}Callback" %> == nil {
|
|
104
|
-
return fmt.Errorf(
|
|
106
|
+
return fmt.Errorf("%s", t(auth.Resource.Client, "authentication.callback_required", map[string]string{
|
|
107
|
+
"callback": <%= go_string_literal("#{a.go_name}Callback") %>,
|
|
108
|
+
}))
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
if err := options.<%= "#{a.go_name}Callback" %>(input); err != nil {
|
|
108
|
-
return fmt.Errorf(
|
|
112
|
+
return fmt.Errorf("%s", t(auth.Resource.Client, "authentication.callback_failed", map[string]string{
|
|
113
|
+
"callback": <%= go_string_literal("#{a.go_name}Callback") %>,
|
|
114
|
+
"error": err.Error(),
|
|
115
|
+
}))
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
resp, err := request.Call()
|
|
@@ -113,7 +120,10 @@ func (auth *TokenAuth) nextAuthenticationStep(options *TokenAuthOptions, action
|
|
|
113
120
|
if err != nil {
|
|
114
121
|
return err
|
|
115
122
|
} else if !resp.Status {
|
|
116
|
-
return fmt.Errorf("
|
|
123
|
+
return fmt.Errorf("%s", t(auth.Resource.Client, "authentication.token_step_failed", map[string]string{
|
|
124
|
+
"action": action,
|
|
125
|
+
"message": resp.Message,
|
|
126
|
+
}))
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
if resp.Output.Complete {
|
|
@@ -130,7 +140,9 @@ func (auth *TokenAuth) nextAuthenticationStep(options *TokenAuthOptions, action
|
|
|
130
140
|
}
|
|
131
141
|
<% end -%>
|
|
132
142
|
|
|
133
|
-
return fmt.Errorf("
|
|
143
|
+
return fmt.Errorf("%s", t(auth.Resource.Client, "authentication.unsupported_auth_action", map[string]string{
|
|
144
|
+
"action": action,
|
|
145
|
+
}))
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
// SetExistingTokenAuth will use a previously acquired token
|
|
@@ -156,7 +168,9 @@ func (client *Client) RevokeAuthToken() error {
|
|
|
156
168
|
if err != nil {
|
|
157
169
|
return err
|
|
158
170
|
} else if !resp.Status {
|
|
159
|
-
return fmt.Errorf("
|
|
171
|
+
return fmt.Errorf("%s", t(client, "authentication.token_revoke_failed", map[string]string{
|
|
172
|
+
"message": resp.Message,
|
|
173
|
+
}))
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
client.Authentication = nil
|
data/template/client.go.erb
CHANGED
|
@@ -13,6 +13,12 @@ type Client struct {
|
|
|
13
13
|
// Options for authentication method
|
|
14
14
|
Authentication Authenticator
|
|
15
15
|
|
|
16
|
+
// Language sent to the API server, e.g. "cs" or "cs-CZ".
|
|
17
|
+
Language string
|
|
18
|
+
|
|
19
|
+
// HTTP header used to send Language. Defaults to Accept-Language.
|
|
20
|
+
LanguageHeader string
|
|
21
|
+
|
|
16
22
|
httpClient *http.Client
|
|
17
23
|
oauth2TrustedOrigins map[string]struct{}
|
|
18
24
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
package <%= package %>
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"net/http"
|
|
5
|
+
"sort"
|
|
6
|
+
"strconv"
|
|
7
|
+
"strings"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
const defaultLanguageHeader = "Accept-Language"
|
|
11
|
+
|
|
12
|
+
var clientI18nMessages = map[string]map[string]map[string]string{
|
|
13
|
+
<% i18n_messages.keys.sort.each do |locale| -%>
|
|
14
|
+
<%= go_string_literal(locale) %>: {
|
|
15
|
+
<% i18n_messages[locale].keys.sort.each do |group| -%>
|
|
16
|
+
<%= go_string_literal(group) %>: {
|
|
17
|
+
<% i18n_messages[locale][group].keys.sort.each do |key| -%>
|
|
18
|
+
<%= go_string_literal(key) %>: <%= go_string_literal(i18n_messages[locale][group][key]) %>,
|
|
19
|
+
<% end -%>
|
|
20
|
+
},
|
|
21
|
+
<% end -%>
|
|
22
|
+
},
|
|
23
|
+
<% end -%>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// SetLanguage configures the value sent in the language request header.
|
|
27
|
+
func (client *Client) SetLanguage(language string) error {
|
|
28
|
+
if strings.ContainsAny(language, "\x00\r\n") {
|
|
29
|
+
err := NewValidationError(client)
|
|
30
|
+
err.Errors["language"] = []string{
|
|
31
|
+
t(client, "validation.invalid_string", nil),
|
|
32
|
+
}
|
|
33
|
+
return err
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
client.Language = language
|
|
37
|
+
return nil
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// SetLanguageHeader configures the HTTP header used by SetLanguage.
|
|
41
|
+
func (client *Client) SetLanguageHeader(header string) error {
|
|
42
|
+
if !validHeaderName(header) {
|
|
43
|
+
err := NewValidationError(client)
|
|
44
|
+
err.Errors["language_header"] = []string{
|
|
45
|
+
t(client, "validation.invalid_string", nil),
|
|
46
|
+
}
|
|
47
|
+
return err
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
client.LanguageHeader = header
|
|
51
|
+
return nil
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func (client *Client) addLanguageHeader(req *http.Request) {
|
|
55
|
+
if client == nil || req == nil || client.Language == "" {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
req.Header.Set(client.languageHeaderName(), client.Language)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func (client *Client) languageHeaderName() string {
|
|
63
|
+
if client == nil || client.LanguageHeader == "" {
|
|
64
|
+
return defaultLanguageHeader
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return client.LanguageHeader
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func t(client *Client, key string, values map[string]string) string {
|
|
71
|
+
language := ""
|
|
72
|
+
if client != nil {
|
|
73
|
+
language = client.Language
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return tLanguage(language, key, values)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func tLanguage(language string, key string, values map[string]string) string {
|
|
80
|
+
message := lookupMessage(localeFor(language), key)
|
|
81
|
+
if message == "" {
|
|
82
|
+
message = lookupMessage("en", key)
|
|
83
|
+
}
|
|
84
|
+
if message == "" {
|
|
85
|
+
message = key
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for name, value := range values {
|
|
89
|
+
message = strings.ReplaceAll(message, "%{"+name+"}", value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return message
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func lookupMessage(locale string, key string) string {
|
|
96
|
+
parts := strings.Split(strings.TrimPrefix(key, "haveapi_client."), ".")
|
|
97
|
+
if len(parts) != 2 {
|
|
98
|
+
return ""
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
groups, ok := clientI18nMessages[locale]
|
|
102
|
+
if !ok {
|
|
103
|
+
return ""
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
messages, ok := groups[parts[0]]
|
|
107
|
+
if !ok {
|
|
108
|
+
return ""
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return messages[parts[1]]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func localeFor(language string) string {
|
|
115
|
+
if language == "" {
|
|
116
|
+
return "en"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for _, tag := range parseAcceptLanguage(language) {
|
|
120
|
+
locale := normalizeLocale(tag)
|
|
121
|
+
if _, ok := clientI18nMessages[locale]; ok {
|
|
122
|
+
return locale
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return "en"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func parseAcceptLanguage(header string) []string {
|
|
130
|
+
type tag struct {
|
|
131
|
+
name string
|
|
132
|
+
q float64
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
tags := []tag{}
|
|
136
|
+
for _, part := range strings.Split(header, ",") {
|
|
137
|
+
tokens := strings.Split(part, ";")
|
|
138
|
+
name := strings.TrimSpace(tokens[0])
|
|
139
|
+
q := 1.0
|
|
140
|
+
|
|
141
|
+
for _, token := range tokens[1:] {
|
|
142
|
+
pair := strings.SplitN(token, "=", 2)
|
|
143
|
+
if len(pair) == 2 && strings.TrimSpace(pair[0]) == "q" {
|
|
144
|
+
if parsed, err := strconv.ParseFloat(strings.TrimSpace(pair[1]), 64); err == nil {
|
|
145
|
+
q = parsed
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if name != "" && q > 0 {
|
|
151
|
+
tags = append(tags, tag{name: name, q: q})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
sort.SliceStable(tags, func(i, j int) bool {
|
|
156
|
+
return tags[i].q > tags[j].q
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
ret := make([]string, 0, len(tags))
|
|
160
|
+
for _, tag := range tags {
|
|
161
|
+
ret = append(ret, tag.name)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return ret
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func normalizeLocale(tag string) string {
|
|
168
|
+
normalized := strings.ToLower(strings.TrimSpace(strings.ReplaceAll(tag, "_", "-")))
|
|
169
|
+
if i := strings.Index(normalized, "."); i >= 0 {
|
|
170
|
+
normalized = normalized[:i]
|
|
171
|
+
}
|
|
172
|
+
if i := strings.Index(normalized, "-"); i >= 0 {
|
|
173
|
+
normalized = normalized[:i]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return normalized
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
func validHeaderName(header string) bool {
|
|
180
|
+
if header == "" {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for _, r := range header {
|
|
185
|
+
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
switch r {
|
|
190
|
+
case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~':
|
|
191
|
+
continue
|
|
192
|
+
default:
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return true
|
|
198
|
+
}
|
data/template/request.go.erb
CHANGED
|
@@ -182,6 +182,8 @@ func (client *Client) DoQueryStringRequest(path string, queryParams map[string]s
|
|
|
182
182
|
return err
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
client.addLanguageHeader(req)
|
|
186
|
+
|
|
185
187
|
if client.Authentication != nil {
|
|
186
188
|
client.Authentication.Authenticate(req)
|
|
187
189
|
}
|
|
@@ -232,6 +234,7 @@ func (client *Client) DoBodyRequest(method string, path string, params interface
|
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
req.Header.Set("Content-Type", "application/json")
|
|
237
|
+
client.addLanguageHeader(req)
|
|
235
238
|
|
|
236
239
|
if client.Authentication != nil {
|
|
237
240
|
client.Authentication.Authenticate(req)
|
data/template/types.go.erb
CHANGED
|
@@ -2,10 +2,13 @@ package <%= package %>
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"math"
|
|
5
|
+
"runtime"
|
|
5
6
|
"sort"
|
|
6
7
|
"strconv"
|
|
7
8
|
"strings"
|
|
9
|
+
"sync"
|
|
8
10
|
"time"
|
|
11
|
+
"unsafe"
|
|
9
12
|
)
|
|
10
13
|
|
|
11
14
|
type ProgressCallbackReturn int
|
|
@@ -28,10 +31,22 @@ type ValidationError struct {
|
|
|
28
31
|
Errors map[string][]string
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
// Keep the exported ValidationError layout compatible with older generated clients.
|
|
35
|
+
var validationErrorLanguages sync.Map
|
|
36
|
+
|
|
37
|
+
func NewValidationError(client ...*Client) *ValidationError {
|
|
38
|
+
ret := &ValidationError{
|
|
33
39
|
Errors: make(map[string][]string),
|
|
34
40
|
}
|
|
41
|
+
|
|
42
|
+
if len(client) > 0 && client[0] != nil && client[0].Language != "" {
|
|
43
|
+
validationErrorLanguages.Store(validationErrorKey(ret), client[0].Language)
|
|
44
|
+
runtime.SetFinalizer(ret, func(e *ValidationError) {
|
|
45
|
+
validationErrorLanguages.Delete(validationErrorKey(e))
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return ret
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
func (e *ValidationError) Add(param string, msg string) {
|
|
@@ -52,7 +67,11 @@ func (e *ValidationError) Empty() bool {
|
|
|
52
67
|
|
|
53
68
|
func (e *ValidationError) Error() string {
|
|
54
69
|
if e == nil || len(e.Errors) == 0 {
|
|
55
|
-
|
|
70
|
+
if e == nil {
|
|
71
|
+
return t(nil, "validation.validation_failed", nil)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return tLanguage(e.language(), "validation.validation_failed", nil)
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
keys := make([]string, 0, len(e.Errors))
|
|
@@ -72,7 +91,27 @@ func (e *ValidationError) Error() string {
|
|
|
72
91
|
}
|
|
73
92
|
}
|
|
74
93
|
|
|
75
|
-
return
|
|
94
|
+
return tLanguage(e.language(), "validation.validation_failed_with_errors", map[string]string{
|
|
95
|
+
"errors": strings.Join(parts, "; "),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func (e *ValidationError) language() string {
|
|
100
|
+
if e == nil {
|
|
101
|
+
return ""
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
v, ok := validationErrorLanguages.Load(validationErrorKey(e))
|
|
105
|
+
if !ok {
|
|
106
|
+
return ""
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
language, _ := v.(string)
|
|
110
|
+
return language
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func validationErrorKey(e *ValidationError) uintptr {
|
|
114
|
+
return uintptr(unsafe.Pointer(e))
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
func isFiniteFloat64(v float64) bool {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi-go-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
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.29.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.29.0
|
|
26
26
|
description: Go client generator
|
|
27
27
|
email:
|
|
28
28
|
- jakub.skokan@vpsfree.cz
|
|
@@ -49,6 +49,7 @@ files:
|
|
|
49
49
|
- lib/haveapi/go_client/cli.rb
|
|
50
50
|
- lib/haveapi/go_client/erb_template.rb
|
|
51
51
|
- lib/haveapi/go_client/generator.rb
|
|
52
|
+
- lib/haveapi/go_client/i18n_messages.yml
|
|
52
53
|
- lib/haveapi/go_client/input_output.rb
|
|
53
54
|
- lib/haveapi/go_client/metadata.rb
|
|
54
55
|
- lib/haveapi/go_client/parameter.rb
|
|
@@ -70,6 +71,7 @@ files:
|
|
|
70
71
|
- template/authentication/token.go.erb
|
|
71
72
|
- template/client.go.erb
|
|
72
73
|
- template/go.mod.erb
|
|
74
|
+
- template/i18n.go.erb
|
|
73
75
|
- template/request.go.erb
|
|
74
76
|
- template/resource.go.erb
|
|
75
77
|
- template/response.go.erb
|