Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YAML parsing support to mappings #1935

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rare-carpets-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphprotocol/graph-ts': minor
---

feat: add yaml parsing support to mappings
299 changes: 299 additions & 0 deletions packages/ts/common/yaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import { Bytes, Result, TypedMap } from './collections';
import { BigInt } from './numbers';

/**
* Host YAML interface.
*/
export declare namespace yaml {
/**
* Parses a YAML document from UTF-8 encoded bytes.
* Aborts mapping execution if the bytes cannot be parsed.
*/
function fromBytes(data: Bytes): YAMLValue;

/**
* Parses a YAML document from UTF-8 encoded bytes.
* Returns `Result.error == true` if the bytes cannot be parsed.
*/
function try_fromBytes(data: Bytes): Result<YAMLValue, bool>;
}

export namespace yaml {
/**
* Parses a YAML document from a UTF-8 encoded string.
* Aborts mapping execution if the string cannot be parsed.
*/
export function fromString(data: string): YAMLValue {
const bytes = Bytes.fromUTF8(data);

return yaml.fromBytes(bytes);
}

/**
* Parses a YAML document from a UTF-8 encoded string.
* Returns `Result.error == true` if the string cannot be parsed.
*/
export function try_fromString(data: string): Result<YAMLValue, bool> {
const bytes = Bytes.fromUTF8(data);

return yaml.try_fromBytes(bytes);
}
}

/**
* All possible YAML value types.
*/
export enum YAMLValueKind {
NULL = 0,
BOOL = 1,
NUMBER = 2,
STRING = 3,
ARRAY = 4,
OBJECT = 5,
TAGGED = 6,
}

/**
* Pointer type for `YAMLValue` data.
*
* Big enough to fit any pointer or native `YAMLValue.data`.
*/
export type YAMLValuePayload = u64;

export class YAMLValue {
kind: YAMLValueKind;
data: YAMLValuePayload;

constructor(kind: YAMLValueKind, data: YAMLValuePayload) {
this.kind = kind;
this.data = data;
}

static newNull(): YAMLValue {
return new YAMLValue(YAMLValueKind.NULL, 0);
}

static newBool(data: bool): YAMLValue {
return new YAMLValue(YAMLValueKind.BOOL, data ? 1 : 0);
}

static newI64(data: i64): YAMLValue {
return new YAMLValue(YAMLValueKind.NUMBER, changetype<usize>(data.toString()));
}

static newU64(data: u64): YAMLValue {
return new YAMLValue(YAMLValueKind.NUMBER, changetype<usize>(data.toString()));
}

static newF64(data: f64): YAMLValue {
return new YAMLValue(YAMLValueKind.NUMBER, changetype<usize>(data.toString()));
}

static newBigInt(data: BigInt): YAMLValue {
return new YAMLValue(YAMLValueKind.STRING, changetype<usize>(data.toString()));
}

static newString(data: string): YAMLValue {
return new YAMLValue(YAMLValueKind.STRING, changetype<usize>(data));
}

static newArray(data: Array<YAMLValue>): YAMLValue {
return new YAMLValue(YAMLValueKind.ARRAY, changetype<usize>(data));
}

static newObject(data: TypedMap<YAMLValue, YAMLValue>): YAMLValue {
return new YAMLValue(YAMLValueKind.OBJECT, changetype<usize>(data));
}

static newTagged(tag: string, value: YAMLValue): YAMLValue {
const tagged = new YAMLTaggedValue(tag, value);
return new YAMLValue(YAMLValueKind.TAGGED, changetype<usize>(tagged));
}

isNull(): bool {
return this.kind == YAMLValueKind.NULL;
}

isBool(): bool {
return this.kind == YAMLValueKind.BOOL;
}

isNumber(): bool {
return this.kind == YAMLValueKind.NUMBER;
}

isString(): bool {
return this.kind == YAMLValueKind.STRING;
}

isArray(): bool {
return this.kind == YAMLValueKind.ARRAY;
}

isObject(): bool {
return this.kind == YAMLValueKind.OBJECT;
}

isTagged(): bool {
return this.kind == YAMLValueKind.TAGGED;
}

toBool(): bool {
assert(this.isBool(), 'YAML value is not a boolean');
return this.data != 0;
}

toNumber(): string {
assert(this.isNumber(), 'YAML value is not a number');
return changetype<string>(this.data as usize);
}

toI64(): i64 {
return I64.parseInt(this.toNumber());
}

toU64(): u64 {
return U64.parseInt(this.toNumber());
}

toF64(): f64 {
return F64.parseFloat(this.toNumber());
}

toBigInt(): BigInt {
assert(this.isNumber() || this.isString(), 'YAML value is not numeric');
return BigInt.fromString(changetype<string>(this.data as usize));
}

toString(): string {
assert(this.isString(), 'YAML value is not a string');
return changetype<string>(this.data as usize);
}

toArray(): Array<YAMLValue> {
assert(this.isArray(), 'YAML value is not an array');
return changetype<Array<YAMLValue>>(this.data as usize);
}

toObject(): TypedMap<YAMLValue, YAMLValue> {
assert(this.isObject(), 'YAML value is not an object');
return changetype<TypedMap<YAMLValue, YAMLValue>>(this.data as usize);
}

toTagged(): YAMLTaggedValue {
assert(this.isTagged(), 'YAML value is not tagged');
return changetype<YAMLTaggedValue>(this.data as usize);
}

// Allows access to YAML values from within an object.
@operator('==')
static eq(a: YAMLValue, b: YAMLValue): bool {
if (a.isBool() && b.isBool()) {
return a.toBool() == b.toBool();
}

if (a.isNumber() && b.isNumber()) {
return a.toNumber() == b.toNumber();
}

if (a.isString() && b.isString()) {
return a.toString() == b.toString();
}

if (a.isArray() && b.isArray()) {
const arrA = a.toArray();
const arrB = b.toArray();

if (arrA.length == arrB.length) {
for (let i = 0; i < arrA.length; i++) {
if (arrA[i] != arrB[i]) {
return false;
}
}

return true;
}

return false;
}

if (a.isObject() && b.isObject()) {
const objA = a.toObject();
const objB = b.toObject();

if (objA.entries.length == objB.entries.length) {
for (let i = 0; i < objA.entries.length; i++) {
const valB = objB.get(objA.entries[i].key);

if (!valB || objA.entries[i].value != valB) {
return false;
}
}

return true;
}

return false;
}

if (a.isTagged() && b.isTagged()) {
return a.toTagged() == b.toTagged();
}

return false;
}

// Allows access to YAML values from within an object.
@operator('!=')
static ne(a: YAMLValue | null, b: YAMLValue | null): bool {
if (!a || !b) {
return true;
}

return !(a! == b!);
}

// Makes it easier to access a specific index in a YAML array or a string key in a YAML object.
//
// Examples:
// Usage in YAML objects: `yaml.fromString(subgraphManifest)['specVersion']`;
// Nesting is also supported: `yaml.fromString(subgraphManifest)['schema']['file']`;
// Usage in YAML arrays: `yaml.fromString(subgraphManifest)['dataSources']['0']`;
// YAML arrays and objects: `yaml.fromString(subgraphManifest)['dataSources']['0']['source']['address']`;
@operator('[]')
get(index: string): YAMLValue {
assert(this.isArray() || this.isObject(), 'YAML value can not be accessed by index');

if (this.isArray()) {
return this.toArray()[I32.parseInt(index)];
}

return this.toObject().mustGet(YAMLValue.newString(index));
}
}

