haveapi 0.32.1 → 0.32.2
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/.gitignore +3 -8
- data/README.md +3 -422
- data/Rakefile +17 -7
- data/benchmarks/performance_compare.rb +27 -6
- data/doc/cs/client-examples.md +535 -0
- data/doc/cs/create-client.md +118 -0
- data/doc/cs/json-schema.html +1182 -0
- data/doc/cs/protocol.md +595 -0
- data/doc/cs/readme.md +432 -0
- data/doc/cs/typed-input-validation.md +122 -0
- data/doc/{client-examples.md → en/client-examples.md} +20 -10
- data/doc/{documentation-ui.md → en/documentation-ui.md} +42 -6
- data/doc/{performance.md → en/performance.md} +11 -0
- data/doc/{protocol.md → en/protocol.md} +2 -2
- data/doc/en/readme.md +423 -0
- data/haveapi.gemspec +2 -2
- data/lib/haveapi/client_example.rb +18 -5
- data/lib/haveapi/client_examples/curl.rb +5 -5
- data/lib/haveapi/client_examples/dynamic_helpers.rb +10 -10
- data/lib/haveapi/client_examples/go_client.rb +1 -1
- data/lib/haveapi/client_examples/http.rb +4 -4
- data/lib/haveapi/client_examples/js_client.rb +10 -10
- data/lib/haveapi/client_examples/php_client.rb +24 -25
- data/lib/haveapi/client_examples/python_client.rb +11 -11
- data/lib/haveapi/client_examples/ruby_cli.rb +6 -9
- data/lib/haveapi/client_examples/ruby_client.rb +24 -16
- data/lib/haveapi/client_examples/rust_client.rb +1 -1
- data/lib/haveapi/client_examples/ts_generated_client.rb +1 -1
- data/lib/haveapi/documentation_context.rb +14 -2
- data/lib/haveapi/documentation_helpers.rb +92 -15
- data/lib/haveapi/documentation_locale.rb +65 -0
- data/lib/haveapi/documentation_oauth.rb +12 -12
- data/lib/haveapi/documentation_search.rb +5 -5
- data/lib/haveapi/documentation_validators.rb +43 -33
- data/lib/haveapi/locales/cs.yml +468 -0
- data/lib/haveapi/locales/en.yml +467 -0
- data/lib/haveapi/public/css/documentation.css +8 -4
- data/lib/haveapi/public/js/documentation.js +73 -20
- data/lib/haveapi/server.rb +32 -47
- data/lib/haveapi/shared_documentation_helpers.rb +53 -5
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/doc_layout.erb +3 -2
- data/lib/haveapi/views/doc_sidebars/create-client.erb +10 -10
- data/lib/haveapi/views/doc_sidebars/json-schema.erb +2 -2
- data/lib/haveapi/views/doc_sidebars/protocol.erb +24 -24
- data/lib/haveapi/views/documentation/search_form.erb +5 -4
- data/lib/haveapi/views/documentation/search_results.erb +6 -6
- data/lib/haveapi/views/index.erb +3 -3
- data/lib/haveapi/views/main_layout.erb +4 -4
- data/lib/haveapi/views/shared_documentation.erb +8 -5
- data/lib/haveapi/views/shared_sidebar.erb +11 -11
- data/lib/haveapi/views/version_page/auth_body.erb +7 -7
- data/lib/haveapi/views/version_page/client_auth.erb +1 -1
- data/lib/haveapi/views/version_page/client_example.erb +1 -1
- data/lib/haveapi/views/version_page/client_init.erb +1 -1
- data/lib/haveapi/views/version_page/client_overview.erb +7 -5
- data/lib/haveapi/views/version_page/example_panels.erb +5 -2
- data/lib/haveapi/views/version_page/resource_body.erb +24 -24
- data/lib/haveapi/views/version_page/resource_directory.erb +2 -2
- data/lib/haveapi/views/version_page/usage.erb +5 -5
- data/lib/haveapi/views/version_page.erb +11 -11
- data/lib/haveapi/views/version_sidebar/auth_nav.erb +2 -2
- data/lib/haveapi/views/version_sidebar/authentication_control.erb +8 -8
- data/lib/haveapi/views/version_sidebar/authentication_status.erb +1 -1
- data/lib/haveapi/views/version_sidebar/brand.erb +16 -5
- data/lib/haveapi/views/version_sidebar.erb +33 -18
- data/spec/client_examples/assets/documentation_test.cjs +107 -9
- data/spec/client_examples/comment_capture.rb +72 -0
- data/spec/client_examples/comments_spec.rb +162 -0
- data/spec/client_examples/context_spec.rb +18 -2
- data/spec/client_examples/detail_spec.rb +68 -0
- data/spec/documentation/client_overview_spec.rb +60 -0
- data/spec/documentation/example_selection_spec.rb +192 -0
- data/spec/documentation/language_settings_spec.rb +132 -0
- data/spec/documentation/locale_spec.rb +367 -0
- data/spec/documentation/navigation_spec.rb +61 -3
- data/spec/documentation/oauth_spec.rb +42 -0
- data/spec/documentation/search_index_routes_spec.rb +8 -4
- data/spec/documentation/search_spec.rb +1 -1
- data/spec/documentation/shared_pages_spec.rb +63 -2
- data/spec/documentation/validators_spec.rb +25 -0
- data/spec/extensions/exception_mailer_spec.rb +2 -2
- data/spec/packaging_spec.rb +68 -9
- data/spec/performance/event_baseline_spec.rb +1 -1
- data/spec/performance/source_versions_spec.rb +111 -2
- metadata +24 -11
- /data/doc/{create-client.md → en/create-client.md} +0 -0
- /data/doc/{documentation-oauth.md → en/documentation-oauth.md} +0 -0
- /data/doc/{json-schema.html → en/json-schema.html} +0 -0
- /data/doc/{typed-input-validation.md → en/typed-input-validation.md} +0 -0
|
@@ -47,9 +47,9 @@ module HaveAPI::ClientExamples
|
|
|
47
47
|
result = {}
|
|
48
48
|
value.each do |key, entry|
|
|
49
49
|
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
50
|
-
raise Unsupported, '
|
|
50
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.object_key')
|
|
51
51
|
end
|
|
52
|
-
raise Unsupported, '
|
|
52
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.duplicate_object_keys') if result.has_key?(key.to_s)
|
|
53
53
|
|
|
54
54
|
result[key.to_s] = json_data(entry)
|
|
55
55
|
end
|
|
@@ -59,17 +59,17 @@ module HaveAPI::ClientExamples
|
|
|
59
59
|
when String, Integer, TrueClass, FalseClass, NilClass
|
|
60
60
|
value
|
|
61
61
|
when Float
|
|
62
|
-
raise Unsupported, '
|
|
62
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.non_finite_number') unless value.finite?
|
|
63
63
|
|
|
64
64
|
value
|
|
65
65
|
else
|
|
66
|
-
raise Unsupported, '
|
|
66
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.literal')
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def structured_sample(sample)
|
|
71
71
|
unless sample.is_a?(Hash) && (!field(sample, :request).nil? || !field(sample, :response).nil?)
|
|
72
|
-
raise Unsupported, '
|
|
72
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.prose_only')
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
%i[global object].each do |scope|
|
|
@@ -77,29 +77,29 @@ module HaveAPI::ClientExamples
|
|
|
77
77
|
|
|
78
78
|
input = field(field(field(current_action, :meta), scope), :input)
|
|
79
79
|
if field(input, :parameters, {}).values.any? { |parameter| field(parameter, :required) }
|
|
80
|
-
raise Unsupported, '
|
|
80
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.input_metadata')
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
placeholders = field(current_action, :path, '').scan(/\{([a-zA-Z0-9_-]+)\}/).flatten
|
|
85
85
|
values = field(sample, :path_params, []) || []
|
|
86
86
|
unless values.is_a?(Array) && values.length == placeholders.length
|
|
87
|
-
raise Unsupported, '
|
|
87
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.path_arguments_missing')
|
|
88
88
|
end
|
|
89
89
|
unless values.all? { |value| value.is_a?(String) || value.is_a?(Integer) }
|
|
90
|
-
raise Unsupported, '
|
|
90
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.path_argument_type')
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
request = field(sample, :request)
|
|
94
94
|
input = field(current_action, :input)
|
|
95
95
|
if !request.nil? && input.nil? && request != {}
|
|
96
|
-
raise Unsupported, '
|
|
96
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.input_description_missing')
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
if request.is_a?(Hash) && input
|
|
100
100
|
known = field(input, :parameters, {}).keys.map(&:to_s)
|
|
101
101
|
unless (request.keys.map(&:to_s) - known).empty?
|
|
102
|
-
raise Unsupported, '
|
|
102
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.input_hidden')
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
[placeholders.zip(values).to_h, json_data(request)]
|
|
@@ -34,7 +34,7 @@ module HaveAPI::ClientExamples
|
|
|
34
34
|
|
|
35
35
|
def render_documentation(kind, sample: nil, **)
|
|
36
36
|
unless context && self.class.available?(context)
|
|
37
|
-
return { status: :limitation, reason: '
|
|
37
|
+
return { status: :limitation, reason: HaveAPI.t('haveapi.documentation.limitations.go_profile') }.freeze
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
selected = kind == :example ? context.with(sample:) : context
|
|
@@ -42,17 +42,17 @@ module HaveAPI::ClientExamples
|
|
|
42
42
|
|
|
43
43
|
when :oauth2
|
|
44
44
|
<<~END
|
|
45
|
-
# 1) Request authorization code
|
|
45
|
+
#{comment_lines(example_comment('http_oauth_code', default: '1) Request authorization code'), '# ')}
|
|
46
46
|
GET #{desc[:authorize_path]}?response_type=code&client_id=example-client&state=example-state&redirect_uri=https%3A%2F%2Fexample.org%2Fcallback HTTP/1.1
|
|
47
47
|
Host: #{host}
|
|
48
48
|
|
|
49
|
-
# 2) The user logs in using this API
|
|
49
|
+
#{comment_lines(example_comment('http_oauth_login', default: '2) The user logs in using this API'), '# ')}
|
|
50
50
|
|
|
51
|
-
# 3) The API then redirects the user back to the client application
|
|
51
|
+
#{comment_lines(example_comment('http_oauth_redirect', default: '3) The API then redirects the user back to the client application'), '# ')}
|
|
52
52
|
GET /callback?code=example-code&state=example-state
|
|
53
53
|
Host: client-application
|
|
54
54
|
|
|
55
|
-
# 4) The client application requests access token
|
|
55
|
+
#{comment_lines(example_comment('http_oauth_token', default: '4) The client application requests access token'), '# ')}
|
|
56
56
|
POST #{desc[:token_path]}
|
|
57
57
|
Content-Type: application/x-www-form-urlencoded
|
|
58
58
|
|
|
@@ -32,7 +32,7 @@ module HaveAPI::ClientExamples
|
|
|
32
32
|
when Array
|
|
33
33
|
"[#{value.map { |entry| js_literal(entry) }.join(', ')}]"
|
|
34
34
|
when Integer
|
|
35
|
-
raise Unsupported, '
|
|
35
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.javascript_integer') if value.abs > 9_007_199_254_740_991
|
|
36
36
|
|
|
37
37
|
value.to_s
|
|
38
38
|
else
|
|
@@ -103,7 +103,7 @@ module HaveAPI::ClientExamples
|
|
|
103
103
|
return nil unless field(current_action, :auth)
|
|
104
104
|
|
|
105
105
|
method = %i[token basic oauth2].find { |name| supported_auth?(name) }
|
|
106
|
-
raise Unsupported, '
|
|
106
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.action_authentication') unless method
|
|
107
107
|
|
|
108
108
|
options = {
|
|
109
109
|
token: '{token: "example-token"}',
|
|
@@ -118,7 +118,7 @@ module HaveAPI::ClientExamples
|
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def auth(method, description)
|
|
121
|
-
return limitation('
|
|
121
|
+
return limitation(HaveAPI.t('haveapi.documentation.limitations.authentication_provider')) unless supported_auth?(method)
|
|
122
122
|
|
|
123
123
|
code_result do
|
|
124
124
|
options = case method.to_sym
|
|
@@ -140,7 +140,7 @@ module HaveAPI::ClientExamples
|
|
|
140
140
|
end
|
|
141
141
|
source = program(thorough? ? "console.log(\"Authenticated\");\n" : '', authentication:)
|
|
142
142
|
if method.to_sym == :oauth2
|
|
143
|
-
source = "
|
|
143
|
+
source = "#{comment_lines(example_comment('js_existing_oauth', default: 'Use an existing access token; this client does not run an OAuth authorization flow.'), '// ')}\n#{source}"
|
|
144
144
|
end
|
|
145
145
|
source
|
|
146
146
|
end
|
|
@@ -169,16 +169,16 @@ module HaveAPI::ClientExamples
|
|
|
169
169
|
code_result do
|
|
170
170
|
path, request = structured_sample(sample)
|
|
171
171
|
if request.is_a?(Array)
|
|
172
|
-
raise Unsupported, '
|
|
172
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.javascript_list_input')
|
|
173
173
|
end
|
|
174
174
|
if !request.nil? && !request.is_a?(Hash)
|
|
175
|
-
raise Unsupported, '
|
|
175
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.javascript_object_input')
|
|
176
176
|
end
|
|
177
177
|
if !asynchronous? && field(field(current_action, :input), :parameters, {}).keys.any? { |name| %w[hasOwnProperty __proto__].include?(name.to_s) }
|
|
178
|
-
raise Unsupported, '
|
|
178
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.callback_parameter_name')
|
|
179
179
|
end
|
|
180
180
|
if request && %w[GET OPTIONS].include?(field(current_action, :method).to_s.upcase) && request.values.any? { |value| value.is_a?(Hash) || value.is_a?(Array) }
|
|
181
|
-
raise Unsupported, '
|
|
181
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.javascript_nested_query')
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
args = path.values.map { |value| js_literal(value) }
|
|
@@ -191,7 +191,7 @@ module HaveAPI::ClientExamples
|
|
|
191
191
|
body << "console.log(result.value);\n"
|
|
192
192
|
body << "console.log(result.response.meta);\n" if thorough?
|
|
193
193
|
if field(current_action, :blocking)
|
|
194
|
-
body << "
|
|
194
|
+
body << "#{comment_lines(example_comment('js_request_completion', default: 'Request success alone does not establish operation completion.'), '// ')}\n"
|
|
195
195
|
body << "const completed = await api.waitForCompletion(result, {timeoutMs: 300000});\nconsole.log(completed.state);\n"
|
|
196
196
|
end
|
|
197
197
|
else
|
|
@@ -209,7 +209,7 @@ module HaveAPI::ClientExamples
|
|
|
209
209
|
options << " console.log(reply.meta());\n" if thorough?
|
|
210
210
|
options << "}\n"
|
|
211
211
|
body << "action.directInvoke(#{(args + ["{\n#{indent(options)}}"]).join(', ')});\n"
|
|
212
|
-
body << "
|
|
212
|
+
body << "#{comment_lines(example_comment('js_initial_response', default: 'This prints the initial response; use its action-state metadata to monitor completion.'), '// ')}\n" if field(current_action, :blocking)
|
|
213
213
|
end
|
|
214
214
|
authentication = action_authentication
|
|
215
215
|
thorough? ? program(body, authentication:) : body
|
|
@@ -29,7 +29,7 @@ module HaveAPI::ClientExamples
|
|
|
29
29
|
} catch (\\HaveAPI\\Client\\Exception\\Base $error) {
|
|
30
30
|
echo $error->getMessage() . "\\n";
|
|
31
31
|
} catch (\\Exception $error) {
|
|
32
|
-
|
|
32
|
+
#{comment_lines(example_comment('dependency_failures', default: 'Dependency failures still propagate to the application.'), ' // ')}
|
|
33
33
|
echo $error->getMessage() . "\\n";
|
|
34
34
|
throw $error;
|
|
35
35
|
}
|
|
@@ -60,51 +60,50 @@ module HaveAPI::ClientExamples
|
|
|
60
60
|
<<~END
|
|
61
61
|
#{initialization.chomp}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
#{comment_lines(example_comment('token_credentials', default: 'Get token using username and password'), '// ')}
|
|
64
64
|
$api->authenticate('token', #{token_options(desc)});
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
#{comment_lines(example_comment('token_reuse_next', default: 'Next time, the client can authenticate using the token directly'), '// ')}
|
|
67
67
|
$api->authenticate('token', ['token' => 'example-token']);
|
|
68
68
|
END
|
|
69
69
|
|
|
70
70
|
when :oauth2
|
|
71
71
|
<<~END
|
|
72
|
-
|
|
72
|
+
#{comment_lines(example_comment('oauth_session', default: 'OAuth2 requires session'), '// ')}
|
|
73
73
|
session_start();
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
#{comment_lines(example_comment('client_instance', default: 'Client instance'), '// ')}
|
|
76
76
|
#{initialization}
|
|
77
|
-
|
|
77
|
+
#{comment_lines(example_comment('oauth_check_token', default: 'Check if we already have an access token'), '// ')}
|
|
78
78
|
if (isset($_SESSION['access_token'])) {
|
|
79
|
-
|
|
79
|
+
#{comment_lines(example_comment('oauth_reuse_token', default: 'We\'re already authenticated, reuse the existing access token'), ' // ')}
|
|
80
80
|
$api->authenticate('oauth2', ['access_token' => $_SESSION['access_token']]);
|
|
81
81
|
} else {
|
|
82
|
-
|
|
83
|
-
// authorization code
|
|
82
|
+
#{comment_lines(example_comment('oauth_code_flow', default: "Follow the OAuth2 authorization process to get an access token using\nauthorization code"), ' // ')}
|
|
84
83
|
$api->authenticate('oauth2', [
|
|
85
|
-
|
|
84
|
+
#{comment_lines(example_comment('oauth_client_credentials', default: 'Client id and secret are given by the API server'), ' // ')}
|
|
86
85
|
'client_id' => 'example-client',
|
|
87
86
|
'client_secret' => 'example-client-secret',
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
#{comment_lines(example_comment('oauth_callback_url', default: 'This example code should run on the URL below'), ' // ')}
|
|
90
89
|
'redirect_uri' => 'https://your-client.tld/oauth2-callback',
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
#{comment_lines(example_comment('oauth_scopes', default: 'Scopes are specific to the API implementation'), ' // ')}
|
|
93
92
|
'scope' => 'all',
|
|
94
93
|
]);
|
|
95
94
|
|
|
96
95
|
$provider = $api->getAuthenticationProvider();
|
|
97
96
|
|
|
98
|
-
|
|
97
|
+
#{comment_lines(example_comment('oauth_need_code', default: 'We don\'t have authorization code yet, request one'), ' // ')}
|
|
99
98
|
if (!isset($_GET['code'])) {
|
|
100
|
-
|
|
99
|
+
#{comment_lines(example_comment('oauth_redirect', default: 'Redirect the user to the authorization endpoint'), ' // ')}
|
|
101
100
|
$provider->requestAuthorizationCode();
|
|
102
101
|
exit;
|
|
103
102
|
} else {
|
|
104
|
-
|
|
103
|
+
#{comment_lines(example_comment('oauth_request_token', default: 'Request access token using the token endpoint'), ' // ')}
|
|
105
104
|
$provider->requestAccessToken();
|
|
106
105
|
|
|
107
|
-
|
|
106
|
+
#{comment_lines(example_comment('oauth_store_token', default: 'Store the access token in the session'), ' // ')}
|
|
108
107
|
$_SESSION['access_token'] = $provider->jsonSerialize();
|
|
109
108
|
}
|
|
110
109
|
}
|
|
@@ -151,7 +150,7 @@ module HaveAPI::ClientExamples
|
|
|
151
150
|
return "$api->authenticate('basic', ['user' => 'user', 'password' => 'secret']);" if providers.include?('basic')
|
|
152
151
|
return "$api->authenticate('oauth2', ['access_token' => ['access_token' => 'example-access-token']]);" if providers.include?('oauth2')
|
|
153
152
|
|
|
154
|
-
raise HaveAPI::ClientExample::UnsupportedAuthentication, '
|
|
153
|
+
raise HaveAPI::ClientExample::UnsupportedAuthentication, HaveAPI.t('haveapi.documentation.limitations.action_authentication')
|
|
155
154
|
end
|
|
156
155
|
|
|
157
156
|
def example(sample)
|
|
@@ -171,7 +170,7 @@ module HaveAPI::ClientExamples
|
|
|
171
170
|
out << if sample[:status]
|
|
172
171
|
response(sample)
|
|
173
172
|
else
|
|
174
|
-
'
|
|
173
|
+
comment_lines(example_comment('throws_exception', default: 'Throws exception %{exception}', exception: '\\HaveAPI\\Client\\Exception\\ActionFailed'), '// ')
|
|
175
174
|
end
|
|
176
175
|
thorough? ? with_error_handling(["#{initialization}$api->setup();", credentials, out.chomp].reject(&:empty?).join("\n\n")) : out
|
|
177
176
|
rescue HaveAPI::ClientExample::UnsupportedAuthentication => e
|
|
@@ -183,16 +182,16 @@ module HaveAPI::ClientExamples
|
|
|
183
182
|
|
|
184
183
|
case action[:output][:layout]
|
|
185
184
|
when :hash
|
|
186
|
-
out << "
|
|
187
|
-
out << "
|
|
185
|
+
out << "#{comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: '$reply', type: '\\HaveAPI\\Client\\Response'), '// ')}\n"
|
|
186
|
+
out << "#{comment_lines(example_comment('response_associative', default: '%{call} returns an associative array of output parameters:', call: '$reply->getResponse()'), '// ')}\n"
|
|
188
187
|
out << format_parameters(:output, sample[:response] || {}, '// ')
|
|
189
188
|
|
|
190
189
|
when :hash_list
|
|
191
|
-
out << "
|
|
192
|
-
out << "
|
|
190
|
+
out << "#{comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: '$reply', type: '\\HaveAPI\\Client\\Response'), '// ')}\n"
|
|
191
|
+
out << "#{comment_lines(example_comment('response_associative_arrays', default: '%{call} returns an array of associative arrays:', call: '$reply->getResponse()'), '// ')}\n"
|
|
193
192
|
|
|
194
193
|
when :object
|
|
195
|
-
out << "
|
|
194
|
+
out << "#{comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: '$reply', type: '\\HaveAPI\\Client\\ResourceInstance'), '// ')}\n"
|
|
196
195
|
|
|
197
196
|
(sample[:response] || {}).each do |pn, pv|
|
|
198
197
|
param = action[:output][:parameters][pn]
|
|
@@ -210,7 +209,7 @@ module HaveAPI::ClientExamples
|
|
|
210
209
|
out << ")\n"
|
|
211
210
|
|
|
212
211
|
elsif param[:type] == 'Custom'
|
|
213
|
-
out <<
|
|
212
|
+
out << comment_lines(example_comment('custom_type', default: '%{variable} is a custom type', variable: "$reply->#{pn}"), '// ')
|
|
214
213
|
|
|
215
214
|
else
|
|
216
215
|
out << "// $reply->#{pn} = #{PP.pp(pv, '')}"
|
|
@@ -218,7 +217,7 @@ module HaveAPI::ClientExamples
|
|
|
218
217
|
end
|
|
219
218
|
|
|
220
219
|
when :object_list
|
|
221
|
-
out << '
|
|
220
|
+
out << comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: '$reply', type: '\\HaveAPI\\Client\\ResourceInstanceList'), '// ')
|
|
222
221
|
end
|
|
223
222
|
|
|
224
223
|
out
|
|
@@ -70,20 +70,20 @@ module HaveAPI::ClientExamples
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def init
|
|
73
|
-
code_result { program(thorough? ? "print(\"Discovery complete\")\n" : "# Call API actions inside this block
|
|
73
|
+
code_result { program(thorough? ? "print(\"Discovery complete\")\n" : "#{comment_lines(example_comment('python_actions', default: 'Call API actions inside this block.'), '# ')}\npass\n") }
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def auth(method, description)
|
|
77
|
-
return limitation('
|
|
77
|
+
return limitation(HaveAPI.t('haveapi.documentation.limitations.authentication_provider')) unless supported_auth?(method)
|
|
78
78
|
|
|
79
79
|
code_result do
|
|
80
|
-
body = thorough? ? "print(\"Authenticated\")\n" : "# Call API actions inside this block
|
|
80
|
+
body = thorough? ? "print(\"Authenticated\")\n" : "#{comment_lines(example_comment('python_actions', default: 'Call API actions inside this block.'), '# ')}\npass\n"
|
|
81
81
|
case method.to_sym
|
|
82
82
|
when :basic
|
|
83
83
|
program(body, imports: ['BasicAuth'], auth: 'BasicAuth("user", "secret")')
|
|
84
84
|
when :oauth2
|
|
85
85
|
source = program(body, imports: ['OAuth2Auth'], auth: 'OAuth2Auth("example-access-token")')
|
|
86
|
-
"# Use an existing access token; OAuth acquisition and refresh are outside this client
|
|
86
|
+
"#{comment_lines(example_comment('python_existing_oauth', default: 'Use an existing access token; OAuth acquisition and refresh are outside this client.'), '# ')}\n#{source}"
|
|
87
87
|
when :token
|
|
88
88
|
if token_login_fields(description).empty?
|
|
89
89
|
program(body, imports: ['TokenAuth'], auth: 'TokenAuth("example-token")')
|
|
@@ -112,13 +112,13 @@ module HaveAPI::ClientExamples
|
|
|
112
112
|
path, request = structured_sample(sample)
|
|
113
113
|
method = field(current_action, :method).to_s.upcase
|
|
114
114
|
unless %w[GET POST PUT DELETE].include?(method)
|
|
115
|
-
raise Unsupported, '
|
|
115
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.python_method')
|
|
116
116
|
end
|
|
117
117
|
unless request.nil? || request.is_a?(Hash) || request.is_a?(Array)
|
|
118
|
-
raise Unsupported, '
|
|
118
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.python_input')
|
|
119
119
|
end
|
|
120
120
|
if request.is_a?(Array) && field(field(current_action, :input), :namespace).to_s.empty?
|
|
121
|
-
raise Unsupported, '
|
|
121
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.python_list_namespace')
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
check_get_sample(request) if method == 'GET'
|
|
@@ -134,7 +134,7 @@ module HaveAPI::ClientExamples
|
|
|
134
134
|
body << "print(response.meta)\n" if thorough?
|
|
135
135
|
if field(current_action, :blocking)
|
|
136
136
|
body << <<~PY
|
|
137
|
-
# A request timeout does not cancel a server operation.
|
|
137
|
+
#{comment_lines(example_comment('timeout_no_cancel', default: 'A request timeout does not cancel a server operation.'), '# ')}
|
|
138
138
|
if response.operation is not None:
|
|
139
139
|
completed = #{await_prefix}response.operation.wait(timeout=300, interval=1)
|
|
140
140
|
print(completed)
|
|
@@ -142,7 +142,7 @@ module HaveAPI::ClientExamples
|
|
|
142
142
|
end
|
|
143
143
|
if field(current_action, :auth)
|
|
144
144
|
method = %i[token basic oauth2].find { |name| supported_auth?(name) }
|
|
145
|
-
raise Unsupported, '
|
|
145
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.action_authentication') unless method
|
|
146
146
|
|
|
147
147
|
provider, auth = {
|
|
148
148
|
token: ['TokenAuth', 'TokenAuth("example-token")'],
|
|
@@ -161,7 +161,7 @@ module HaveAPI::ClientExamples
|
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
def action_fragment(body)
|
|
164
|
-
asynchronous? ? "# Run inside an async function after initializing api
|
|
164
|
+
asynchronous? ? "#{comment_lines(example_comment('python_async_context', default: 'Run inside an async function after initializing api.'), '# ')}\n#{body}" : body
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
def check_get_sample(request)
|
|
@@ -180,7 +180,7 @@ module HaveAPI::ClientExamples
|
|
|
180
180
|
end)
|
|
181
181
|
return if safe_namespace && safe_request
|
|
182
182
|
|
|
183
|
-
raise Unsupported, '
|
|
183
|
+
raise Unsupported, HaveAPI.t('haveapi.documentation.limitations.python_get_input')
|
|
184
184
|
end
|
|
185
185
|
end
|
|
186
186
|
end
|
|
@@ -23,30 +23,27 @@ module HaveAPI::ClientExamples
|
|
|
23
23
|
case method
|
|
24
24
|
when :basic
|
|
25
25
|
<<~END
|
|
26
|
-
# Provide credentials on command line
|
|
26
|
+
#{comment_lines(example_comment('cli_credentials', default: 'Provide credentials on command line'), '# ')}
|
|
27
27
|
#{init} --auth basic --user user --password secret
|
|
28
28
|
|
|
29
|
-
# If username or password isn't provided, the user is asked on stdin
|
|
29
|
+
#{comment_lines(example_comment('cli_ask_stdin', default: 'If username or password isn\'t provided, the user is asked on stdin'), '# ')}
|
|
30
30
|
#{init} --auth basic --user user
|
|
31
31
|
# Password: secret
|
|
32
32
|
END
|
|
33
33
|
|
|
34
34
|
when :token
|
|
35
35
|
<<~END
|
|
36
|
-
# Get token using username and password and save it to disk
|
|
37
|
-
# Note that the client always has to call some action. APIs should provide
|
|
38
|
-
# an action to get information about the current user, so that's what we're
|
|
39
|
-
# calling now.
|
|
36
|
+
#{comment_lines(example_comment('cli_save_token', default: 'Get token using username and password and save it to disk'), '# ')}
|
|
37
|
+
#{comment_lines(example_comment('cli_action_required', default: "Note that the client always has to call some action. APIs should provide\nan action to get information about the current user, so that's what we're\ncalling now."), '# ')}
|
|
40
38
|
#{init} --auth token #{auth_token_credentials(desc, password: false).map { |k, v| "#{Shellwords.escape("--#{k}")} #{Shellwords.escape(v.to_s)}" }.join(' ')} --save user current
|
|
41
39
|
# Password: secret
|
|
42
40
|
|
|
43
|
-
# Now the token is read from disk and the user does not have to provide username
|
|
44
|
-
# nor password and be authenticated
|
|
41
|
+
#{comment_lines(example_comment('cli_read_token', default: "Now the token is read from disk and the user does not have to provide username\nnor password and be authenticated"), '# ')}
|
|
45
42
|
#{init} user current
|
|
46
43
|
END
|
|
47
44
|
|
|
48
45
|
when :oauth2
|
|
49
|
-
'
|
|
46
|
+
comment_lines(example_comment('cli_oauth_unsupported', default: 'OAuth2 is not supported by HaveAPI Ruby CLI.'), '# ')
|
|
50
47
|
end
|
|
51
48
|
rescue HaveAPI::ClientExample::UnsupportedAuthentication => e
|
|
52
49
|
{ status: :limitation, reason: e.message }.freeze
|
|
@@ -14,11 +14,19 @@ module HaveAPI::ClientExamples
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def initialization
|
|
17
|
-
"
|
|
17
|
+
"#{client_library}#{client_initialization}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def client_library
|
|
21
|
+
"require 'haveapi/client'\n\n"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def client_initialization
|
|
25
|
+
"api = HaveAPI::Client.new(#{base_url.dump}, version: #{version.to_s.dump})\n"
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
def with_error_handling(body)
|
|
21
|
-
<<~RUBY
|
|
29
|
+
client_library + <<~RUBY
|
|
22
30
|
begin
|
|
23
31
|
#{body.lines.map { |line| line == "\n" ? line : " #{line}" }.join.chomp}
|
|
24
32
|
rescue HaveAPI::Client::ValidationError => error
|
|
@@ -35,7 +43,7 @@ module HaveAPI::ClientExamples
|
|
|
35
43
|
end
|
|
36
44
|
|
|
37
45
|
def init
|
|
38
|
-
thorough? ? with_error_handling("#{
|
|
46
|
+
thorough? ? with_error_handling("#{client_initialization}api.setup\n") : initialization
|
|
39
47
|
end
|
|
40
48
|
|
|
41
49
|
def auth_login_literal(name, parameter, value)
|
|
@@ -65,13 +73,13 @@ module HaveAPI::ClientExamples
|
|
|
65
73
|
end
|
|
66
74
|
RUBY
|
|
67
75
|
end
|
|
68
|
-
%(#{code.chomp}\n\n# Reuse an existing token\napi.authenticate(:token, token: 'example-token'))
|
|
76
|
+
%(#{code.chomp}\n\n#{comment_lines(example_comment('reuse_token', default: 'Reuse an existing token'), '# ')}\napi.authenticate(:token, token: 'example-token'))
|
|
69
77
|
when :oauth2
|
|
70
|
-
return '
|
|
78
|
+
return comment_lines(example_comment('ruby_oauth_unsupported', default: 'OAuth2 is not supported by HaveAPI Ruby client.'), '# ')
|
|
71
79
|
end
|
|
72
80
|
return unless body
|
|
73
81
|
|
|
74
|
-
source = [initialization.chomp, (thorough? ? 'api.setup' : nil), body].compact.join("\n\n")
|
|
82
|
+
source = [(thorough? ? client_initialization : initialization).chomp, (thorough? ? 'api.setup' : nil), body].compact.join("\n\n")
|
|
75
83
|
thorough? ? with_error_handling(source) : source
|
|
76
84
|
rescue HaveAPI::ClientExample::UnsupportedAuthentication => e
|
|
77
85
|
{ status: :limitation, reason: e.message }.freeze
|
|
@@ -84,7 +92,7 @@ module HaveAPI::ClientExamples
|
|
|
84
92
|
return "api.authenticate(:token, token: 'example-token')\n" if providers.include?('token')
|
|
85
93
|
return "api.authenticate(:basic, user: 'user', password: 'secret')\n" if providers.include?('basic')
|
|
86
94
|
|
|
87
|
-
raise HaveAPI::ClientExample::UnsupportedAuthentication, '
|
|
95
|
+
raise HaveAPI::ClientExample::UnsupportedAuthentication, HaveAPI.t('haveapi.documentation.limitations.action_authentication')
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
def example(sample)
|
|
@@ -104,9 +112,9 @@ module HaveAPI::ClientExamples
|
|
|
104
112
|
out << if sample[:status]
|
|
105
113
|
response(sample)
|
|
106
114
|
else
|
|
107
|
-
"\n# Raises exception HaveAPI::Client::ActionFailed\n"
|
|
115
|
+
"\n#{comment_lines(example_comment('raises_exception', default: 'Raises exception %{exception}', exception: 'HaveAPI::Client::ActionFailed'), '# ')}\n"
|
|
108
116
|
end
|
|
109
|
-
thorough? ? with_error_handling(["#{
|
|
117
|
+
thorough? ? with_error_handling(["#{client_initialization}api.setup", credentials.chomp, out].reject(&:empty?).join("\n\n")) : out
|
|
110
118
|
rescue HaveAPI::ClientExample::UnsupportedAuthentication => e
|
|
111
119
|
{ status: :limitation, reason: e.message }.freeze
|
|
112
120
|
end
|
|
@@ -116,17 +124,17 @@ module HaveAPI::ClientExamples
|
|
|
116
124
|
|
|
117
125
|
case action[:output][:layout]
|
|
118
126
|
when :hash
|
|
119
|
-
out << "#
|
|
120
|
-
out << "#
|
|
127
|
+
out << "#{comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: 'reply', type: 'HaveAPI::Client::Response'), '# ')}\n"
|
|
128
|
+
out << "#{comment_lines(example_comment('response_hash', default: '%{call} returns a hash of output parameters:', call: 'reply.response()'), '# ')}\n"
|
|
121
129
|
out << PP.pp(sample[:response] || {}, '').split("\n").map { |v| "# #{v}" }.join("\n")
|
|
122
130
|
|
|
123
131
|
when :hash_list
|
|
124
|
-
out << "#
|
|
125
|
-
out << "#
|
|
132
|
+
out << "#{comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: 'reply', type: 'HaveAPI::Client::Response'), '# ')}\n"
|
|
133
|
+
out << "#{comment_lines(example_comment('response_hashes', default: '%{call} returns an array of hashes:', call: 'reply.response()'), '# ')}\n"
|
|
126
134
|
out << PP.pp(sample[:response] || [], '').split("\n").map { |v| "# #{v}" }.join("\n")
|
|
127
135
|
|
|
128
136
|
when :object
|
|
129
|
-
out << "#
|
|
137
|
+
out << "#{comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: 'reply', type: 'HaveAPI::Client::ResourceInstance'), '# ')}\n"
|
|
130
138
|
|
|
131
139
|
(sample[:response] || {}).each do |pn, pv|
|
|
132
140
|
param = action[:output][:parameters][pn]
|
|
@@ -149,8 +157,8 @@ module HaveAPI::ClientExamples
|
|
|
149
157
|
end
|
|
150
158
|
|
|
151
159
|
when :object_list
|
|
152
|
-
out << "#
|
|
153
|
-
out << '
|
|
160
|
+
out << "#{comment_lines(example_comment('instance_of', default: '%{variable} is an instance of %{type}', variable: 'reply', type: 'HaveAPI::Client::ResourceInstanceList'), '# ')},\n"
|
|
161
|
+
out << comment_lines(example_comment('array_subclass', default: 'which is a subclass of Array'), '# ')
|
|
154
162
|
end
|
|
155
163
|
|
|
156
164
|
out
|
|
@@ -33,7 +33,7 @@ module HaveAPI::ClientExamples
|
|
|
33
33
|
|
|
34
34
|
def render_rust(kind, rendering_context: context, **)
|
|
35
35
|
unless self.class.available?(rendering_context)
|
|
36
|
-
return { status: :limitation, reason: '
|
|
36
|
+
return { status: :limitation, reason: HaveAPI.t('haveapi.documentation.limitations.rust_profile') }.freeze
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
rendering_context.profile.adapter.render(kind:, context: rendering_context, mode: rust_mode, **)
|
|
@@ -35,7 +35,7 @@ module HaveAPI::ClientExamples
|
|
|
35
35
|
|
|
36
36
|
def render_documentation(kind, rendering_context: context, **)
|
|
37
37
|
unless self.class.available?(rendering_context)
|
|
38
|
-
return { status: :limitation, reason: '
|
|
38
|
+
return { status: :limitation, reason: HaveAPI.t('haveapi.documentation.limitations.typescript_profile') }.freeze
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
rendering_context.profile.adapter.render(kind:, context: rendering_context, mode: :async, **)
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
require 'haveapi/client_example'
|
|
2
|
+
require 'haveapi/i18n'
|
|
2
3
|
|
|
3
4
|
module HaveAPI
|
|
4
5
|
class ClientExample
|
|
5
6
|
# A request-local snapshot: never freeze or retain the server's shared help.
|
|
6
7
|
class Context
|
|
7
|
-
FIELDS = %i[version host base_url version_url description resource_path resource action_name action sample profile detail].freeze
|
|
8
|
+
FIELDS = %i[version host base_url version_url description resource_path resource action_name action sample profile detail locale].freeze
|
|
8
9
|
attr_reader(*FIELDS)
|
|
9
10
|
|
|
10
11
|
def initialize(version:, host:, base_url:, version_url:, description:,
|
|
11
12
|
resource_path: [], resource: nil, action_name: nil, action: nil,
|
|
12
|
-
sample: nil, profile: nil, detail: :simple)
|
|
13
|
+
sample: nil, profile: nil, detail: :simple, locale: :en)
|
|
13
14
|
@version = normalize(:version, version)
|
|
14
15
|
@host = normalize(:host, host)
|
|
15
16
|
@base_url = normalize(:base_url, base_url)
|
|
@@ -22,6 +23,7 @@ module HaveAPI
|
|
|
22
23
|
@sample = normalize(:sample, sample)
|
|
23
24
|
@profile = normalize(:profile, profile)
|
|
24
25
|
@detail = normalize(:detail, detail)
|
|
26
|
+
@locale = normalize(:locale, locale)
|
|
25
27
|
freeze
|
|
26
28
|
end
|
|
27
29
|
|
|
@@ -44,6 +46,14 @@ module HaveAPI
|
|
|
44
46
|
derived.freeze
|
|
45
47
|
end
|
|
46
48
|
|
|
49
|
+
def documentation_message(key, default:, **values)
|
|
50
|
+
::I18n.t("haveapi.documentation.#{key}", locale:, default:, **values)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def example_comment(key, default:, **values)
|
|
54
|
+
documentation_message("comments.#{key}", default:, **values)
|
|
55
|
+
end
|
|
56
|
+
|
|
47
57
|
private
|
|
48
58
|
|
|
49
59
|
def normalize(field, value)
|
|
@@ -54,6 +64,8 @@ module HaveAPI
|
|
|
54
64
|
copy(value.map(&:to_s))
|
|
55
65
|
when :action_name
|
|
56
66
|
value.nil? ? nil : value.to_s.dup.freeze
|
|
67
|
+
when :locale
|
|
68
|
+
value.to_sym
|
|
57
69
|
when :profile
|
|
58
70
|
value
|
|
59
71
|
when :detail
|