haveapi-rust-client 0.31.2 → 0.32.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/Gemfile.lock +9 -9
- data/docs/development.md +23 -6
- data/docs/documentation.md +17 -8
- data/lib/haveapi/rust_client/documentation.rb +72 -41
- data/lib/haveapi/rust_client/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 680519d92eeac2f6ea9c1f6eb7c73fc03f197068a329c933db47cb9db42d6fc4
|
|
4
|
+
data.tar.gz: 05ac7f3e221583754c716d048f61ac585e89e47a0a1bd9b10ac9e8a533be6a95
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b0a52816bfa919ebfe50d35aad9c4405ddeaa145a93c326ba6f92daf7d31efb6c81c27550576e1b917463a9d5c33264cb0d7d6c33f1a6df1b1c9e117e74d7bd
|
|
7
|
+
data.tar.gz: a3f6d9e91b0c247e9930d614b95c53af5ef219aadd35cc09f2f2fe9f2b4a46f769883498a334f08ad5391d01e93c6d68eebe023128fd8212d8d35a4c614befb5
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../ruby
|
|
3
3
|
specs:
|
|
4
|
-
haveapi-client (0.
|
|
4
|
+
haveapi-client (0.32.0)
|
|
5
5
|
activesupport (>= 7.0)
|
|
6
6
|
highline (~> 3.1)
|
|
7
7
|
json
|
|
@@ -12,9 +12,9 @@ PATH
|
|
|
12
12
|
PATH
|
|
13
13
|
remote: .
|
|
14
14
|
specs:
|
|
15
|
-
haveapi-rust-client (0.
|
|
15
|
+
haveapi-rust-client (0.32.0)
|
|
16
16
|
fiddle (~> 1.1, >= 1.1.8)
|
|
17
|
-
haveapi-client (~> 0.
|
|
17
|
+
haveapi-client (~> 0.32.0)
|
|
18
18
|
tomlrb (~> 2.0)
|
|
19
19
|
|
|
20
20
|
GEM
|
|
@@ -50,7 +50,7 @@ GEM
|
|
|
50
50
|
domain_name (~> 0.5)
|
|
51
51
|
i18n (1.15.2)
|
|
52
52
|
concurrent-ruby (~> 1.0)
|
|
53
|
-
io-console (0.9.
|
|
53
|
+
io-console (0.9.5)
|
|
54
54
|
json (2.21.2)
|
|
55
55
|
json_schemer (2.5.0)
|
|
56
56
|
bigdecimal
|
|
@@ -65,7 +65,7 @@ GEM
|
|
|
65
65
|
mime-types (3.7.0)
|
|
66
66
|
logger
|
|
67
67
|
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
|
68
|
-
mime-types-data (3.2026.
|
|
68
|
+
mime-types-data (3.2026.1027)
|
|
69
69
|
minitest (6.0.6)
|
|
70
70
|
drb (~> 2.0)
|
|
71
71
|
prism (~> 1.5)
|
|
@@ -156,13 +156,13 @@ CHECKSUMS
|
|
|
156
156
|
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
157
157
|
fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee
|
|
158
158
|
hana (1.3.7) sha256=5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d
|
|
159
|
-
haveapi-client (0.
|
|
160
|
-
haveapi-rust-client (0.
|
|
159
|
+
haveapi-client (0.32.0)
|
|
160
|
+
haveapi-rust-client (0.32.0)
|
|
161
161
|
highline (3.1.2) sha256=67cbd34d19f6ef11a7ee1d82ffab5d36dfd5b3be861f450fc1716c7125f4bb4a
|
|
162
162
|
http-accept (1.7.0) sha256=c626860682bfbb3b46462f8c39cd470fd7b0584f61b3cc9df5b2e9eb9972a126
|
|
163
163
|
http-cookie (1.1.6) sha256=ba4b82be64de61dc281243dac70e3c382c45142f20268ed9276a3670c93feaa9
|
|
164
164
|
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
|
|
165
|
-
io-console (0.9.
|
|
165
|
+
io-console (0.9.5) sha256=83dd9bbf4c4680416629245b39cd4212eb5f4d9a4ea91804870c1f6b846414d5
|
|
166
166
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
167
167
|
json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
|
|
168
168
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
@@ -170,7 +170,7 @@ CHECKSUMS
|
|
|
170
170
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
171
171
|
mcp (0.25.0) sha256=8acf3a5f3b60b6fd6e5d9b8ac5eb85ac504766976cbadaca182d2ffe7d795d98
|
|
172
172
|
mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56
|
|
173
|
-
mime-types-data (3.2026.
|
|
173
|
+
mime-types-data (3.2026.1027) sha256=4568729e44974c1c8a8ceb76f7a5fc164e8c9553335455688bb3c00020f5d83f
|
|
174
174
|
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
|
175
175
|
netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f
|
|
176
176
|
parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970
|
data/docs/development.md
CHANGED
|
@@ -6,7 +6,14 @@ From the HaveAPI root:
|
|
|
6
6
|
nix develop .#client-rust
|
|
7
7
|
cd clients/rust
|
|
8
8
|
bundle exec rspec spec/unit
|
|
9
|
-
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Run Ruby style checks from the HaveAPI root in the default Nix shell, using the
|
|
12
|
+
root bundle:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
BUNDLE_GEMFILE="$PWD/Gemfile" bundle exec rubocop clients/rust/lib clients/rust/bin \
|
|
16
|
+
clients/rust/spec clients/rust/haveapi-rust-client.gemspec
|
|
10
17
|
```
|
|
11
18
|
|
|
12
19
|
The component shell runs `bundle install` for this library's dependency ranges
|
|
@@ -22,11 +29,21 @@ The root Git hooks also need the root shell's dependencies: prepare them with
|
|
|
22
29
|
Overcommit hooks enabled. Do not install gems concurrently in the same gem home.
|
|
23
30
|
|
|
24
31
|
From the HaveAPI root, `make test-rust-unit` runs the generator specs.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
The specs check generated Rust with the recorded formatter, including failures
|
|
33
|
+
after a successful version probe, without starting Cargo or a server. They also
|
|
34
|
+
verify that every copied runtime file retains its maintained source bytes.
|
|
35
|
+
`make check-rust` runs the runtime's read-only `cargo fmt` check.
|
|
36
|
+
`make test-rust` additionally checks formatting and runs both runtime feature
|
|
37
|
+
configurations, the prepared server fixture contracts and the generated-client
|
|
38
|
+
protocol harness.
|
|
39
|
+
Quick unit specs need only the Rust component bundle; they never load the server
|
|
40
|
+
application, start a listener or invoke Cargo. Compilation, Rust linting, server
|
|
41
|
+
integration, MSRV and packaging checks can take longer; follow the development
|
|
42
|
+
workspace's verification procedure when working there.
|
|
43
|
+
|
|
44
|
+
The generated-client harness also runs `cargo fmt --check` over its complete
|
|
45
|
+
temporary crate, including the copied acceptance tests. This check precedes its
|
|
46
|
+
compilation and protocol tests. Clippy remains a separate runtime lint check.
|
|
30
47
|
|
|
31
48
|
The Nix shell supplies the development compiler and formatter. New test
|
|
32
49
|
fixtures record that formatter; existing clients retain their own exact
|
data/docs/documentation.md
CHANGED
|
@@ -110,8 +110,15 @@ Their internal structural URL is not an SDK provenance source or emitted request
|
|
|
110
110
|
URL. Requests use the real server context. Actual SDK generation still requires
|
|
111
111
|
its full versioned packaging configuration; these factories do not relax it.
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
Simple action examples assume that initialization and authentication have
|
|
114
|
+
established `api`. They show the actual call and sample output. Async fragments
|
|
115
|
+
belong in an async function returning `Result<(), Box<dyn std::error::Error>>`;
|
|
116
|
+
blocking fragments use a synchronous function with that return type, outside
|
|
117
|
+
async runtime threads. The `?` operator propagates request and conversion errors.
|
|
118
|
+
Both detail levels share sample validation and limitations.
|
|
119
|
+
|
|
120
|
+
**With error handling** shows a complete program and Cargo dependency comments.
|
|
121
|
+
Async programs use Tokio and `.send().await?`. Blocking programs enable the SDK's
|
|
115
122
|
`blocking` feature and construct, use and drop the client in a synchronous
|
|
116
123
|
`main`, outside async runtime threads. `HAVEAPI_BASE_URL` can override the shown
|
|
117
124
|
mounted root. Authenticated actions use advertised existing credentials from
|
|
@@ -149,13 +156,15 @@ authentication rendering separately. These lanes do not generate or compile an
|
|
|
149
156
|
SDK. Compare the same fixture, runtime, dependency inputs and output digests
|
|
150
157
|
against both implementation revisions before claiming a measured improvement.
|
|
151
158
|
|
|
152
|
-
The
|
|
153
|
-
`RustBlockingClient.render` source.
|
|
159
|
+
The context must expose `detail` as `:simple` or `:thorough`. The acceptance runner
|
|
160
|
+
extracts actual `RustAsyncClient.render` and `RustBlockingClient.render` source.
|
|
161
|
+
It generates a matching temporary fixture
|
|
154
162
|
SDK, compiles the emitted imports, paths, methods and types, and executes the
|
|
155
|
-
programs against the existing disposable HaveAPI fixture
|
|
156
|
-
`/mounted/api/`. It adds output assertions without replacing emitted
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
programs for both detail levels against the existing disposable HaveAPI fixture
|
|
164
|
+
at `/` and `/mounted/api/`. It adds output assertions without replacing emitted
|
|
165
|
+
requests. Simple fragments use separately rendered setup. Authentication checks
|
|
166
|
+
compose the emitted authentication builder with the emitted action body.
|
|
167
|
+
It tests both modes with the development compiler and
|
|
159
168
|
Rust 1.95, including zero/false/empty/null/omitted inputs, nested Custom, lists,
|
|
160
169
|
nested resources, failure branches, metadata, existing credentials and an
|
|
161
170
|
action-state reference. The fixture uses local test credentials and in-memory
|
|
@@ -2,10 +2,13 @@ require 'haveapi/rust_client'
|
|
|
2
2
|
require 'date'
|
|
3
3
|
|
|
4
4
|
module HaveAPI::RustClient
|
|
5
|
-
# Resolves the complete
|
|
5
|
+
# Resolves the complete API client model before checking a request's authorized help.
|
|
6
6
|
class Documentation
|
|
7
7
|
include Model
|
|
8
8
|
|
|
9
|
+
SIMPLE_DETAIL_LEVELS = [:simple].freeze
|
|
10
|
+
DETAIL_LEVELS = %i[simple thorough].freeze
|
|
11
|
+
|
|
9
12
|
class Unsupported < StandardError; end
|
|
10
13
|
|
|
11
14
|
def self.from_description(description:, api_version:, protocol_version:, mount_path:,
|
|
@@ -24,29 +27,29 @@ module HaveAPI::RustClient
|
|
|
24
27
|
|
|
25
28
|
def self.validate_source(source)
|
|
26
29
|
return nil if source.nil?
|
|
27
|
-
raise Error, 'invalid Rust
|
|
30
|
+
raise Error, 'invalid Rust API client source' unless source.is_a?(Hash) && source.length == 2
|
|
28
31
|
|
|
29
32
|
values = source.to_h { |key, value| [key.to_s, value] }
|
|
30
33
|
url, revision = values.values_at('git', 'rev')
|
|
31
34
|
unless values.keys.sort == %w[git rev] && url.is_a?(String) && !url.match?(/[\x00-\x20\x7f]/) &&
|
|
32
35
|
revision.is_a?(String) && revision.match?(/\A[0-9a-fA-F]{40}\z/)
|
|
33
|
-
raise Error, 'invalid Rust
|
|
36
|
+
raise Error, 'invalid Rust API client source'
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
uri = URI.parse(url)
|
|
37
40
|
unless uri.is_a?(URI::HTTPS) && uri.host && !uri.host.empty? &&
|
|
38
41
|
uri.userinfo.nil? && uri.query.nil? && uri.fragment.nil?
|
|
39
|
-
raise Error, 'invalid Rust
|
|
42
|
+
raise Error, 'invalid Rust API client source'
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
values.transform_values { |value| value.dup.freeze }.freeze
|
|
43
46
|
rescue URI::InvalidURIError
|
|
44
|
-
raise Error, 'invalid Rust
|
|
47
|
+
raise Error, 'invalid Rust API client source'
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def initialize(snapshot:, config: nil, crate_name: nil, crate_version: nil, source: nil)
|
|
48
51
|
if config
|
|
49
|
-
raise Error, 'conflicting Rust
|
|
52
|
+
raise Error, 'conflicting Rust API client package settings' unless crate_name.nil? && crate_version.nil?
|
|
50
53
|
|
|
51
54
|
config = JSON.parse(Snapshot.canonical(config))
|
|
52
55
|
Generator.validate_config(config)
|
|
@@ -74,7 +77,7 @@ module HaveAPI::RustClient
|
|
|
74
77
|
|
|
75
78
|
def render(kind:, context:, mode:, auth_method: nil, auth_description: nil)
|
|
76
79
|
unsupported!('Unsupported Rust execution mode.') unless %i[async blocking].include?(mode)
|
|
77
|
-
unsupported!('The Rust
|
|
80
|
+
unsupported!('The Rust API client targets a different API version.') unless context.version.to_s == @snapshot['api_version']
|
|
78
81
|
|
|
79
82
|
source = case kind
|
|
80
83
|
when :init then program(context, mode, [])
|
|
@@ -87,8 +90,16 @@ module HaveAPI::RustClient
|
|
|
87
90
|
{ status: :limitation, reason: e.message.freeze }.freeze
|
|
88
91
|
end
|
|
89
92
|
|
|
93
|
+
def detail_levels_for(kind:, context:, result:, mode:, auth_method: nil, auth_description: nil)
|
|
94
|
+
result[:status] == :code && %i[init auth example].include?(kind) && %i[async blocking].include?(mode) ? DETAIL_LEVELS : SIMPLE_DETAIL_LEVELS
|
|
95
|
+
end
|
|
96
|
+
|
|
90
97
|
private
|
|
91
98
|
|
|
99
|
+
def failed_sample?(sample)
|
|
100
|
+
sample['status'] == false || (sample['http_status'].is_a?(Integer) && sample['http_status'] >= 400)
|
|
101
|
+
end
|
|
102
|
+
|
|
92
103
|
def unsupported!(reason)
|
|
93
104
|
raise Unsupported, reason
|
|
94
105
|
end
|
|
@@ -130,14 +141,21 @@ module HaveAPI::RustClient
|
|
|
130
141
|
lines = dependencies + ["use #{client};", '']
|
|
131
142
|
lines << '#[tokio::main]' if mode == :async
|
|
132
143
|
lines << "#{'async ' if mode == :async}fn main() -> Result<(), Box<dyn std::error::Error>> {"
|
|
133
|
-
|
|
134
|
-
lines
|
|
135
|
-
lines.concat(credentials.map { |line| " #{line}" })
|
|
136
|
-
lines << ' .build()?;'
|
|
144
|
+
construction = "#{["Client::builder(#{literal(context.base_url)})", *credentials].join("\n ")}\n .build()"
|
|
145
|
+
lines.concat(result_lines(context, 'api', construction, 'Client configuration failed').flat_map { |line| line.lines(chomp: true).map { |part| " #{part}" } })
|
|
137
146
|
lines.concat((body.empty? ? ['let _ = &api;'] : body).map { |line| line.empty? ? '' : " #{line}" })
|
|
138
147
|
lines.push(' Ok(())', '}', '').join("\n")
|
|
139
148
|
end
|
|
140
149
|
|
|
150
|
+
def result_lines(context, name, expression, label)
|
|
151
|
+
return ["let #{name} = #{expression}?;"] unless context.detail == :thorough
|
|
152
|
+
|
|
153
|
+
["let #{name} = match #{expression} {", " Ok(#{name}) => #{name},", ' Err(error) => {',
|
|
154
|
+
" eprintln!(\"#{label}: {}\", error);",
|
|
155
|
+
' eprintln!("HTTP status: {:?}; timeout: {}", error.http_status(), error.is_timeout());',
|
|
156
|
+
' return Err(error.into());', ' }', '};']
|
|
157
|
+
end
|
|
158
|
+
|
|
141
159
|
def dependency_lines(mode)
|
|
142
160
|
unless @source || @crate_version
|
|
143
161
|
return ["// cargo add #{@crate_name}#{' --features blocking' if mode == :blocking}"]
|
|
@@ -155,23 +173,23 @@ module HaveAPI::RustClient
|
|
|
155
173
|
live = normalized_members(providers)[provider]
|
|
156
174
|
saved = @snapshot.fetch('description').fetch('authentication')[provider]
|
|
157
175
|
unless live.is_a?(Hash) && saved
|
|
158
|
-
unsupported!('This authentication provider is unavailable in the authorized Rust
|
|
176
|
+
unsupported!('This authentication provider is unavailable in the authorized Rust API client profile.')
|
|
159
177
|
end
|
|
160
178
|
live = normalize(live)
|
|
161
179
|
unsupported!('The authentication description differs from the authorized help.') unless normalize(description) == live
|
|
162
180
|
|
|
163
181
|
credentials = case provider
|
|
164
182
|
when 'basic'
|
|
165
|
-
['.basic_auth(
|
|
183
|
+
['.basic_auth("user", "secret")']
|
|
166
184
|
when 'token', 'oauth2'
|
|
167
185
|
unless saved['http_header'] == live['http_header']
|
|
168
|
-
unsupported!('The Rust
|
|
186
|
+
unsupported!('The Rust API client authentication header differs from this API.')
|
|
169
187
|
end
|
|
170
188
|
setter = provider == 'token' ? 'token' : 'oauth2_token'
|
|
171
|
-
[".#{setter}(
|
|
189
|
+
[".#{setter}(#{literal(provider == 'token' ? 'example-token' : 'example-access-token')})"]
|
|
172
190
|
else unsupported!('This authentication provider is unsupported by the Rust client.')
|
|
173
191
|
end
|
|
174
|
-
body = ['//
|
|
192
|
+
body = ['// Configure existing credentials only; building does not validate them with the server.',
|
|
175
193
|
'let _ = &api;']
|
|
176
194
|
program(context, mode, body, credentials:)
|
|
177
195
|
end
|
|
@@ -179,7 +197,7 @@ module HaveAPI::RustClient
|
|
|
179
197
|
def example(context, mode)
|
|
180
198
|
saved = resolve(resource_path: context.resource_path, action_name: context.action_name)
|
|
181
199
|
live = authorized_action(context)
|
|
182
|
-
unsupported!('This action is unavailable in the authorized Rust
|
|
200
|
+
unsupported!('This action is unavailable in the authorized Rust API client profile.') unless saved && live
|
|
183
201
|
sample = normalize(context.sample)
|
|
184
202
|
unless sample.is_a?(Hash) && (%w[path_params request response errors].any? { |key| !sample[key].nil? } ||
|
|
185
203
|
sample['status'] == false || (sample['http_status'] && sample['http_status'] != 200))
|
|
@@ -188,7 +206,7 @@ module HaveAPI::RustClient
|
|
|
188
206
|
compatible_action!(saved, live, context)
|
|
189
207
|
paths = sample['path_params'] || []
|
|
190
208
|
unless paths.is_a?(Array) && paths.length == saved[:placeholders].length && paths.all? { |value| valid_path?(value) }
|
|
191
|
-
unsupported!('The sample path arguments cannot be represented by this Rust
|
|
209
|
+
unsupported!('The sample path arguments cannot be represented by this Rust API client.')
|
|
192
210
|
end
|
|
193
211
|
inputs = input_lines(saved, live, sample['request'])
|
|
194
212
|
validate_response!(saved, live, sample['response']) unless sample['response'].nil?
|
|
@@ -198,18 +216,31 @@ module HaveAPI::RustClient
|
|
|
198
216
|
call << inputs.fetch(:chain)
|
|
199
217
|
send = mode == :async ? '.send().await' : '.send()'
|
|
200
218
|
body = inputs.fetch(:before)
|
|
201
|
-
if
|
|
219
|
+
if failed_sample?(sample)
|
|
202
220
|
body += ["match #{call}#{send} {", ' Ok(_) => return Err("expected the documented request to fail".into()),',
|
|
203
|
-
' Err(error) => {', '
|
|
204
|
-
|
|
205
|
-
|
|
221
|
+
' Err(error) => {', ' eprintln!("HTTP status: {:?}", error.http_status());']
|
|
222
|
+
if context.detail == :thorough
|
|
223
|
+
body += [' eprintln!("Request failed: {}; timeout: {}", error, error.is_timeout());',
|
|
224
|
+
' // Inspect these explicitly; server diagnostics can contain sensitive values.',
|
|
225
|
+
' let _ = (error.server_message(), error.validation_errors());']
|
|
226
|
+
end
|
|
227
|
+
body += [' }', '}']
|
|
206
228
|
else
|
|
207
|
-
body
|
|
229
|
+
body.concat(result_lines(context, 'response', "#{call}#{send}", 'Request failed'))
|
|
230
|
+
body += ['let _ = response.http_status;', 'let _ = &response.meta;'] if context.detail == :thorough
|
|
208
231
|
body.concat(output_lines(saved, sample['response']))
|
|
209
232
|
end
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
233
|
+
if saved[:desc]['blocking'] == true
|
|
234
|
+
body << '// Request success or a timeout does not establish completion of a server-side operation.'
|
|
235
|
+
if context.detail == :thorough
|
|
236
|
+
body << '// Use only the separately documented action-state action; this API client has no automatic wait helper.'
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
credentials = action_credentials(saved, context)
|
|
240
|
+
return program(context, mode, body, credentials:) if context.detail == :thorough
|
|
241
|
+
|
|
242
|
+
enclosing = mode == :async ? 'an async function' : 'a synchronous function outside async runtime threads'
|
|
243
|
+
(["// Use inside #{enclosing} returning Result<(), Box<dyn std::error::Error>>."] + body + ['']).join("\n")
|
|
213
244
|
end
|
|
214
245
|
|
|
215
246
|
def action_credentials(action, context)
|
|
@@ -218,7 +249,7 @@ module HaveAPI::RustClient
|
|
|
218
249
|
live = normalized_members(normalized_members(context.description)['authentication'])
|
|
219
250
|
saved = @snapshot.fetch('description').fetch('authentication')
|
|
220
251
|
if live.has_key?('basic') && saved.has_key?('basic')
|
|
221
|
-
return ['.basic_auth(
|
|
252
|
+
return ['.basic_auth("user", "secret")']
|
|
222
253
|
end
|
|
223
254
|
|
|
224
255
|
%w[token oauth2].each do |provider|
|
|
@@ -227,9 +258,9 @@ module HaveAPI::RustClient
|
|
|
227
258
|
next unless normalize(normalized_members(advertised)['http_header']) == saved[provider]['http_header']
|
|
228
259
|
|
|
229
260
|
setter = provider == 'token' ? 'token' : 'oauth2_token'
|
|
230
|
-
return [".#{setter}(
|
|
261
|
+
return [".#{setter}(#{literal(provider == 'token' ? 'example-token' : 'example-access-token')})"]
|
|
231
262
|
end
|
|
232
|
-
unsupported!('This action requires authentication unavailable in the authorized Rust
|
|
263
|
+
unsupported!('This action requires authentication unavailable in the authorized Rust API client profile.')
|
|
233
264
|
end
|
|
234
265
|
|
|
235
266
|
def authorized_action(context)
|
|
@@ -251,7 +282,7 @@ module HaveAPI::RustClient
|
|
|
251
282
|
mount += '/' unless mount.end_with?('/')
|
|
252
283
|
unless live['method'] == saved[:desc]['method'] && live['path'] == mount + saved[:route] &&
|
|
253
284
|
(live['auth'] == false) == (saved[:desc]['auth'] == false)
|
|
254
|
-
unsupported!('The Rust
|
|
285
|
+
unsupported!('The Rust API client route or HTTP method differs from this API action.')
|
|
255
286
|
end
|
|
256
287
|
%w[input output].each do |direction|
|
|
257
288
|
saved_io = saved[direction.to_sym]
|
|
@@ -260,13 +291,13 @@ module HaveAPI::RustClient
|
|
|
260
291
|
|
|
261
292
|
unless saved_io && live_io && saved_io[:desc]['layout'] == live_io['layout'] &&
|
|
262
293
|
saved_io[:desc]['namespace'] == live_io['namespace'] && saved_io[:desc]['format'] == live_io['format']
|
|
263
|
-
unsupported!('The Rust
|
|
294
|
+
unsupported!('The Rust API client input or output layout differs from this API action.')
|
|
264
295
|
end
|
|
265
296
|
end
|
|
266
297
|
%i[global_input object_input].each do |key|
|
|
267
298
|
next unless saved[key] && saved[key][:parameters].any? { |parameter| parameter[:desc]['required'] == true }
|
|
268
299
|
|
|
269
|
-
unsupported!('This Rust
|
|
300
|
+
unsupported!('This Rust API client action requires metadata that is absent from the sample.')
|
|
270
301
|
end
|
|
271
302
|
%w[global object].each do |role|
|
|
272
303
|
if live.dig('meta', role, 'input', 'parameters')&.any? { |_name, parameter| parameter['required'] == true }
|
|
@@ -282,7 +313,7 @@ module HaveAPI::RustClient
|
|
|
282
313
|
def input_lines(saved, live, request)
|
|
283
314
|
io = saved[:input]
|
|
284
315
|
if io.nil?
|
|
285
|
-
unsupported!('This Rust
|
|
316
|
+
unsupported!('This Rust API client action cannot represent the sample input.') unless request.nil? || request == {}
|
|
286
317
|
return { before: [], chain: '' }
|
|
287
318
|
end
|
|
288
319
|
list = io[:layout].end_with?('_list')
|
|
@@ -293,7 +324,7 @@ module HaveAPI::RustClient
|
|
|
293
324
|
request
|
|
294
325
|
end
|
|
295
326
|
unless list ? values.is_a?(Array) && values.all?(Hash) : values.is_a?(Hash)
|
|
296
|
-
unsupported!('The sample input does not match the Rust
|
|
327
|
+
unsupported!('The sample input does not match the Rust API client layout.')
|
|
297
328
|
end
|
|
298
329
|
objects = list ? values : [values]
|
|
299
330
|
objects.each { |object| validate_object!(io, live['input'], object, required: true, method: saved[:desc]['method']) }
|
|
@@ -321,10 +352,10 @@ module HaveAPI::RustClient
|
|
|
321
352
|
parameter = saved_io[:parameters].find { |item| item[:name] == name }
|
|
322
353
|
live_parameter = live_params && live_params[name]
|
|
323
354
|
unless parameter && live_parameter && compatible_parameter?(parameter[:desc], live_parameter)
|
|
324
|
-
unsupported!('A sample parameter is unavailable or has changed in this Rust
|
|
355
|
+
unsupported!('A sample parameter is unavailable or has changed in this Rust API client.')
|
|
325
356
|
end
|
|
326
357
|
unless valid_value?(parameter[:desc], value, output: !required)
|
|
327
|
-
unsupported!('A sample value cannot be represented by this Rust
|
|
358
|
+
unsupported!('A sample value cannot be represented by this Rust API client.')
|
|
328
359
|
end
|
|
329
360
|
if method == 'GET' && parameter[:desc]['type'] == 'Custom' && (value.is_a?(Hash) || value.is_a?(Array))
|
|
330
361
|
unsupported!('Rust GET requests do not support nested Custom query values.')
|
|
@@ -334,7 +365,7 @@ module HaveAPI::RustClient
|
|
|
334
365
|
|
|
335
366
|
[saved_io[:desc].fetch('parameters'), live_params || {}].each do |parameters|
|
|
336
367
|
if parameters.any? { |name, desc| desc['required'] == true && !object.has_key?(name) }
|
|
337
|
-
unsupported!('The sample omits a required Rust
|
|
368
|
+
unsupported!('The sample omits a required Rust API client input.')
|
|
338
369
|
end
|
|
339
370
|
end
|
|
340
371
|
end
|
|
@@ -394,18 +425,18 @@ module HaveAPI::RustClient
|
|
|
394
425
|
def validate_response!(saved, live, response)
|
|
395
426
|
io = saved[:output]
|
|
396
427
|
unless io
|
|
397
|
-
unsupported!('The sample response does not match this Rust
|
|
428
|
+
unsupported!('The sample response does not match this Rust API client.') unless response == {}
|
|
398
429
|
return
|
|
399
430
|
end
|
|
400
431
|
list = io[:layout].end_with?('_list')
|
|
401
432
|
unless list ? response.is_a?(Array) && response.all?(Hash) : response.is_a?(Hash)
|
|
402
|
-
unsupported!('The sample response does not match this Rust
|
|
433
|
+
unsupported!('The sample response does not match this Rust API client layout.')
|
|
403
434
|
end
|
|
404
435
|
(list ? response : [response]).each { |object| validate_object!(io, live['output'], object, required: false) }
|
|
405
436
|
end
|
|
406
437
|
|
|
407
438
|
def output_lines(action, response)
|
|
408
|
-
return [
|
|
439
|
+
return [] unless action[:output] && response
|
|
409
440
|
|
|
410
441
|
list = action[:output][:layout].end_with?('_list')
|
|
411
442
|
names = (list ? response : [response]).flat_map(&:keys).uniq
|
|
@@ -413,7 +444,7 @@ module HaveAPI::RustClient
|
|
|
413
444
|
names.each do |name|
|
|
414
445
|
parameter = action[:output][:parameters].find { |item| item[:name] == name }
|
|
415
446
|
target = list ? 'item' : 'response.output'
|
|
416
|
-
lines << "#{' ' if list}
|
|
447
|
+
lines << "#{' ' if list}println!(\"{:?}\", &#{target}.#{parameter[:field]});"
|
|
417
448
|
end
|
|
418
449
|
lines << '}' if list
|
|
419
450
|
lines
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi-rust-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.32.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakub Skokan
|
|
@@ -35,14 +35,14 @@ dependencies:
|
|
|
35
35
|
requirements:
|
|
36
36
|
- - "~>"
|
|
37
37
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: 0.
|
|
38
|
+
version: 0.32.0
|
|
39
39
|
type: :runtime
|
|
40
40
|
prerelease: false
|
|
41
41
|
version_requirements: !ruby/object:Gem::Requirement
|
|
42
42
|
requirements:
|
|
43
43
|
- - "~>"
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: 0.
|
|
45
|
+
version: 0.32.0
|
|
46
46
|
- !ruby/object:Gem::Dependency
|
|
47
47
|
name: tomlrb
|
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|