Skip to content

Commit 82080b1

Browse files
authored
Add YAML parsing support to WASM runtime (#5785)
* runtime: add yaml parsing support to wasm runtime * runtime: add yaml parsing tests
1 parent a3f3da7 commit 82080b1

File tree

15 files changed

+533
-2
lines changed

15 files changed

+533
-2
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

graph/src/runtime/gas/costs.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,10 @@ pub const JSON_FROM_BYTES: GasOp = GasOp {
8383
base_cost: DEFAULT_BASE_COST,
8484
size_mult: DEFAULT_GAS_PER_BYTE * 100,
8585
};
86+
87+
// Deeply nested YAML can take up more than 100 times the memory of the serialized format.
88+
// Multiplying the size cost by 100 accounts for this.
89+
pub const YAML_FROM_BYTES: GasOp = GasOp {
90+
base_cost: DEFAULT_BASE_COST,
91+
size_mult: DEFAULT_GAS_PER_BYTE * 100,
92+
};

graph/src/runtime/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,17 @@ pub enum IndexForAscTypeId {
371371
// Subgraph Data Source types
372372
AscEntityTrigger = 4500,
373373

374-
// Reserved discriminant space for a future blockchain type IDs: [4,500, 5,499]
374+
// Reserved discriminant space for YAML type IDs: [5,500, 6,499]
375+
YamlValue = 5500,
376+
YamlTaggedValue = 5501,
377+
YamlTypedMapEntryValueValue = 5502,
378+
YamlTypedMapValueValue = 5503,
379+
YamlArrayValue = 5504,
380+
YamlArrayTypedMapEntryValueValue = 5505,
381+
YamlWrappedValue = 5506,
382+
YamlResultValueBool = 5507,
383+
384+
// Reserved discriminant space for a future blockchain type IDs: [6,500, 7,499]
375385
//
376386
// Generated with the following shell script:
377387
//

runtime/test/src/test.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,3 +1698,71 @@ async fn test_store_ts() {
16981698
"Cannot get entity of type `Stats`. The type must be an @entity type",
16991699
);
17001700
}
1701+
1702+
async fn test_yaml_parsing(api_version: Version, gas_used: u64) {
1703+
let mut module = test_module(
1704+
"yamlParsing",
1705+
mock_data_source(
1706+
&wasm_file_path("yaml_parsing.wasm", api_version.clone()),
1707+
api_version.clone(),
1708+
),
1709+
api_version,
1710+
)
1711+
.await;
1712+
1713+
let mut test = |input: &str, expected: &str| {
1714+
let ptr: AscPtr<AscString> = module.invoke_export1("handleYaml", input.as_bytes());
1715+
let resp: String = module.asc_get(ptr).unwrap();
1716+
assert_eq!(resp, expected, "failed on input: {input}");
1717+
};
1718+
1719+
// Test invalid YAML;
1720+
test("{a: 1, - b: 2}", "error");
1721+
1722+
// Test size limit;
1723+
test(&"x".repeat(10_000_0001), "error");
1724+
1725+
// Test nulls;
1726+
test("null", "(0) null");
1727+
1728+
// Test booleans;
1729+
test("false", "(1) false");
1730+
test("true", "(1) true");
1731+
1732+
// Test numbers;
1733+
test("12345", "(2) 12345");
1734+
test("12345.6789", "(2) 12345.6789");
1735+
1736+
// Test strings;
1737+
test("aa bb cc", "(3) aa bb cc");
1738+
test("\"aa bb cc\"", "(3) aa bb cc");
1739+
1740+
// Test arrays;
1741+
test("[1, 2, 3, 4]", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]");
1742+
test("- 1\n- 2\n- 3\n- 4", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]");
1743+
1744+
// Test objects;
1745+
test("{a: 1, b: 2, c: 3}", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}");
1746+
test("a: 1\nb: 2\nc: 3", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}");
1747+
1748+
// Test tagged values;
1749+
test("!AA bb cc", "(6) !AA (3) bb cc");
1750+
1751+
// Test nesting;
1752+
test(
1753+
"aa:\n bb:\n - cc: !DD ee",
1754+
"(5) {aa: (5) {bb: (4) [(5) {cc: (6) !DD (3) ee}]}}",
1755+
);
1756+
1757+
assert_eq!(module.gas_used(), gas_used, "gas used");
1758+
}
1759+
1760+
#[tokio::test]
1761+
async fn yaml_parsing_v0_0_4() {
1762+
test_yaml_parsing(API_VERSION_0_0_4, 10462217077171).await;
1763+
}
1764+
1765+
#[tokio::test]
1766+
async fn yaml_parsing_v0_0_5() {
1767+
test_yaml_parsing(API_VERSION_0_0_5, 10462245390665).await;
1768+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import "allocator/arena";
2+
3+
import {Bytes, Result} from "../api_version_0_0_5/common/types";
4+
import {debug, YAMLValue} from "../api_version_0_0_5/common/yaml";
5+
6+
export {memory};
7+
8+
declare namespace yaml {
9+
function try_fromBytes(data: Bytes): Result<YAMLValue, boolean>;
10+
}
11+
12+
export function handleYaml(data: Bytes): string {
13+
let result = yaml.try_fromBytes(data);
14+
15+
if (result.isError) {
16+
return "error";
17+
}
18+
19+
return debug(result.value);
20+
}
Binary file not shown.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import {TypedMap} from './types';
2+
3+
export enum YAMLValueKind {
4+
NULL = 0,
5+
BOOL = 1,
6+
NUMBER = 2,
7+
STRING = 3,
8+
ARRAY = 4,
9+
OBJECT = 5,
10+
TAGGED = 6,
11+
}
12+
13+
export class YAMLValue {
14+
kind: YAMLValueKind;
15+
data: u64;
16+
17+
isBool(): boolean {
18+
return this.kind == YAMLValueKind.BOOL;
19+
}
20+
21+
isNumber(): boolean {
22+
return this.kind == YAMLValueKind.NUMBER;
23+
}
24+
25+
isString(): boolean {
26+
return this.kind == YAMLValueKind.STRING;
27+
}
28+
29+
isArray(): boolean {
30+
return this.kind == YAMLValueKind.ARRAY;
31+
}
32+
33+
isObject(): boolean {
34+
return this.kind == YAMLValueKind.OBJECT;
35+
}
36+
37+
isTagged(): boolean {
38+
return this.kind == YAMLValueKind.TAGGED;
39+
}
40+
41+
42+
toBool(): boolean {
43+
assert(this.isBool(), 'YAML value is not a boolean');
44+
return this.data != 0;
45+
}
46+
47+
toNumber(): string {
48+
assert(this.isNumber(), 'YAML value is not a number');
49+
return changetype<string>(this.data as usize);
50+
}
51+
52+
toString(): string {
53+
assert(this.isString(), 'YAML value is not a string');
54+
return changetype<string>(this.data as usize);
55+
}
56+
57+
toArray(): Array<YAMLValue> {
58+
assert(this.isArray(), 'YAML value is not an array');
59+
return changetype<Array<YAMLValue>>(this.data as usize);
60+
}
61+
62+
toObject(): TypedMap<YAMLValue, YAMLValue> {
63+
assert(this.isObject(), 'YAML value is not an object');
64+
return changetype<TypedMap<YAMLValue, YAMLValue>>(this.data as usize);
65+
}
66+
67+
toTagged(): YAMLTaggedValue {
68+
assert(this.isTagged(), 'YAML value is not tagged');
69+
return changetype<YAMLTaggedValue>(this.data as usize);
70+
}
71+
}
72+
73+
export class YAMLTaggedValue {
74+
tag: string;
75+
value: YAMLValue;
76+
}
77+
78+
79+
export function debug(value: YAMLValue): string {
80+
return "(" + value.kind.toString() + ") " + debug_value(value);
81+
}
82+
83+
function debug_value(value: YAMLValue): string {
84+
switch (value.kind) {
85+
case YAMLValueKind.NULL:
86+
return "null";
87+
case YAMLValueKind.BOOL:
88+
return value.toBool() ? "true" : "false";
89+
case YAMLValueKind.NUMBER:
90+
return value.toNumber();
91+
case YAMLValueKind.STRING:
92+
return value.toString();
93+
case YAMLValueKind.ARRAY: {
94+
let arr = value.toArray();
95+
96+
let s = "[";
97+
for (let i = 0; i < arr.length; i++) {
98+
if (i > 0) {
99+
s += ", ";
100+
}
101+
s += debug(arr[i]);
102+
}
103+
s += "]";
104+
105+
return s;
106+
}
107+
case YAMLValueKind.OBJECT: {
108+
let arr = value.toObject().entries.sort((a, b) => {
109+
if (a.key.toString() < b.key.toString()) {
110+
return -1;
111+
}
112+
113+
if (a.key.toString() > b.key.toString()) {
114+
return 1;
115+
}
116+
117+
return 0;
118+
});
119+
120+
let s = "{";
121+
for (let i = 0; i < arr.length; i++) {
122+
if (i > 0) {
123+
s += ", ";
124+
}
125+
s += debug_value(arr[i].key) + ": " + debug(arr[i].value);
126+
}
127+
s += "}";
128+
129+
return s;
130+
}
131+
case YAMLValueKind.TAGGED: {
132+
let tagged = value.toTagged();
133+
134+
return tagged.tag + " " + debug(tagged.value);
135+
}
136+
default:
137+
return "undefined";
138+
}
139+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {debug, YAMLValue, YAMLTaggedValue} from './common/yaml';
2+
import {Bytes, Result, TypedMap, TypedMapEntry, Wrapped} from './common/types';
3+
4+
enum TypeId {
5+
STRING = 0,
6+
UINT8_ARRAY = 6,
7+
8+
YamlValue = 5500,
9+
YamlTaggedValue = 5501,
10+
YamlTypedMapEntryValueValue = 5502,
11+
YamlTypedMapValueValue = 5503,
12+
YamlArrayValue = 5504,
13+
YamlArrayTypedMapEntryValueValue = 5505,
14+
YamlWrappedValue = 5506,
15+
YamlResultValueBool = 5507,
16+
}
17+
18+
export function id_of_type(type_id_index: TypeId): usize {
19+
switch (type_id_index) {
20+
case TypeId.STRING:
21+
return idof<string>();
22+
case TypeId.UINT8_ARRAY:
23+
return idof<Uint8Array>();
24+
25+
case TypeId.YamlValue:
26+
return idof<YAMLValue>();
27+
case TypeId.YamlTaggedValue:
28+
return idof<YAMLTaggedValue>();
29+
case TypeId.YamlTypedMapEntryValueValue:
30+
return idof<TypedMapEntry<YAMLValue, YAMLValue>>();
31+
case TypeId.YamlTypedMapValueValue:
32+
return idof<TypedMap<YAMLValue, YAMLValue>>();
33+
case TypeId.YamlArrayValue:
34+
return idof<Array<YAMLValue>>();
35+
case TypeId.YamlArrayTypedMapEntryValueValue:
36+
return idof<Array<TypedMapEntry<YAMLValue, YAMLValue>>>();
37+
case TypeId.YamlWrappedValue:
38+
return idof<Wrapped<YAMLValue>>();
39+
case TypeId.YamlResultValueBool:
40+
return idof<Result<YAMLValue, boolean>>();
41+
default:
42+
return 0;
43+
}
44+
}
45+
46+
export function allocate(n: usize): usize {
47+
return __alloc(n);
48+
}
49+
50+
declare namespace yaml {
51+
function try_fromBytes(data: Bytes): Result<YAMLValue, boolean>;
52+
}
53+
54+
export function handleYaml(data: Bytes): string {
55+
let result = yaml.try_fromBytes(data);
56+
57+
if (result.isError) {
58+
return "error";
59+
}
60+
61+
return debug(result.value);
62+
}
Binary file not shown.

runtime/wasm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ wasm-instrument = { version = "0.2.0", features = ["std", "sign_ext"] }
2020

2121
# AssemblyScript uses sign extensions
2222
parity-wasm = { version = "0.45", features = ["std", "sign_ext"] }
23+
24+
serde_yaml = { workspace = true }

0 commit comments

Comments
 (0)