Skip to content

Commit 0c36a02

Browse files
isumYaroShkvorets
andauthored
Add YAML parsing support to mappings (#1935)
* feat: add yaml parsing support to mappings * add test * fix test runner --------- Co-authored-by: YaroShkvorets <[email protected]>
1 parent e4af888 commit 0c36a02

File tree

6 files changed

+550
-4
lines changed

6 files changed

+550
-4
lines changed

.changeset/rare-carpets-tell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphprotocol/graph-ts': minor
3+
---
4+
5+
feat: add yaml parsing support to mappings

packages/ts/common/yaml.ts

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
import { Bytes, Result, TypedMap } from './collections';
2+
import { BigInt } from './numbers';
3+
4+
/**
5+
* Host YAML interface.
6+
*/
7+
export declare namespace yaml {
8+
/**
9+
* Parses a YAML document from UTF-8 encoded bytes.
10+
* Aborts mapping execution if the bytes cannot be parsed.
11+
*/
12+
function fromBytes(data: Bytes): YAMLValue;
13+
14+
/**
15+
* Parses a YAML document from UTF-8 encoded bytes.
16+
* Returns `Result.error == true` if the bytes cannot be parsed.
17+
*/
18+
function try_fromBytes(data: Bytes): Result<YAMLValue, bool>;
19+
}
20+
21+
export namespace yaml {
22+
/**
23+
* Parses a YAML document from a UTF-8 encoded string.
24+
* Aborts mapping execution if the string cannot be parsed.
25+
*/
26+
export function fromString(data: string): YAMLValue {
27+
const bytes = Bytes.fromUTF8(data);
28+
29+
return yaml.fromBytes(bytes);
30+
}
31+
32+
/**
33+
* Parses a YAML document from a UTF-8 encoded string.
34+
* Returns `Result.error == true` if the string cannot be parsed.
35+
*/
36+
export function try_fromString(data: string): Result<YAMLValue, bool> {
37+
const bytes = Bytes.fromUTF8(data);
38+
39+
return yaml.try_fromBytes(bytes);
40+
}
41+
}
42+
43+
/**
44+
* All possible YAML value types.
45+
*/
46+
export enum YAMLValueKind {
47+
NULL = 0,
48+
BOOL = 1,
49+
NUMBER = 2,
50+
STRING = 3,
51+
ARRAY = 4,
52+
OBJECT = 5,
53+
TAGGED = 6,
54+
}
55+
56+
/**
57+
* Pointer type for `YAMLValue` data.
58+
*
59+
* Big enough to fit any pointer or native `YAMLValue.data`.
60+
*/
61+
export type YAMLValuePayload = u64;
62+
63+
export class YAMLValue {
64+
kind: YAMLValueKind;
65+
data: YAMLValuePayload;
66+
67+
constructor(kind: YAMLValueKind, data: YAMLValuePayload) {
68+
this.kind = kind;
69+
this.data = data;
70+
}
71+
72+
static newNull(): YAMLValue {
73+
return new YAMLValue(YAMLValueKind.NULL, 0);
74+
}
75+
76+
static newBool(data: bool): YAMLValue {
77+
return new YAMLValue(YAMLValueKind.BOOL, data ? 1 : 0);
78+
}
79+
80+
static newI64(data: i64): YAMLValue {
81+
return new YAMLValue(YAMLValueKind.NUMBER, changetype<usize>(data.toString()));
82+
}
83+
84+
static newU64(data: u64): YAMLValue {
85+
return new YAMLValue(YAMLValueKind.NUMBER, changetype<usize>(data.toString()));
86+
}
87+
88+
static newF64(data: f64): YAMLValue {
89+
return new YAMLValue(YAMLValueKind.NUMBER, changetype<usize>(data.toString()));
90+
}
91+
92+
static newBigInt(data: BigInt): YAMLValue {
93+
return new YAMLValue(YAMLValueKind.STRING, changetype<usize>(data.toString()));
94+
}
95+
96+
static newString(data: string): YAMLValue {
97+
return new YAMLValue(YAMLValueKind.STRING, changetype<usize>(data));
98+
}
99+
100+
static newArray(data: Array<YAMLValue>): YAMLValue {
101+
return new YAMLValue(YAMLValueKind.ARRAY, changetype<usize>(data));
102+
}
103+
104+
static newObject(data: TypedMap<YAMLValue, YAMLValue>): YAMLValue {
105+
return new YAMLValue(YAMLValueKind.OBJECT, changetype<usize>(data));
106+
}
107+
108+
static newTagged(tag: string, value: YAMLValue): YAMLValue {
109+
const tagged = new YAMLTaggedValue(tag, value);
110+
return new YAMLValue(YAMLValueKind.TAGGED, changetype<usize>(tagged));
111+
}
112+
113+
isNull(): bool {
114+
return this.kind == YAMLValueKind.NULL;
115+
}
116+
117+
isBool(): bool {
118+
return this.kind == YAMLValueKind.BOOL;
119+
}
120+
121+
isNumber(): bool {
122+
return this.kind == YAMLValueKind.NUMBER;
123+
}
124+
125+
isString(): bool {
126+
return this.kind == YAMLValueKind.STRING;
127+
}
128+
129+
isArray(): bool {
130+
return this.kind == YAMLValueKind.ARRAY;
131+
}
132+
133+
isObject(): bool {
134+
return this.kind == YAMLValueKind.OBJECT;
135+
}
136+
137+
isTagged(): bool {
138+
return this.kind == YAMLValueKind.TAGGED;
139+
}
140+
141+
toBool(): bool {
142+
assert(this.isBool(), 'YAML value is not a boolean');
143+
return this.data != 0;
144+
}
145+
146+
toNumber(): string {
147+
assert(this.isNumber(), 'YAML value is not a number');
148+
return changetype<string>(this.data as usize);
149+
}
150+
151+
toI64(): i64 {
152+
return I64.parseInt(this.toNumber());
153+
}
154+
155+
toU64(): u64 {
156+
return U64.parseInt(this.toNumber());
157+
}
158+
159+
toF64(): f64 {
160+
return F64.parseFloat(this.toNumber());
161+
}
162+
163+
toBigInt(): BigInt {
164+
assert(this.isNumber() || this.isString(), 'YAML value is not numeric');
165+
return BigInt.fromString(changetype<string>(this.data as usize));
166+
}
167+
168+
toString(): string {
169+
assert(this.isString(), 'YAML value is not a string');
170+
return changetype<string>(this.data as usize);
171+
}
172+
173+
toArray(): Array<YAMLValue> {
174+
assert(this.isArray(), 'YAML value is not an array');
175+
return changetype<Array<YAMLValue>>(this.data as usize);
176+
}
177+
178+
toObject(): TypedMap<YAMLValue, YAMLValue> {
179+
assert(this.isObject(), 'YAML value is not an object');
180+
return changetype<TypedMap<YAMLValue, YAMLValue>>(this.data as usize);
181+
}
182+
183+
toTagged(): YAMLTaggedValue {
184+
assert(this.isTagged(), 'YAML value is not tagged');
185+
return changetype<YAMLTaggedValue>(this.data as usize);
186+
}
187+
188+
// Allows access to YAML values from within an object.
189+
@operator('==')
190+
static eq(a: YAMLValue, b: YAMLValue): bool {
191+
if (a.isBool() && b.isBool()) {
192+
return a.toBool() == b.toBool();
193+
}
194+
195+
if (a.isNumber() && b.isNumber()) {
196+
return a.toNumber() == b.toNumber();
197+
}
198+
199+
if (a.isString() && b.isString()) {
200+
return a.toString() == b.toString();
201+
}
202+
203+
if (a.isArray() && b.isArray()) {
204+
const arrA = a.toArray();
205+
const arrB = b.toArray();
206+
207+
if (arrA.length == arrB.length) {
208+
for (let i = 0; i < arrA.length; i++) {
209+
if (arrA[i] != arrB[i]) {
210+
return false;
211+
}
212+
}
213+
214+
return true;
215+
}
216+
217+
return false;
218+
}
219+
220+
if (a.isObject() && b.isObject()) {
221+
const objA = a.toObject();
222+
const objB = b.toObject();
223+
224+
if (objA.entries.length == objB.entries.length) {
225+
for (let i = 0; i < objA.entries.length; i++) {
226+
const valB = objB.get(objA.entries[i].key);
227+
228+
if (!valB || objA.entries[i].value != valB) {
229+
return false;
230+
}
231+
}
232+
233+
return true;
234+
}
235+
236+
return false;
237+
}
238+
239+
if (a.isTagged() && b.isTagged()) {
240+
return a.toTagged() == b.toTagged();
241+
}
242+
243+
return false;
244+
}
245+
246+
// Allows access to YAML values from within an object.
247+
@operator('!=')
248+
static ne(a: YAMLValue | null, b: YAMLValue | null): bool {
249+
if (!a || !b) {
250+
return true;
251+
}
252+
253+
return !(a! == b!);
254+
}
255+
256+
// Makes it easier to access a specific index in a YAML array or a string key in a YAML object.
257+
//
258+
// Examples:
259+
// Usage in YAML objects: `yaml.fromString(subgraphManifest)['specVersion']`;
260+
// Nesting is also supported: `yaml.fromString(subgraphManifest)['schema']['file']`;
261+
// Usage in YAML arrays: `yaml.fromString(subgraphManifest)['dataSources']['0']`;
262+
// YAML arrays and objects: `yaml.fromString(subgraphManifest)['dataSources']['0']['source']['address']`;
263+
@operator('[]')
264+
get(index: string): YAMLValue {
265+
assert(this.isArray() || this.isObject(), 'YAML value can not be accessed by index');
266+
267+
if (this.isArray()) {
268+
return this.toArray()[I32.parseInt(index)];
269+
}
270+
271+
return this.toObject().mustGet(YAMLValue.newString(index));
272+
}
273+
}
274+
275+
export class YAMLTaggedValue {
276+
tag: string;
277+
value: YAMLValue;
278+
279+
constructor(tag: string, value: YAMLValue) {
280+
this.tag = tag;
281+
this.value = value;
282+
}
283+
284+
// Allows access to YAML values from within an object.
285+
@operator('==')
286+
static eq(a: YAMLTaggedValue, b: YAMLTaggedValue): bool {
287+
return a.tag == b.tag && a.value == b.value;
288+
}
289+
290+
// Allows access to YAML values from within an object.
291+
@operator('!=')
292+
static ne(a: YAMLTaggedValue | null, b: YAMLTaggedValue | null): bool {
293+
if (!a || !b) {
294+
return true;
295+
}
296+
297+
return !(a! == b!);
298+
}
299+
}

packages/ts/global/global.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { starknet } from '../chain/starknet';
66
import { Bytes, Entity, Result, TypedMap, TypedMapEntry, Wrapped } from '../common/collections';
77
import { BigDecimal } from '../common/numbers';
88
import { JSONValue, Value } from '../common/value';
9+
import { YAMLTaggedValue, YAMLValue } from '../common/yaml';
910

1011
/**
1112
* Contains type IDs and their discriminants for every blockchain supported by Graph-Node.
@@ -247,7 +248,17 @@ export enum TypeId {
247248
```
248249
*/
249250

250-
// Reserved discriminant space for a future blockchain type IDs: [4,500, 5,499]
251+
// Reserved discriminant space for YAML type IDs: [5,500, 6,499]
252+
YamlValue = 5500,
253+
YamlTaggedValue = 5501,
254+
YamlTypedMapEntryValueValue = 5502,
255+
YamlTypedMapValueValue = 5503,
256+
YamlArrayValue = 5504,
257+
YamlArrayTypedMapEntryValueValue = 5505,
258+
YamlWrappedValue = 5506,
259+
YamlResultValueBool = 5507,
260+
261+
// Reserved discriminant space for a future blockchain type IDs: [6,500, 7,499]
251262
}
252263

