haveapi-rust-client 0.30.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 21033d06951c8de41c052be7cf6d615912ba7fb83e450ed07d360d46691fc880
4
+ data.tar.gz: ace71e7005ca64d911730b47a67795ec64338097bcc3f4116976460c70d5de6b
5
+ SHA512:
6
+ metadata.gz: a0de1d3a4d6c2d282e4b68012a43147e3ca865251fe03f179853db9d9b6fe3963fc9950b1a7585457c274a08bb351cc4d073f33385e764649ce4d81e15b96743
7
+ data.tar.gz: 55cebb5a66f6592549da6ccfdb9384d83812373485656e2c2f907da4c3e33618686e5f0fec2b36c2b29af79d1b6fefa7d6d3e126fea6827b719e5d3abbf98226
data/Gemfile.lock ADDED
@@ -0,0 +1,200 @@
1
+ PATH
2
+ remote: ../ruby
3
+ specs:
4
+ haveapi-client (0.30.0)
5
+ activesupport (>= 7.0)
6
+ highline (~> 3.1)
7
+ json
8
+ require_all (~> 2.0.0)
9
+ rest-client (~> 2.1.0)
10
+ ruby-progressbar (~> 1.13.0)
11
+
12
+ PATH
13
+ remote: .
14
+ specs:
15
+ haveapi-rust-client (0.30.0)
16
+ haveapi-client (~> 0.30.0)
17
+ tomlrb (~> 2.0)
18
+
19
+ GEM
20
+ remote: https://rubygems.org/
21
+ specs:
22
+ activesupport (8.1.4)
23
+ base64
24
+ bigdecimal
25
+ concurrent-ruby (~> 1.0, >= 1.3.1)
26
+ connection_pool (>= 2.2.5)
27
+ drb
28
+ i18n (>= 1.6, < 2)
29
+ json
30
+ logger (>= 1.4.2)
31
+ minitest (>= 5.1)
32
+ securerandom (>= 0.3)
33
+ tzinfo (~> 2.0, >= 2.0.5)
34
+ uri (>= 0.13.1)
35
+ ast (2.4.3)
36
+ base64 (0.3.0)
37
+ bigdecimal (4.1.3)
38
+ concurrent-ruby (1.3.8)
39
+ connection_pool (3.0.2)
40
+ diff-lcs (1.6.2)
41
+ domain_name (0.7.20261007)
42
+ drb (2.2.3)
43
+ hana (1.3.7)
44
+ highline (3.1.2)
45
+ reline
46
+ http-accept (1.7.0)
47
+ http-cookie (1.1.6)
48
+ domain_name (~> 0.5)
49
+ i18n (1.15.2)
50
+ concurrent-ruby (~> 1.0)
51
+ io-console (0.9.4)
52
+ json (2.21.2)
53
+ json_schemer (2.5.0)
54
+ bigdecimal
55
+ hana (~> 1.3)
56
+ regexp_parser (~> 2.0)
57
+ simpleidn (~> 0.2)
58
+ language_server-protocol (3.17.0.6)
59
+ lint_roller (1.1.0)
60
+ logger (1.7.0)
61
+ mcp (0.25.0)
62
+ json_schemer (>= 2.4)
63
+ mime-types (3.7.0)
64
+ logger
65
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
66
+ mime-types-data (3.2026.1013)
67
+ minitest (6.0.6)
68
+ drb (~> 2.0)
69
+ prism (~> 1.5)
70
+ netrc (0.11.0)
71
+ parallel (1.28.0)
72
+ parser (3.3.12.0)
73
+ ast (~> 2.4.1)
74
+ racc
75
+ prism (1.9.0)
76
+ racc (1.8.1)
77
+ rainbow (3.1.1)
78
+ rake (13.4.2)
79
+ regexp_parser (2.13.1)
80
+ reline (0.7.0)
81
+ io-console (~> 0.5)
82
+ require_all (2.0.0)
83
+ rest-client (2.1.0)
84
+ http-accept (>= 1.7.0, < 2.0)
85
+ http-cookie (>= 1.0.2, < 2.0)
86
+ mime-types (>= 1.16, < 4.0)
87
+ netrc (~> 0.8)
88
+ rspec (3.13.2)
89
+ rspec-core (~> 3.13.0)
90
+ rspec-expectations (~> 3.13.0)
91
+ rspec-mocks (~> 3.13.0)
92
+ rspec-core (3.13.6)
93
+ rspec-support (~> 3.13.0)
94
+ rspec-expectations (3.13.5)
95
+ diff-lcs (>= 1.2.0, < 2.0)
96
+ rspec-support (~> 3.13.0)
97
+ rspec-mocks (3.13.8)
98
+ diff-lcs (>= 1.2.0, < 2.0)
99
+ rspec-support (~> 3.13.0)
100
+ rspec-support (3.13.7)
101
+ rubocop (1.85.1)
102
+ json (~> 2.3)
103
+ language_server-protocol (~> 3.17.0.2)
104
+ lint_roller (~> 1.1.0)
105
+ mcp (~> 0.6)
106
+ parallel (~> 1.10)
107
+ parser (>= 3.3.0.2)
108
+ rainbow (>= 2.2.2, < 4.0)
109
+ regexp_parser (>= 2.9.3, < 3.0)
110
+ rubocop-ast (>= 1.49.0, < 2.0)
111
+ ruby-progressbar (~> 1.7)
112
+ unicode-display_width (>= 2.4.0, < 4.0)
113
+ rubocop-ast (1.50.0)
114
+ parser (>= 3.3.7.2)
115
+ prism (~> 1.7)
116
+ rubocop-rspec (3.9.0)
117
+ lint_roller (~> 1.1)
118
+ rubocop (~> 1.81)
119
+ ruby-progressbar (1.13.0)
120
+ securerandom (0.4.1)
121
+ simpleidn (0.3.0)
122
+ tomlrb (2.0.5)
123
+ racc (~> 1.4)
124
+ tzinfo (2.0.6)
125
+ concurrent-ruby (~> 1.0)
126
+ unicode-display_width (3.3.0)
127
+ unicode-emoji (~> 4.3)
128
+ unicode-emoji (4.3.0)
129
+ uri (1.1.2)
130
+
131
+ PLATFORMS
132
+ ruby
133
+ x86_64-linux
134
+
135
+ DEPENDENCIES
136
+ haveapi-client!
137
+ haveapi-rust-client!
138
+ rake
139
+ rspec
140
+ rubocop (~> 1.85.0)
141
+ rubocop-rspec
142
+
143
+ CHECKSUMS
144
+ activesupport (8.1.4) sha256=891dd3fe43c4fc8391934b237b1189bb1f661c932a84a840043db9d46a4e80df
145
+ ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
146
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
147
+ bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66
148
+ bundler (4.0.23) sha256=407ba8a1d928f412847f819aa5f8b6aef7c59fb51feb517112af8a165487287b
149
+ concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
150
+ connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
151
+ diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
152
+ domain_name (0.7.20261007) sha256=0976c6ab837dbde991b433d03acc38fdb91fa8839df31a1ed75ce0318dc0441c
153
+ drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
154
+ hana (1.3.7) sha256=5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d
155
+ haveapi-client (0.30.0)
156
+ haveapi-rust-client (0.30.0)
157
+ highline (3.1.2) sha256=67cbd34d19f6ef11a7ee1d82ffab5d36dfd5b3be861f450fc1716c7125f4bb4a
158
+ http-accept (1.7.0) sha256=c626860682bfbb3b46462f8c39cd470fd7b0584f61b3cc9df5b2e9eb9972a126
159
+ http-cookie (1.1.6) sha256=ba4b82be64de61dc281243dac70e3c382c45142f20268ed9276a3670c93feaa9
160
+ i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
161
+ io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1
162
+ json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
163
+ json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
164
+ language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
165
+ lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
166
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
167
+ mcp (0.25.0) sha256=8acf3a5f3b60b6fd6e5d9b8ac5eb85ac504766976cbadaca182d2ffe7d795d98
168
+ mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56
169
+ mime-types-data (3.2026.1013) sha256=370f152f2a47e54e58829766492ea2cbe130a45358374b8788a6a8ad4e961c96
170
+ minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
171
+ netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f
172
+ parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970
173
+ parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
174
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
175
+ racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
176
+ rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
177
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
178
+ regexp_parser (2.13.1) sha256=5aedb6b7c35688f51e86eef17a15374dfd287c83f4ca644b2c5f3ec50a33b44b
179
+ reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
180
+ require_all (2.0.0) sha256=68d7b944b0f1c9898a16f9f102b448897c06fd2781d30efec19cbcf6e2164e6a
181
+ rest-client (2.1.0) sha256=35a6400bdb14fae28596618e312776c158f7ebbb0ccad752ff4fa142bf2747e3
182
+ rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
183
+ rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
184
+ rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
185
+ rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
186
+ rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
187
+ rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77
188
+ rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
189
+ rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
190
+ ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
191
+ securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
192
+ simpleidn (0.3.0) sha256=12ca730bed2f3db04d11e9bfd1bca3e11fb37f55b21eb2e9793fb5814bf54d03
193
+ tomlrb (2.0.5) sha256=6e9a5e70a94a723842e770fcd14c49867703500d07cd03ebb6c343138a7b4477
194
+ tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
195
+ unicode-display_width (3.3.0) sha256=4b7aa66a4b11db50f6f7e98411215cd0dd10eaecb665d36eec5bcacb3fa0b613
196
+ unicode-emoji (4.3.0) sha256=11c02fa73290378c066bb0562cd4c87d8e1b706fbbe0059ca12746a4244de8ce
197
+ uri (1.1.2) sha256=89292e98eabbef6248b0a0ed5e7e398e152f83f17560023a6d94a7f492b169fb
198
+
199
+ BUNDLED WITH
200
+ 4.0.23
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jakub Skokan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Rust client generator
2
+
3
+ This Ruby generator turns a saved HaveAPI protocol 2 description into a typed,
4
+ self-contained Rust client for one explicit API version. The generated client
5
+ uses async reqwest by default and offers the same actions through the optional
6
+ `blocking` feature. Ruby and HaveAPI are generation tools, not dependencies of
7
+ the resulting Rust crate.
8
+
9
+ The client contract uses edition 2024, Rust 1.95 or newer, Serde, url 2.5,
10
+ and reqwest 0.13.5 with verified rustls TLS. Generation requires the exact
11
+ formatter version recorded in the client configuration. New test fixtures use
12
+ the formatter from the HaveAPI Nix shell; existing clients retain their own
13
+ recorded formatter. A separate acceptance check verifies Rust 1.95 because the
14
+ development compiler is newer.
15
+
16
+ ## Development
17
+
18
+ From the HaveAPI root:
19
+
20
+ ```sh
21
+ nix develop .#client-rust
22
+ cd clients/rust
23
+ bundle exec rspec spec/unit
24
+ bundle exec rubocop lib bin spec/unit spec/spec_helper.rb haveapi-rust-client.gemspec
25
+ ```
26
+
27
+ The component shell installs the dependencies frozen in the tracked
28
+ `clients/rust/Gemfile.lock`. Use its Ruby interpreter with those gems; native
29
+ gems installed by this shell cannot be loaded by a different ambient Ruby.
30
+ The root Git hooks also need the root shell's dependencies: prepare them with
31
+ `nix develop --command true` before committing, then commit with the declared
32
+ Overcommit hooks enabled. Do not install gems concurrently in the same gem home.
33
+
34
+ `make test-rust-unit` runs the generator specs. `make test-rust` additionally
35
+ runs both runtime feature configurations and the generated-client protocol
36
+ harness. Compilation, linting Rust, server integration, MSRV and packaging can
37
+ take longer; use the development workspace's verification procedure when
38
+ working there. Unit specs never start a server or invoke Cargo.
39
+
40
+ ## Generate a client
41
+
42
+ Keep acquisition separate from generation:
43
+
44
+ ```sh
45
+ bundle exec haveapi-rust-client fetch https://api.example.test/v1.0/ \
46
+ --api-version 1.0 --profile public --output schema/api-1.0.json
47
+ bundle exec haveapi-rust-client generate --schema schema/api-1.0.json \
48
+ --api-version 1.0 --config generator.toml --output src/generated
49
+ bundle exec haveapi-rust-client generate --schema schema/api-1.0.json \
50
+ --api-version 1.0 --config generator.toml --output src/generated --check
51
+ ```
52
+
53
+ Run these commands from the consuming project with this generator available in
54
+ its Bundler environment. The explicit URL includes the selected version.
55
+ Neither command selects the server's default API version. Offline generation
56
+ uses only the saved schema, generator configuration, locked Ruby dependencies,
57
+ maintained runtime source and pinned formatter.
58
+
59
+ The handwritten crate entry point is:
60
+
61
+ ```rust
62
+ mod generated;
63
+ pub use generated::*;
64
+ ```
65
+
66
+ A generated action looks like
67
+ `api.vps().update(123).hostname("example").send().await?`. The blocking client
68
+ uses `blocking::Client` and ends the same chain in `.send()?`. Construct, use and
69
+ drop a blocking client outside an async runtime, or put its entire lifecycle
70
+ inside `tokio::task::spawn_blocking`. See the [runtime contract](runtime/README.md)
71
+ for authentication, errors, deadlines and blocking restrictions.
72
+
73
+ Read the [integration harness guide](spec/integration/README.md) for fixture
74
+ setup and acceptance commands.
75
+
76
+ Read [generation and snapshots](docs/generation.md) for configuration, naming,
77
+ metadata fidelity and ownership rules, and [packaging](docs/releasing.md) before
78
+ preparing a generator or generated-client release.
79
+
80
+ The [server documentation adapter](docs/documentation.md) provides optional
81
+ async and blocking examples for a configured published SDK profile.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'haveapi/rust_client/cli'
3
+ exit HaveAPI::RustClient::Cli.run
@@ -0,0 +1,103 @@
1
+ # Rust examples in server documentation
2
+
3
+ The optional `HaveAPI::RustClient::Documentation` adapter maps a published Rust
4
+ SDK's saved snapshot and TOML configuration to server documentation. It uses the same
5
+ Snapshot validation, complete IR, naming scopes and types as the generator.
6
+ Construction and rendering make no network requests and run no generator, compiler
7
+ or formatter. The server loads its trusted local files at startup.
8
+
9
+ An API operator installs the matching `haveapi-rust-client` gem and configures a
10
+ profile before mounting the server:
11
+
12
+ ```ruby
13
+ api.documentation_clients(
14
+ '1.0',
15
+ rust: {
16
+ snapshot_path: '/srv/api/sdk/schema/api-1.0.json',
17
+ generator_config_path: '/srv/api/sdk/generator.toml',
18
+ generator_version: HaveAPI::RustClient::VERSION
19
+ }
20
+ )
21
+ ```
22
+
23
+ Use the exact generator version that produced the published SDK. The profile's
24
+ package name and package version come from `crate_name` and `crate_version` in
25
+ that SDK's existing [generator config](generation.md). They are independent of
26
+ the API and HaveAPI package versions. Rust imports normalize crate-name hyphens
27
+ to underscores. Configure a profile only for an SDK that users can install; the
28
+ examples do not publish or generate that package.
29
+
30
+ The server hides both Rust variants when that API version has no Rust profile.
31
+ Invalid configured profiles fail at startup. A configured profile can still serve examples after unrelated additions to the
32
+ live API. Each rendered sample must match the saved route,
33
+ HTTP method, input and output layouts, and the contracts of the parameters it
34
+ uses. The adapter
35
+ checks the full SDK map against the current user's filtered help and sample;
36
+ it never recomputes names from a reduced schema or prints hidden saved defaults.
37
+ An unavailable action, changed parameter or unsupported value produces a short
38
+ limitation in place of code. Filesystem paths remain outside rendered output.
39
+
40
+ Both variants show a complete program and Cargo dependency comments. Async
41
+ programs use Tokio and `.send().await?`. Blocking programs enable the SDK's
42
+ `blocking` feature and construct, use and drop the client in a synchronous
43
+ `main`, outside async runtime threads. `HAVEAPI_BASE_URL` can override the shown
44
+ mounted root. Authenticated actions use advertised existing credentials from
45
+ `HAVEAPI_USER`/`HAVEAPI_PASSWORD` or `HAVEAPI_TOKEN`; authentication examples never
46
+ invent an OAuth acquisition flow or automatically follow token challenges.
47
+
48
+ Object setters preserve null, zero, false and empty strings, and leave omitted
49
+ fields unset.
50
+ Lists use generated `Input` structs with `InputValue` fields and
51
+ `..Default::default()` for omitted values. GET lists and nested Custom query
52
+ values produce limitations. The example DSL has no metadata input channel, so actions requiring metadata
53
+ show a limitation. Responses expose
54
+ `output`, typed `meta` and `http_status`. Fields accessed in examples must occur
55
+ in the authorized sample. Rust provides explicit action-state calls rather
56
+ than an automatic operation-wait helper. Request success and timeouts do not
57
+ establish whether a server-side operation finished or was rolled back.
58
+
59
+ ## Verification
60
+
61
+ The pure adapter specs and server renderer bridge specs run before compilation:
62
+
63
+ ```sh
64
+ nix develop .#client-rust --command bash -c \
65
+ 'cd clients/rust && bundle exec rspec spec/unit/documentation_spec.rb spec/unit/documentation_harness_spec.rb'
66
+ nix develop .#server-ruby --command bash -c \
67
+ 'cd servers/ruby && bundle exec rspec spec/client_examples/rust_client_spec.rb'
68
+ ```
69
+
70
+ The acceptance runner extracts actual `RustAsyncClient.render` and
71
+ `RustBlockingClient.render` source. It generates a matching temporary fixture
72
+ SDK, compiles the emitted imports, paths, methods and types, and executes the
73
+ programs against the existing disposable HaveAPI fixture at `/` and
74
+ `/mounted/api/`. It adds output assertions without replacing emitted requests.
75
+ Authentication checks compose the emitted authentication builder with the
76
+ emitted action body. It tests both modes with the development compiler and
77
+ Rust 1.95, including zero/false/empty/null/omitted inputs, nested Custom, lists,
78
+ nested resources, failure branches, metadata, existing credentials and an
79
+ action-state reference. The fixture uses local test credentials and in-memory
80
+ data.
81
+
82
+ Prepare the Rust/server environments as described in the
83
+ [integration guide](../spec/integration/README.md). Supply absolute paths to
84
+ the independently selected Rust 1.95 Cargo and compiler executables:
85
+
86
+ ```sh
87
+ HAVEAPI_RUST_DOCUMENTATION=1 \
88
+ HAVEAPI_RUST_MSRV_CARGO=/path/to/rust-1.95/bin/cargo \
89
+ HAVEAPI_RUST_MSRV_RUSTC=/path/to/rust-1.95/bin/rustc \
90
+ HAVEAPI_RUST_DOCUMENTATION_EVIDENCE=/path/to/new-evidence-directory \
91
+ nix develop .#client-rust --command ruby clients/rust/spec/documentation/run.rb
92
+ ```
93
+
94
+ The optional `HAVEAPI_RUST_DOCUMENTATION_EVIDENCE` directory retains separate
95
+ `root` and `mounted-api` receipts: snapshot, generator TOML, Cargo manifest and
96
+ lockfile, generated source and ownership manifest, emitted programs and selected
97
+ tool paths/versions. Choose a fresh directory; existing mount receipts cause an
98
+ error. Build outputs and caches remain temporary. A failed run retains whichever
99
+ fixture artifacts were available before the failure.
100
+
101
+ Ordinary unit specs do not start this runner. Temporary generated SDK/source,
102
+ Cargo outputs and the owned server are removed by their native runner cleanup;
103
+ failed server startup retains the integration launcher's private diagnostic log.
@@ -0,0 +1,149 @@
1
+ # Snapshots and generation
2
+
3
+ Each snapshot describes the surface visible to its capture profile for a single
4
+ API version. It cannot establish that permission-filtered or plugin-specific
5
+ resources are complete. Inventory the captured resources, actions and parameters
6
+ and record unknown coverage explicitly. Do not combine unrelated users' schemas
7
+ or add fixture-only descriptions to a production snapshot.
8
+
9
+ ## Snapshot format
10
+
11
+ The saved JSON envelope contains exactly these keys:
12
+
13
+ | Key | Meaning |
14
+ | --- | --- |
15
+ | `schema_format` | Snapshot format, currently `1` |
16
+ | `api_version` | Explicit API version string |
17
+ | `protocol_version` | HaveAPI protocol version, currently major `2` |
18
+ | `source_url` | Credential-free URL of the explicit version endpoint |
19
+ | `profile` | Non-secret capture profile label |
20
+ | `mount_path` | Server mount root, including leading and trailing `/` |
21
+ | `description` | Sanitized HaveAPI description |
22
+
23
+ `Snapshot.canonical` sorts object keys, indents by two spaces and writes UTF-8
24
+ with LF endings and a terminal newline. Array order remains meaningful. The
25
+ schema SHA-256 covers those exact bytes; it identifies the generated interface
26
+ in addition to the API version.
27
+
28
+ The sanitizer removes examples, labels, descriptions, validator messages and
29
+ protected default values. It preserves parameter and choice keys, machine
30
+ validators, ordinary defaults, metadata, auth headers, public OAuth discovery
31
+ and token action schemas. A field named `default`, `description` or `token`
32
+ remains a field. Protected defaults produce diagnostics without their values;
33
+ callers save these in separate provenance. Review remaining defaults before
34
+ publishing a snapshot. Never save credentials, HTTP headers/cookies or login
35
+ responses in a snapshot or provenance record.
36
+
37
+ The library entry points are:
38
+
39
+ ```ruby
40
+ snapshot = HaveAPI::RustClient::Snapshot.fetch(
41
+ url: 'https://api.example.test/v1.0/', api_version: '1.0', profile: 'public'
42
+ )
43
+ snapshot = HaveAPI::RustClient::Snapshot.load('schema/api-1.0.json')
44
+ inventory = HaveAPI::RustClient::IR.new(snapshot).inventory
45
+ HaveAPI::RustClient::Generator.new(schema: snapshot, config: config)
46
+ .generate(output: 'src/generated', check: true)
47
+ ```
48
+
49
+ Capture tools that already hold an OPTIONS envelope can use
50
+ `Snapshot.from_envelope(envelope, api_version:, source_url:, profile:,
51
+ mount_path: '/', diagnostics: [])`. This applies the same sanitizer used by
52
+ fetch. Pass a diagnostics array to retain removal notices in provenance.
53
+ Acquisition timestamps, raw capture hashes, the source revision, resolved
54
+ Ruby/Cargo locks and exact Rust toolchain belong in separate provenance.
55
+ The generated ownership manifest records schema, runtime and generator-source
56
+ digests, Ruby lock digest, configuration and the owned file inventory.
57
+
58
+ ## Configuration and crate scaffold
59
+
60
+ `generator.toml` has these root keys:
61
+
62
+ ```toml
63
+ crate_name = "example-client"
64
+ crate_version = "0.1.0"
65
+ license = "MIT"
66
+ edition = "2024"
67
+ rust_version = "1.95"
68
+ rustfmt_version = "rustfmt 1.9.0"
69
+ ```
70
+
71
+ The crate version is independent of the API version. This generator supports
72
+ the displayed license, edition and MSRV. The formatter version above is an
73
+ example; record the exact `rustfmt --version` output for the client's chosen
74
+ formatter. `RUSTFMT` can select its executable. Generation rejects a version
75
+ that differs from the configuration.
76
+
77
+ `Generator#manifest_requirements` renders the initial Cargo manifest template.
78
+ It declares serde 1 with derive, serde_json 1, url 2.5, and reqwest 0.13.5 with
79
+ `default-features = false` and `features = ["json", "rustls"]`. Features are
80
+ `default = []` and `blocking = ["reqwest/blocking"]`. Tokio 1 with macros and
81
+ rt-multi-thread supports tests/examples. Commit the consuming crate's Cargo.lock.
82
+ Regeneration checks these requirements when the output is `src/generated`
83
+ inside an existing crate. It reports mismatches and never replaces Cargo.toml
84
+ or other handwritten package files.
85
+
86
+ ## Generated interface
87
+
88
+ Resource components normalize to snake_case and join with double underscores.
89
+ For example, nested `project/task` becomes `project__task`, while the top-level
90
+ resource `project_task` keeps that name. A flat accessor binds ancestor path
91
+ arguments: `api.project__task(project_id).show(task_id)`. Arguments follow the
92
+ path template's order and accept values implementing `ToString`; the runtime
93
+ encodes each value as a single path segment. All remaining name collisions fail
94
+ with both original schema paths. Rust keywords receive a trailing underscore.
95
+ Action aliases use the same request and models.
96
+
97
+ Action modules live at `resources::<resource_path>::<action>`. They expose
98
+ `Input`, `Output`, `Meta` and `SPEC` where applicable. Object/hash input has a
99
+ unique defaultable struct with public `InputValue<T>` fields. Its initial state
100
+ is Unset. Value setters preserve zero, false and empty strings; nullable fields
101
+ also have `_null()` setters. Omitted fields stay omitted so server defaults can
102
+ apply. List input uses `Vec<Input>` through `.input(...)`, rather than setters
103
+ that could ambiguously apply to multiple items.
104
+
105
+ The request validates input before JSON serialization, then the runtime checks
106
+ required, nullable and typed values before dispatch. Datetime inputs are strings
107
+ validated using HaveAPI's ISO forms. Resource input is a nonnegative numeric ID;
108
+ portable metadata does not describe arbitrary string primary keys. Server-side
109
+ validators remain authoritative; the generator retains their metadata without
110
+ attempting to translate arbitrary Ruby regular expressions into Rust.
111
+
112
+ All ordinary output fields are optional, including fields marked required in
113
+ the description, because authorization can hide them. Missing and null output
114
+ fields both become `None`; wrong field types remain decoding errors. Extra
115
+ fields and metadata are retained. Custom values use `serde_json::Value`.
116
+ Associations retain their partial JSON maps and documented ID/label keys; they
117
+ do not imply a complete Show response or trigger a network request.
118
+
119
+ GET uses bracketed scalar query inputs. Explicit nullable null becomes an empty
120
+ query value, so nullable empty strings cannot always be distinguished from null
121
+ in GET requests. Nested Custom values and list layouts in GET queries fail
122
+ locally. DELETE, POST, PUT and PATCH send JSON object bodies, preserving input
123
+ and metadata namespaces. Body input supports objects and named lists, including
124
+ nested Custom values and explicit JSON null; an empty assembled body is `{}`.
125
+ Ordinary output `namespace=false` and unnamespaced list input are rejected
126
+ rather than guessed. Named empty object/hash output permits the server's
127
+ `response: {}` shortcut; missing namespaces remain errors for nonempty or list
128
+ outputs.
129
+
130
+ ## Owned output and recovery
131
+
132
+ The generator owns only the requested output directory. It stages and formats
133
+ files before installation and records ownership in `.haveapi-generated.json`.
134
+ Existing nonempty output needs this marker; foreign files and symlinks are
135
+ refused. Regeneration removes obsolete owned files and preserves siblings such
136
+ as the handwritten facade and Cargo.toml. Never put handwritten files inside
137
+ the generated directory.
138
+
139
+ First installation uses an atomic directory rename. Replacing nonempty output
140
+ uses Linux `renameat2` directory exchange; hosts without that operation receive
141
+ an error before replacement. `--check` compares a temporary candidate without
142
+ writing the destination, its parent, or package files. A stale check exits with
143
+ an error and leaves existing contents and timestamps intact.
144
+
145
+ Keep runtime fixes in HaveAPI, regenerate each affected client from its retained
146
+ snapshot, and release that client through its own workflow. Copied runtime
147
+ source matches `runtime/src/runtime` byte-for-byte and has no path or Git runtime
148
+ dependency. Reverting a client revision restores its previous generated surface;
149
+ it cannot undo API actions that a newer client already sent.
data/docs/releasing.md ADDED
@@ -0,0 +1,61 @@
1
+ # Packaging the Rust generator and generated clients
2
+
3
+ The Ruby gem is `haveapi-rust-client` and follows HaveAPI's shared VERSION.
4
+ `make version` updates its version, client dependency and tracked generator lock
5
+ alongside existing components. The top-level release build includes this gem,
6
+ and the protected publishing job uploads it after `haveapi-client`. Configure
7
+ its RubyGems publisher before release; a new gem can use a pending publisher
8
+ without a manual seed upload. Follow the
9
+ [shared release procedure](../../../doc/agent-instructions/releases.md) for
10
+ registry setup, approval, retained artifacts and recovery.
11
+
12
+ Prepare its package from `clients/rust` in the component Nix shell:
13
+
14
+ ```sh
15
+ bundle exec rake build
16
+ ```
17
+
18
+ The gem includes Ruby/ERB sources, the executable, documentation, Ruby dependency
19
+ lock and maintained runtime source. It excludes the private Cargo harness and
20
+ integration fixtures. The top-level `make release` builds and inspects this gem,
21
+ retains its checksum and source in the shared manifest, and runs an isolated
22
+ installed-package generation check. To repeat that check against retained
23
+ artifacts in the root Nix shell:
24
+
25
+ ```sh
26
+ ruby utils/release/rust_smoke.rb dist
27
+ ```
28
+
29
+ The check loads the generator from the package and verifies its copied runtime.
30
+ Run the broader generator, runtime and generated-client checks as well.
31
+ `make test-rust-unit` runs the quick generator specs; `make test-rust` covers
32
+ those specs, both runtime feature configurations
33
+ and generated-client protocol acceptance. Top-level `make test` includes that
34
+ Rust target. Publishing a new gem version requires separate authorization and
35
+ approval in the protected release environment; `make publish` exits without
36
+ publishing.
37
+
38
+ Generated Rust crates have their own SemVer and Cargo.lock. Their API-version
39
+ constant and schema digest identify the target independently of the crate
40
+ version. Adding required input, changing public types or removing actions can
41
+ require a breaking crate release; increasing the MSRV is a deliberate release
42
+ decision. Extra response fields and unknown choice values remain compatible
43
+ through optional fields and open string values.
44
+
45
+ The embedded runtime has no separate registry release. Its `haveapi-runtime-tests`
46
+ Cargo package is a private development harness with `publish = false`, not an
47
+ artifact of HaveAPI's shared version. No crates.io token is needed to release
48
+ the generator gem. Publish a generated crate through its consuming project's
49
+ own release procedure and registry identity.
50
+
51
+ Before shipping a generated client, verify deterministic offline regeneration
52
+ and read-only `--check`, formatting, async and blocking library/examples, protocol
53
+ fixtures, the declared minimum Rust version and `cargo package`. Inspect the
54
+ archive for copied runtime source and ordinary registry dependencies. No Ruby,
55
+ HaveAPI checkout or private runtime crate should be needed to build it.
56
+
57
+ A runtime source fix reaches consumers only after regeneration and release of
58
+ each affected generated crate. Keep the prior schema, generator revision,
59
+ lockfiles and generated revision available for recovery. Pinning a previous
60
+ crate version can restore software behavior but cannot reverse server changes
61
+ made by an API action.