diff --git a/.github/scripts/codegen-check.sh b/.github/scripts/codegen-check.sh index 1d67a6b..130ab36 100644 --- a/.github/scripts/codegen-check.sh +++ b/.github/scripts/codegen-check.sh @@ -2,20 +2,19 @@ # # CI artifact — not part of the SDK. # -# Renders the .trix/client-lib codegen plugin against the shared transfer +# Renders tx3c's built-in `rust-client` template against the shared transfer # fixture and verifies the result: # - the expected public surface is generated, and # - the rendered crate compiles. # -# The template tracks the `tx3-sdk` in this repo, which may be ahead of the -# crate published on crates.io — a PR lands the SDK and the template changes -# together, before the release. So the rendered crate is compiled against the -# in-repo `sdk/` crate via a `[patch.crates-io]` override: this verifies the -# template and the SDK in the same checkout stay consistent. The generated -# `Cargo.toml` still carries the real version requirement; only the source is -# redirected. +# The template ships inside tx3c and pins a published `tx3-sdk` range. The +# rendered crate is compiled against the in-repo `sdk/` crate via a +# `[patch.crates-io]` override, so an SDK change that would break the clients +# the current tx3c generates fails here, before the SDK is released. The +# generated `Cargo.toml` still carries the real version requirement; only the +# source is redirected. # -# Requires `tx3c` and `cargo` on PATH. +# Requires `tx3c` (0.24.0 or later, which ships the built-in templates) and `cargo` on PATH. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" @@ -25,7 +24,7 @@ trap 'rm -rf "$gen" "$gen_complex"' EXIT tx3c codegen \ --tii "$repo_root/sdk/tests/fixtures/transfer.tii" \ - --template "$repo_root/.trix/client-lib" \ + --template rust-client \ --output "$gen" for f in lib.rs Cargo.toml; do @@ -67,8 +66,8 @@ for forbidden in \ fi done -# Compile against the SDK in this checkout — the template may require a -# `tx3-sdk` version not yet published to crates.io. +# Compile against the SDK in this checkout, so an unreleased SDK change is +# checked against the client the current tx3c generates. cat >> "$gen/Cargo.toml" <"`, and that `"#` -// closes a single-hash raw string early. Four hashes because no JSON schema -// plausibly contains `"####`. -pub const ENVIRONMENT_SCHEMA: &str = r####"{{{json tii.environment}}}"####; -{{/if}} - -{{#if tii.profiles}} -/// Profiles declared by the protocol. Required argument to [`Client::new`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Profile { -{{#each tii.profiles}} - {{pascalCase @key}}, -{{/each}} -} - -impl Profile { - fn name(&self) -> &'static str { - match self { -{{#each tii.profiles}} - Profile::{{pascalCase @key}} => "{{@key}}", -{{/each}} - } - } -} - -{{#each tii.profiles}} -static {{constantCase @key}}_PROFILE: LazyLock = LazyLock::new(|| { - serde_json::from_str(r####"{{{json this}}}"####) - .expect("codegen invariant: embedded profile parses") -}); -{{/each}} -{{/if}} - -{{#each tii.transactions}} -/// Embedded TIR envelope for the `{{@key}}` transaction. -pub static {{constantCase @key}}_TIR: LazyLock = LazyLock::new(|| TirEnvelope { - content: "{{tir.content}}".to_string(), - encoding: TirEncoding::{{pascalCase tir.encoding}}, - version: "{{tir.version}}".to_string(), -}); -{{/each}} - -/// The protocol's `components.schemas` table, embedded verbatim from the TII. -/// Resolves `#/components/schemas/` refs inside params schemas. -static COMPONENT_SCHEMAS: LazyLock> = LazyLock::new(|| { -{{#if tii.components.schemas}} - serde_json::from_str(r####"{{{json tii.components.schemas}}}"####) - .expect("codegen invariant: embedded component schemas parse") -{{else}} - HashMap::new() -{{/if}} -}); - -{{#each tii.transactions}} -/// Embedded params JSON schema for the `{{@key}}` transaction. Drives -/// type-directed argument encoding into the TRP wire form at resolve time. -static {{constantCase @key}}_PARAMS_SCHEMA: LazyLock = LazyLock::new(|| { - serde_json::from_str(r####"{{{json params}}}"####) - .expect("codegen invariant: embedded params schema parses") -}); -{{/each}} - -{{#if tii.components.schemas}} -// Named types for the protocol's custom (record / variant) types. -{{{componentTypes tii.components.schemas "rust"}}} -{{/if}} -{{#each tii.transactions}} -/// Arguments for the {{@key}} transaction. -#[derive(Debug, Clone, Serialize)] -pub struct {{pascalCase @key}}Params { -{{#each params.properties}} - pub {{snakeCase @key}}: {{schemaTypeFor this "rust"}}, -{{/each}} -} - -impl From<{{pascalCase @key}}Params> for ArgMap { - fn from(args: {{pascalCase @key}}Params) -> Self { - let mut map = ArgMap::new(); -{{#each params.properties}} - map.insert( - "{{@key}}".to_string(), - serde_json::to_value(&args.{{snakeCase @key}}).expect("failed to serialize tx arg"), - ); -{{/each}} - map - } -} - -{{/each}} -/// Typed protocol client exposing the full transaction lifecycle. -pub struct Client(Tx3Client); - -impl Client { - /// Creates a Client over the given TRP options{{#if tii.profiles}} and profile{{/if}}. - {{#if tii.profiles}} - /// The profile is locked in at construction; switching profiles requires - /// a new `Client`. - {{/if}} - pub fn new(options: ClientOptions{{#if tii.profiles}}, profile: Profile{{/if}}) -> Self { - let mut transactions = HashMap::new(); -{{#each tii.transactions}} - transactions.insert("{{@key}}".to_string(), {{constantCase @key}}_TIR.clone()); -{{/each}} - -{{#if tii.profiles}} - let mut profiles = HashMap::new(); -{{#each tii.profiles}} - profiles.insert("{{@key}}".to_string(), {{constantCase @key}}_PROFILE.clone()); -{{/each}} -{{else}} - let profiles = HashMap::new(); -{{/if}} - - let inner = Tx3ClientBuilder::from_parts(transactions, profiles, Default::default()) - .trp(options) -{{#each tii.transactions}} - .with_tx_params( - "{{@key}}", - tx3_sdk::tii::params_from_schema(&{{constantCase @key}}_PARAMS_SCHEMA, &COMPONENT_SCHEMAS), - ) -{{/each}} -{{#if tii.profiles}} - .with_profile(profile.name()) -{{/if}} - .build() - .expect("codegen invariant: builder validates against embedded protocol"); - - Self(inner) - } - -{{#each tii.parties}} - /// Binds the `{{@key}}` party (signer or read-only address). Overrides - /// any address declared for that party by the selected profile. - pub fn with_{{snakeCase @key}}(mut self, party: Party) -> Self { - self.0 = self.0.with_party_unchecked("{{@key}}", party); - self - } - -{{/each}} -{{#each tii.transactions}} - /// Builds the {{@key}} transaction. Drive the returned builder with - /// `.resolve().await?` and the rest of the lifecycle chain. - pub fn {{snakeCase @key}}(&self, args: {{pascalCase @key}}Params) -> TxBuilder { - self.0 - .tx("{{@key}}") - .expect("codegen invariant: tx is declared by the protocol") - .args(args.into()) - } -{{/each}} -}