253264
export function id_of_type(typeId: TypeId): usize {
@@ -593,6 +604,25 @@ export function id_of_type(typeId: TypeId): usize {
593604
return idof<starknet.Event>();
594605
case TypeId.StarknetArrayBytes:
595606
return idof<Array<Bytes>>();
607+
/**
608+
* YAML type IDs.
609+
*/
610+
case TypeId.YamlValue:
611+
return idof<YAMLValue>();
612+
case TypeId.YamlTaggedValue:
613+
return idof<YAMLTaggedValue>();
614+
case TypeId.YamlTypedMapEntryValueValue:
615+
return idof<TypedMapEntry<YAMLValue, YAMLValue>>();
616+
case TypeId.YamlTypedMapValueValue:
617+
return idof<TypedMap<YAMLValue, YAMLValue>>();
618+
case TypeId.YamlArrayValue:
619+
return idof<Array<YAMLValue>>();
620+
case TypeId.YamlArrayTypedMapEntryValueValue:
621+
return idof<Array<TypedMapEntry<YAMLValue, YAMLValue>>>();
622+
case TypeId.YamlWrappedValue:
623+
return idof<Wrapped<YAMLValue>>();
624+
case TypeId.YamlResultValueBool:
625+
return idof<Result<YAMLValue, boolean>>();
596626
default:
597627
return 0;
598628
}

packages/ts/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export * from './common/datasource';
1919
export * from './common/json';
2020
export * from './common/numbers';
2121
export * from './common/value';
22+
export * from './common/yaml';
2223

2324
/**
2425
* Host store interface.

0 commit comments

Comments
 (0)