export class YAMLTaggedValue {
tag: string;
value: YAMLValue;

constructor(tag: string, value: YAMLValue) {
this.tag = tag;
this.value = value;
}

// Allows access to YAML values from within an object.
@operator('==')
static eq(a: YAMLTaggedValue, b: YAMLTaggedValue): bool {
return a.tag == b.tag && a.value == b.value;
}

// Allows access to YAML values from within an object.
@operator('!=')
static ne(a: YAMLTaggedValue | null, b: YAMLTaggedValue | null): bool {
if (!a || !b) {
return true;
}

return !(a! == b!);
}
}
32 changes: 31 additions & 1 deletion packages/ts/global/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { starknet } from '../chain/starknet';
import { Bytes, Entity, Result, TypedMap, TypedMapEntry, Wrapped } from '../common/collections';
import { BigDecimal } from '../common/numbers';
import { JSONValue, Value } from '../common/value';
import { YAMLTaggedValue, YAMLValue } from '../common/yaml';

/**
* Contains type IDs and their discriminants for every blockchain supported by Graph-Node.
Expand Down Expand Up @@ -247,7 +248,17 @@ export enum TypeId {
```
*/

// Reserved discriminant space for a future blockchain type IDs: [4,500, 5,499]
// Reserved discriminant space for YAML type IDs: [5,500, 6,499]
YamlValue = 5500,
YamlTaggedValue = 5501,
YamlTypedMapEntryValueValue = 5502,
YamlTypedMapValueValue = 5503,
YamlArrayValue = 5504,
YamlArrayTypedMapEntryValueValue = 5505,
YamlWrappedValue = 5506,
YamlResultValueBool = 5507,

// Reserved discriminant space for a future blockchain type IDs: [6,500, 7,499]
}

export function id_of_type(typeId: TypeId): usize {
Expand Down Expand Up @@ -593,6 +604,25 @@ export function id_of_type(typeId: TypeId): usize {
return idof<starknet.Event>();
case TypeId.StarknetArrayBytes:
return idof<Array<Bytes>>();
/**
* YAML type IDs.
*/
case TypeId.YamlValue:
return idof<YAMLValue>();
case TypeId.YamlTaggedValue:
return idof<YAMLTaggedValue>();
case TypeId.YamlTypedMapEntryValueValue:
return idof<TypedMapEntry<YAMLValue, YAMLValue>>();
case TypeId.YamlTypedMapValueValue:
return idof<TypedMap<YAMLValue, YAMLValue>>();
case TypeId.YamlArrayValue:
return idof<Array<YAMLValue>>();
case TypeId.YamlArrayTypedMapEntryValueValue:
return idof<Array<TypedMapEntry<YAMLValue, YAMLValue>>>();
case TypeId.YamlWrappedValue:
return idof<Wrapped<YAMLValue>>();
case TypeId.YamlResultValueBool:
return idof<Result<YAMLValue, boolean>>();
default:
return 0;
}
Expand Down
1 change: 1 addition & 0 deletions packages/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './common/datasource';
export * from './common/json';
export * from './common/numbers';
export * from './common/value';
export * from './common/yaml';

/**
* Host store interface.
Expand Down
Loading