Skip to content

Commit d000aad

Browse files
authored
Merge pull request #1558 from polywrap/0.9-wraperror-rust-parsing-fix
0.9 Fix: WrapError now correctly parsing Rust unwrap errors
2 parents f27bea8 + 82da7ab commit d000aad

18 files changed

Lines changed: 372 additions & 50 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Polywrap Origin (0.9.6)
2+
## Bugs
3+
* [PR-1558](https://github.com/polywrap/toolchain/pull/1558) `@polywrap/core-js` WrapError now correctly parsing Rust unwrap errors.
4+
15
# Polywrap Origin (0.9.5)
26
## Bugs
37
* [PR-1541](https://github.com/polywrap/toolchain/pull/1541) `polywrap` CLI: Update build images to use the latest multi-platform versions.

packages/js/client/src/__tests__/core/error-structure.spec.ts

Lines changed: 177 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,44 @@ const incompatibleWrapperPath = `${GetPathToTestWrappers()}/wasm-as/simple-depre
2222
const incompatibleWrapperUri = new Uri(`fs/${incompatibleWrapperPath}`);
2323

2424
// RS
25-
const invalidTypesWrapperPath = `${GetPathToTestWrappers()}/wasm-rs/invalid-types`;
26-
const invalidTypesWrapperUri = new Uri(`fs/${invalidTypesWrapperPath}/build`);
25+
const invalidTypesWrapperRSPath = `${GetPathToTestWrappers()}/wasm-rs/invalid-types`;
26+
const invalidTypesWrapperRSUri = new Uri(`fs/${invalidTypesWrapperRSPath}/build`);
2727

28-
describe("Error structure", () => {
28+
const subinvokeErrorWrapperRSPath = `${GetPathToTestWrappers()}/wasm-rs/subinvoke-error/invoke`;
29+
const subinvokeErrorWrapperRSUri = new Uri(`fs/${subinvokeErrorWrapperRSPath}/build`);
2930

30-
let client: PolywrapClient;
31+
const badMathWrapperRSPath = `${GetPathToTestWrappers()}/wasm-rs/subinvoke-error/0-subinvoke`;
32+
const badMathWrapperRSUri = new Uri(`fs/${badMathWrapperRSPath}/build`);
3133

32-
beforeAll(async () => {
33-
await buildWrapper(simpleWrapperPath);
34-
await buildWrapper(badUtilWrapperPath);
35-
await buildWrapper(badMathWrapperPath);
36-
await buildWrapper(subinvokeErrorWrapperPath);
37-
await buildWrapper(invalidTypesWrapperPath);
34+
const badUtilWrapperRSPath = `${GetPathToTestWrappers()}/wasm-rs/subinvoke-error/1-subinvoke`;
35+
const badUtilWrapperRSUri = new Uri(`fs/${badUtilWrapperRSPath}/build`);
3836

39-
client = new PolywrapClient({
40-
redirects: [
41-
{
42-
from: "ens/bad-math.eth",
43-
to: badMathWrapperUri,
44-
},
45-
{
46-
from: "ens/bad-util.eth",
47-
to: badUtilWrapperUri,
48-
}
49-
]
50-
})
51-
});
5237

53-
describe("URI resolution", () => {
38+
describe("Error structure", () => {
39+
40+
describe("Wasm wrapper - AS", () => {
41+
let client: PolywrapClient;
42+
43+
beforeAll(async () => {
44+
await buildWrapper(simpleWrapperPath);
45+
await buildWrapper(badUtilWrapperPath);
46+
await buildWrapper(badMathWrapperPath);
47+
await buildWrapper(subinvokeErrorWrapperPath);
48+
49+
client = new PolywrapClient({
50+
redirects: [
51+
{
52+
from: Uri.from("ens/bad-math.eth"),
53+
to: badMathWrapperUri,
54+
},
55+
{
56+
from: Uri.from("ens/bad-util.eth"),
57+
to: badUtilWrapperUri,
58+
}
59+
]
60+
})
61+
});
62+
5463
test("Invoke a wrapper that is not found", async () => {
5564
const result = await client.invoke<string>({
5665
uri: simpleWrapperUri.uri + "-not-found",
@@ -99,10 +108,8 @@ describe("Error structure", () => {
99108
expect(prev.uri).toEqual("wrap://ens/not-found.eth");
100109
expect(prev.resolutionStack).toBeTruthy();
101110
});
102-
});
103111

104-
describe("Wasm wrapper", () => {
105-
test("Invoke a wrapper with malformed arguments - as", async () => {
112+
test("Invoke a wrapper with malformed arguments", async () => {
106113
const result = await client.invoke<string>({
107114
uri: simpleWrapperUri.uri,
108115
method: "simpleMethod",
@@ -123,27 +130,6 @@ describe("Error structure", () => {
123130
expect(result.error?.source).toEqual({ file: "~lib/@polywrap/wasm-as/msgpack/ReadDecoder.ts", row: 167, col: 5 });
124131
});
125132

126-
test("Invoke a wrapper with malformed arguments - rs", async () => {
127-
const result = await client.invoke<string>({
128-
uri: invalidTypesWrapperUri.uri,
129-
method: "boolMethod",
130-
args: {
131-
arg: 3,
132-
},
133-
});
134-
135-
expect(result.ok).toBeFalsy();
136-
if (result.ok) throw Error("should never happen");
137-
138-
expect(result.error?.name).toEqual("WrapError");
139-
expect(result.error?.code).toEqual(WrapErrorCode.WRAPPER_INVOKE_ABORTED);
140-
expect(result.error?.reason.startsWith("__wrap_abort:")).toBeTruthy();
141-
expect(result.error?.uri.endsWith("packages/test-cases/cases/wrappers/wasm-rs/invalid-types/build")).toBeTruthy();
142-
expect(result.error?.method).toEqual("boolMethod");
143-
expect(result.error?.args).toEqual("{\n \"arg\": 3\n}");
144-
expect(result.error?.source).toEqual({ file: "src/wrap/module/wrapped.rs", row: 38, col: 13 });
145-
});
146-
147133
test("Invoke a wrapper method that doesn't exist", async () => {
148134
const result = await client.invoke<string>({
149135
uri: simpleWrapperUri.uri,
@@ -230,7 +216,150 @@ describe("Error structure", () => {
230216
});
231217
});
232218

219+
describe("Wasm wrapper - RS", () => {
220+
let client: PolywrapClient;
221+
222+
beforeAll(async () => {
223+
await buildWrapper(invalidTypesWrapperRSPath);
224+
await buildWrapper(badUtilWrapperRSPath);
225+
await buildWrapper(badMathWrapperRSPath);
226+
await buildWrapper(subinvokeErrorWrapperRSPath);
227+
228+
client = new PolywrapClient({
229+
redirects: [
230+
{
231+
from: Uri.from("ens/bad-math.eth"),
232+
to: badMathWrapperRSUri,
233+
},
234+
{
235+
from: Uri.from("ens/bad-util.eth"),
236+
to: badUtilWrapperRSUri,
237+
}
238+
]
239+
})
240+
});
241+
242+
test("Subinvoke a wrapper that is not found", async () => {
243+
const result = await client.invoke<number>({
244+
uri: subinvokeErrorWrapperRSUri.uri,
245+
method: "subWrapperNotFound",
246+
args: {
247+
a: 1,
248+
b: 1,
249+
},
250+
});
251+
252+
expect(result.ok).toBeFalsy();
253+
if (result.ok) throw Error("should never happen");
254+
255+
expect(result.error?.name).toEqual("WrapError");
256+
expect(result.error?.code).toEqual(WrapErrorCode.WRAPPER_INVOKE_ABORTED);
257+
expect(result.error?.reason.startsWith("SubInvocation exception encountered")).toBeTruthy();
258+
expect(result.error?.uri.endsWith("packages/test-cases/cases/wrappers/wasm-rs/subinvoke-error/invoke/build")).toBeTruthy();
259+
expect(result.error?.method).toEqual("subWrapperNotFound");
260+
expect(result.error?.args).toEqual("{\n \"a\": 1,\n \"b\": 1\n}");
261+
expect(result.error?.source).toEqual({ file: "src/lib.rs", row: 17, col: 57 });
262+
263+
expect(result.error?.innerError instanceof WrapError).toBeTruthy();
264+
const prev = result.error?.innerError as WrapError;
265+
expect(prev.name).toEqual("WrapError");
266+
expect(prev.code).toEqual(WrapErrorCode.URI_NOT_FOUND);
267+
expect(prev.reason).toEqual("Unable to find URI wrap://ens/not-found.eth.");
268+
expect(prev.uri).toEqual("wrap://ens/not-found.eth");
269+
expect(prev.resolutionStack).toBeTruthy();
270+
});
271+
272+
test("Invoke a wrapper with malformed arguments", async () => {
273+
const result = await client.invoke<string>({
274+
uri: invalidTypesWrapperRSUri.uri,
275+
method: "boolMethod",
276+
args: {
277+
arg: 3,
278+
},
279+
});
280+
281+
expect(result.ok).toBeFalsy();
282+
if (result.ok) throw Error("should never happen");
283+
284+
expect(result.error?.name).toEqual("WrapError");
285+
expect(result.error?.code).toEqual(WrapErrorCode.WRAPPER_INVOKE_ABORTED);
286+
expect(result.error?.reason.startsWith("__wrap_abort:")).toBeTruthy();
287+
expect(result.error?.uri.endsWith("packages/test-cases/cases/wrappers/wasm-rs/invalid-types/build")).toBeTruthy();
288+
expect(result.error?.method).toEqual("boolMethod");
289+
expect(result.error?.args).toEqual("{\n \"arg\": 3\n}");
290+
expect(result.error?.source).toEqual({ file: "src/wrap/module/wrapped.rs", row: 38, col: 13 });
291+
});
292+
293+
test("Invoke a wrapper method that doesn't exist", async () => {
294+
const result = await client.invoke<string>({
295+
uri: invalidTypesWrapperRSUri.uri,
296+
method: "complexMethod",
297+
args: {
298+
arg: "test",
299+
},
300+
});
301+
302+
expect(result.ok).toBeFalsy();
303+
if (result.ok) throw Error("should never happen");
304+
305+
expect(result.error?.name).toEqual("WrapError");
306+
expect(result.error?.code).toEqual(WrapErrorCode.WRAPPER_INVOKE_FAIL);
307+
expect(result.error?.reason.startsWith("Could not find invoke function")).toBeTruthy();
308+
expect(result.error?.uri.endsWith("packages/test-cases/cases/wrappers/wasm-rs/invalid-types/build")).toBeTruthy();
309+
expect(result.error?.method).toEqual("complexMethod");
310+
expect(result.error?.args).toEqual("{\n \"arg\": \"test\"\n}");
311+
expect(result.error?.toString().split(
312+
WrapErrorCode.WRAPPER_INVOKE_FAIL.valueOf().toString()
313+
).length).toEqual(2);
314+
expect(result.error?.innerError).toBeUndefined();
315+
});
316+
317+
test("Subinvoke error two layers deep", async () => {
318+
const result = await client.invoke<number>({
319+
uri: subinvokeErrorWrapperRSUri.uri,
320+
method: "throwsInTwoSubinvokeLayers",
321+
args: {
322+
a: 1,
323+
b: 1,
324+
},
325+
});
326+
327+
expect(result.ok).toBeFalsy();
328+
if (result.ok) throw Error("should never happen");
329+
330+
expect(result.error?.name).toEqual("WrapError");
331+
expect(result.error?.code).toEqual(WrapErrorCode.WRAPPER_INVOKE_ABORTED);
332+
expect(result.error?.reason.startsWith("SubInvocation exception encountered")).toBeTruthy();
333+
expect(result.error?.uri.endsWith("packages/test-cases/cases/wrappers/wasm-rs/subinvoke-error/invoke/build")).toBeTruthy();
334+
expect(result.error?.method).toEqual("throwsInTwoSubinvokeLayers");
335+
expect(result.error?.args).toEqual("{\n \"a\": 1,\n \"b\": 1\n}");
336+
expect(result.error?.source).toEqual({ file: "src/lib.rs", row: 9, col: 56 });
337+
338+
expect(result.error?.innerError instanceof WrapError).toBeTruthy();
339+
const prev = result.error?.innerError as WrapError;
340+
expect(prev.name).toEqual("WrapError");
341+
expect(prev.code).toEqual(WrapErrorCode.WRAPPER_INVOKE_ABORTED);
342+
expect(prev.reason.startsWith("SubInvocation exception encountered")).toBeTruthy();
343+
expect(prev.uri).toEqual("wrap://ens/bad-math.eth");
344+
expect(prev.method).toEqual("subInvokeWillThrow");
345+
expect(prev.args).toEqual("{\n \"0\": 130,\n \"1\": 161,\n \"2\": 97,\n \"3\": 1,\n \"4\": 161,\n \"5\": 98,\n \"6\": 1\n}");
346+
expect(prev.source).toEqual({ file: "src/lib.rs", row: 5, col: 75 });
347+
348+
expect(prev.innerError instanceof WrapError).toBeTruthy();
349+
const prevOfPrev = prev.innerError as WrapError;
350+
expect(prevOfPrev.name).toEqual("WrapError");
351+
expect(prevOfPrev.code).toEqual(WrapErrorCode.WRAPPER_INVOKE_ABORTED);
352+
expect(prevOfPrev.reason).toEqual("__wrap_abort: I threw an error!");
353+
expect(prevOfPrev.uri.endsWith("wrap://ens/bad-util.eth")).toBeTruthy();
354+
expect(prevOfPrev.method).toEqual("iThrow");
355+
expect(prevOfPrev.args).toEqual("{\n \"0\": 129,\n \"1\": 161,\n \"2\": 97,\n \"3\": 0\n}");
356+
expect(prevOfPrev.source).toEqual({ file: "src/lib.rs", row: 6, col: 5 });
357+
});
358+
});
359+
233360
describe("Plugin wrapper", () => {
361+
let client: PolywrapClient = new PolywrapClient();
362+
234363
test("Invoke a plugin wrapper with malformed args", async () => {
235364
const result = await client.invoke<Uint8Array>({
236365
uri: "wrap://ens/fs.polywrap.eth",

packages/js/core/src/types/WrapError.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class WrapError extends Error {
8787

8888
private static re = new RegExp(
8989
[
90-
/^(?:[A-Za-z_: ]*; )?WrapError: (?<reason>(?:.|\r|\n)*)/.source,
90+
/^(?:[A-Za-z_:()` ]*;? "?)?WrapError: (?<reason>(?:.|\r|\n)*)/.source,
9191
// there is some padding added to the number of words expected in an error code
9292
/(?:\r\n|\r|\n)code: (?<code>1?[0-9]{1,2}|2[0-4][0-9]|25[0-5]) (?:[A-Z]+ ?){1,5}/
9393
.source,
@@ -99,12 +99,14 @@ export class WrapError extends Error {
9999
.source,
100100
/(?:(?:\r\n|\r|\n)uriResolutionStack: (?<resolutionStack>\[(?:.|\r|\n)+]))?/
101101
.source,
102-
/(?:(?:\r\n|\r|\n){2}This exception was caused by the following exception:(?:\r\n|\r|\n)(?<cause>(?:.|\r|\n)+))?$/
102+
/(?:(?:\r\n|\r|\n){2}This exception was caused by the following exception:(?:\r\n|\r|\n)(?<cause>(?:.|\r|\n)+))?/
103103
.source,
104+
/"?$/.source,
104105
].join("")
105106
);
106107

