From 59f799fdd152f42b3fb31fd6ec175e35cb928eb6 Mon Sep 17 00:00:00 2001 From: Ion Suman <47307091+isum@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:32:32 +0200 Subject: [PATCH] feat: add yaml parsing support to mappings --- .changeset/rare-carpets-tell.md | 5 + packages/ts/common/yaml.ts | 243 ++++++++++++++++++++++++++++++++ packages/ts/global/global.ts | 32 ++++- packages/ts/index.ts | 1 + 4 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 .changeset/rare-carpets-tell.md create mode 100644 packages/ts/common/yaml.ts diff --git a/.changeset/rare-carpets-tell.md b/.changeset/rare-carpets-tell.md new file mode 100644 index 000000000..de17dff24 --- /dev/null +++ b/.changeset/rare-carpets-tell.md @@ -0,0 +1,5 @@ +--- +'@graphprotocol/graph-ts': minor +--- + +add yaml support in mappings diff --git a/packages/ts/common/yaml.ts b/packages/ts/common/yaml.ts new file mode 100644 index 000000000..800648aae --- /dev/null +++ b/packages/ts/common/yaml.ts @@ -0,0 +1,243 @@ +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; +} + +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 { + 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(data.toString())); + } + + static newU64(data: u64): YAMLValue { + return new YAMLValue(YAMLValueKind.NUMBER, changetype(data.toString())); + } + + static newF64(data: f64): YAMLValue { + return new YAMLValue(YAMLValueKind.NUMBER, changetype(data.toString())); + } + + static newBigInt(data: BigInt): YAMLValue { + return new YAMLValue(YAMLValueKind.STRING, changetype(data.toString())); + } + + static newString(data: string): YAMLValue { + return new YAMLValue(YAMLValueKind.STRING, changetype(data)); + } + + static newArray(data: Array): YAMLValue { + return new YAMLValue(YAMLValueKind.ARRAY, changetype(data)); + } + + static newObject(data: TypedMap): YAMLValue { + return new YAMLValue(YAMLValueKind.OBJECT, changetype(data)); + } + + static newTagged(tag: string, value: YAMLValue): YAMLValue { + let tagged = new YAMLTaggedValue(tag, value); + return new YAMLValue(YAMLValueKind.TAGGED, changetype(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(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(this.data as usize)); + } + + toString(): string { + assert(this.isString(), 'YAML value is not a string'); + return changetype(this.data as usize); + } + + toArray(): Array { + assert(this.isArray(), 'YAML value is not an array'); + return changetype>(this.data as usize); + } + + toObject(): TypedMap { + assert(this.isObject(), 'YAML value is not an object'); + return changetype>(this.data as usize); + } + + toTagged(): YAMLTaggedValue { + assert(this.isTagged(), 'YAML value is not tagged'); + return changetype(this.data as usize); + } + + // Allows access to YAML values from within an object. + @operator('==') + static __equals(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.isTagged() && b.isTagged()) { + return a.toTagged() == b.toTagged(); + } + + return false; + } + + // 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 __equals(a: YAMLTaggedValue, b: YAMLTaggedValue): bool { + return a.tag == b.tag && a.value == b.value; + } +} diff --git a/packages/ts/global/global.ts b/packages/ts/global/global.ts index ec7e4bad3..48a081fb1 100644 --- a/packages/ts/global/global.ts +++ b/packages/ts/global/global.ts @@ -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 { YAMLValue, YAMLTaggedValue } from '../common/yaml'; /** * Contains type IDs and their discriminants for every blockchain supported by Graph-Node. @@ -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: [4,500, 5,499] + YamlValue = 4500, + YamlTaggedValue = 4501, + YamlTypedMapEntryValueValue = 4502, + YamlTypedMapValueValue = 4503, + YamlArrayValue = 4504, + YamlArrayTypedMapEntryValueValue = 4505, + YamlWrappedValue = 4506, + YamlResultValueBool = 4507, + + // Reserved discriminant space for a future blockchain type IDs: [5,500, 6,499] } export function id_of_type(typeId: TypeId): usize { @@ -593,6 +604,25 @@ export function id_of_type(typeId: TypeId): usize { return idof(); case TypeId.StarknetArrayBytes: return idof>(); + /** + * YAML type IDs. + */ + case TypeId.YamlValue: + return idof(); + case TypeId.YamlTaggedValue: + return idof(); + case TypeId.YamlTypedMapEntryValueValue: + return idof>(); + case TypeId.YamlTypedMapValueValue: + return idof>(); + case TypeId.YamlArrayValue: + return idof>(); + case TypeId.YamlArrayTypedMapEntryValueValue: + return idof>>(); + case TypeId.YamlWrappedValue: + return idof>(); + case TypeId.YamlResultValueBool: + return idof>(); default: return 0; } diff --git a/packages/ts/index.ts b/packages/ts/index.ts index de81fefee..d0ac26249 100644 --- a/packages/ts/index.ts +++ b/packages/ts/index.ts @@ -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.