Skip to content

Commit

Permalink
feat: separate rust_types to own import path
Browse files Browse the repository at this point in the history
  • Loading branch information
chadoh committed Mar 11, 2024
1 parent 76cb907 commit 821dd35
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 68 deletions.
3 changes: 1 addition & 2 deletions src/contract_client/assembled_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
import {
BASE_FEE,
Contract,
ContractSpec,
Memo,
MemoType,
Operation,
Expand All @@ -18,6 +17,7 @@ import {
hash,
xdr,
} from "..";
import { Err } from "../rust_types";

const DEFAULT_TIMEOUT = 10;

Expand Down Expand Up @@ -251,7 +251,6 @@ export class AssembledTransaction<T> {
let i = parseInt(match[1], 10);
let err = this.options.errorTypes[i];
if (!err) return undefined;
const Err = ContractSpec.Result.Err;
return new Err(err);
}

Expand Down
3 changes: 2 additions & 1 deletion src/contract_client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ export type ContractClientOptions = {

export type MethodOptions = {
/**
* The fee to pay for the transaction. Default: BASE_FEE
* The fee to pay for the transaction. Default: {@link BASE_FEE}
*/
fee?: number;

/**
* The maximum amount of time to wait for the transaction to complete. Default: {@link DEFAULT_TIMEOUT}
*/
Expand Down
64 changes: 2 additions & 62 deletions src/contract_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Contract,
scValToBigInt,
} from ".";
import { Ok } from "./rust_types";

export interface Union<T> {
tag: string;
Expand All @@ -22,48 +23,6 @@ function readObj(args: object, input: xdr.ScSpecFunctionInputV0): any {
return entry[1];
}

/**
* A minimal implementation of Rust's `Result` type. Used for contract
* methods that return Results, to maintain their distinction from methods
* that simply either return a value or throw.
*/
interface Result<T, E extends ErrorMessage> {
unwrap(): T;
unwrapErr(): E;
isOk(): boolean;
isErr(): boolean;
}

/**
* Error interface containing the error message. Matches Rust's implementation.
* See reasoning in {@link Result}.
*/
interface ErrorMessage {
message: string;
}

/**
* Part of implementing {@link Result}.
*/
class Ok<T> implements Result<T, never> {
constructor(readonly value: T) {}
unwrapErr(): never { throw new Error("No error") }
unwrap() { return this.value }
isOk() { return true }
isErr() { return false }
}

/**
* Part of implementing {@link Result}.
*/
class Err<E extends ErrorMessage> implements Result<never, E> {
constructor(readonly error: E) {}
unwrapErr() { return this.error }
unwrap(): never { throw new Error(this.error.message) }
isOk() { return false }
isErr() { return true }
}

/**
* Provides a ContractSpec class which can contains the XDR types defined by the contract.
* This allows the class to be used to convert between native and raw `xdr.ScVal`s.
Expand All @@ -90,25 +49,6 @@ class Err<E extends ErrorMessage> implements Result<never, E> {
* ```
*/
export class ContractSpec {
/**
* A minimal implementation of Rust's `Result` type. Used for contract
* methods that return Results, to maintain their distinction from methods
* that simply either return a value or throw.
*/
static Result = {
/**
* A minimal implementation of Rust's `Ok` Result type. Used for contract
* methods that return successful Results, to maintain their distinction
* from methods that simply either return a value or throw.
*/
Ok,
/**
* A minimal implementation of Rust's `Error` Result type. Used for
* contract methods that return unsuccessful Results, to maintain their
* distinction from methods that simply either return a value or throw.
*/
Err
}
public entries: xdr.ScSpecEntry[] = [];

/**
Expand Down Expand Up @@ -224,7 +164,7 @@ export class ContractSpec {
}
let output = outputs[0];
if (output.switch().value === xdr.ScSpecType.scSpecTypeResult().value) {
return new ContractSpec.Result.Ok(
return new Ok(
this.scValToNative(val, output.result().okType())
);
}
Expand Down
14 changes: 14 additions & 0 deletions src/rust_types/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Rust Types
==========

**Why is this needed?**

This is used by `ContractSpec` and `AssembledTransaction` when parsing values return by contracts.

Contract methods can be implemented to return simple values, in which case they can also throw errors. This matches JavaScript's most idiomatic workflow, using `try...catch` blocks.

But Rust also gives the flexibility of returning `Result` types. And Soroban contracts further support this with the `#[contracterror]` macro. Should JavaScript calls to such methods ignore all of that, and just flatten this extra info down to the same `try...catch` flow as other methods? We're not sure.

For now, we've added this special `rust_types` logic, which exports the `Result` interface and its associated implementations, `Ok` and `Err`. This allows `ContractSpec` and `AssembledTransaction` to work together to duplicate the contract's Rust logic, always returning `Result` types for contract methods that are implemented to do so.

In the future, if this feels too un-idiomatic for JavaScript, we can always remove this and flatten all JS calls to `try...catch`. Easier to remove this logic later than it would be to add it.
47 changes: 47 additions & 0 deletions src/rust_types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* A minimal implementation of Rust's `Result` type. Used for contract
* methods that return Results, to maintain their distinction from methods
* that simply either return a value or throw.
*/
export interface Result<T, E extends ErrorMessage> {
unwrap(): T;
unwrapErr(): E;
isOk(): boolean;
isErr(): boolean;
}

/**
* Error interface containing the error message. Matches Rust's implementation.
* Part of implementing {@link Result}, a minimal implementation of Rust's
* `Result` type. Used for contract methods that return Results, to maintain
* their distinction from methods that simply either return a value or throw.
*/
export interface ErrorMessage {
message: string;
}

/**
* Part of implementing {@link Result}, a minimal implementation of Rust's
* `Result` type. Used for contract methods that return Results, to maintain
* their distinction from methods that simply either return a value or throw.
*/
export class Ok<T> implements Result<T, never> {
constructor(readonly value: T) {}
unwrapErr(): never { throw new Error("No error") }
unwrap() { return this.value }
isOk() { return true }
isErr() { return false }
}

/**
* Part of implementing {@link Result}, a minimal implementation of Rust's
* `Result` type. Used for contract methods that return Results, to maintain
* their distinction from methods that simply either return a value or throw.
*/
export class Err<E extends ErrorMessage> implements Result<never, E> {
constructor(readonly error: E) {}
unwrapErr() { return this.error }
unwrap(): never { throw new Error(this.error.message) }
isOk() { return false }
isErr() { return true }
}
7 changes: 4 additions & 3 deletions test/e2e/src/test-custom-types.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const test = require('ava')
const { Address, ContractSpec } = require('../../..')
const { Address } = require('../../..')
const { Ok, Err } = require('../../../lib/rust_types')
const { clientFor } = require('./util')

test.before(async t => {
Expand All @@ -21,11 +22,11 @@ test('woid', async t => {
test('u32_fail_on_even', async t => {
t.deepEqual(
(await t.context.client.u32FailOnEven({ u32_: 1 })).result,
new ContractSpec.Result.Ok(1)
new Ok(1)
)
t.deepEqual(
(await t.context.client.u32FailOnEven({ u32_: 2 })).result,
new ContractSpec.Result.Err({ message: "Please provide an odd number" })
new Err({ message: "Please provide an odd number" })
)
})

Expand Down

0 comments on commit 821dd35

Please sign in to comment.