107108
static parse(error: string): WrapError | undefined {
109+
error = WrapError.sanitizeUnwrappedRustResult(error);
108110
const delim = "\n\nAnother exception was encountered during execution:\n";
109111
const errorStrings = error.split(delim);
110112

@@ -137,6 +139,19 @@ export class WrapError extends Error {
137139
return `${this.name}: ${this.message}`;
138140
}
139141

142+
// remove escape characters that may have been added by Rust
143+
private static sanitizeUnwrappedRustResult(error: string): string {
144+
if (
145+
error.startsWith(
146+
'__wrap_abort: called `Result::unwrap()` on an `Err` value: "'
147+
)
148+
) {
149+
error = error.replace(/\\"/g, '"');
150+
error = error.replace(/\\n/g, "\n");
151+
}
152+
return error;
153+
}
154+
140155
// parse a single WrapError, where the 'prev' property is undefined
141156
private static _parse(
142157
error: string
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "test-case-simple-subinvoke"
3+
version = "0.1.0"
4+
description = "test-case-simple-subinvoke wrapper"
5+
authors = [
6+
"Kobby Pentangeli <kobbypentangeli@gmail.com>",
7+
"Jordan Ellis <jelli@dorg.tech>"
8+
]
9+
repository = "https://github.com/polywrap/monorepo"
10+
license = "MIT"
11+
edition = "2021"
12+
13+
[dependencies]
14+
polywrap-wasm-rs = { path = "../../../../../../wasm/rs" }
15+
serde = { version = "1.0", features = ["derive"] }
16+
17+
[lib]
18+
crate-type = ["cdylib", "rlib"]
19+
20+
[profile.release]
21+
opt-level = 's'
22+
lto = true
23+
panic = 'abort'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
format: 0.2.0
2+
strategies:
3+
image:
4+
name: test-case-simple-subinvoke-0
5+
linked_packages:
6+
- name: polywrap-wasm-rs
7+
path: ../../../../../../wasm/rs
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
format: 0.2.0
2+
project:
3+
name: test-case-simple-subinvoke
4+
type: wasm/rust
5+
source:
6+
schema: ./schema.graphql
7+
module: ./Cargo.toml
8+
import_abis:
9+
- uri: ens/bad-util.eth
10+
abi: ../1-subinvoke/build/wrap.info
11+
extensions:
12+
build: ./polywrap.build.yaml
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import * into BadUtil from "ens/bad-util.eth"
2+
3+
type Module {
4+
subInvokeWillThrow(a: Int!, b: Int!): Int!
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub mod wrap;
2+
pub use wrap::{*, imported::bad_util_module::ArgsIThrow };
3+
4+
pub fn sub_invoke_will_throw(args: ArgsSubInvokeWillThrow) -> i32 {
5+
let sub_invoke_result = BadUtilModule::i_throw( &ArgsIThrow { a: 0 }).unwrap();
6+
args.a + args.b + sub_invoke_result
7+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "test-case-simple-subinvoke"
3+
version = "0.1.0"
4+
description = "test-case-simple-subinvoke wrapper"
5+
authors = [
6+
"Kobby Pentangeli <kobbypentangeli@gmail.com>",
7+
"Jordan Ellis <jelli@dorg.tech>"
8+
]
9+
repository = "https://github.com/polywrap/monorepo"
10+
license = "MIT"
11+
edition = "2021"
12+
13+
[dependencies]
14+
polywrap-wasm-rs = { path = "../../../../../../wasm/rs" }
15+
serde = { version = "1.0", features = ["derive"] }
16+
17+
[lib]
18+
crate-type = ["cdylib", "rlib"]
19+
20+
[profile.release]
21+
opt-level = 's'
22+
lto = true
23+
panic = 'abort'

0 commit comments

Comments
 (0)