Skip to content

Commit 0c36a02

Browse files
isumYaroShkvorets
andauthoredFeb 11, 2025··
Add YAML parsing support to mappings (#1935)
* feat: add yaml parsing support to mappings * add test * fix test runner --------- Co-authored-by: YaroShkvorets <shkvorets@gmail.com>
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.

‎packages/ts/test/test.mjs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// TODO: disabling eslint for now
22
// We need to re-do this and use TS instead of JS
33
import fs from 'fs';
4-
import path from 'path';
4+
import path from 'node:path';
5+
import { fileURLToPath } from 'node:url';
56
import { StringDecoder } from 'string_decoder';
67
import asc from 'assemblyscript/asc';
78

@@ -30,6 +31,7 @@ async function main() {
3031
fs.copyFileSync('common/json.ts', 'test/temp_lib/common/json.ts');
3132
fs.copyFileSync('common/numbers.ts', 'test/temp_lib/common/numbers.ts');
3233
fs.copyFileSync('common/value.ts', 'test/temp_lib/common/value.ts');
34+
fs.copyFileSync('common/yaml.ts', 'test/temp_lib/common/yaml.ts');
3335
fs.copyFileSync('chain/arweave.ts', 'test/temp_lib/chain/arweave.ts');
3436
fs.copyFileSync('chain/ethereum.ts', 'test/temp_lib/chain/ethereum.ts');
3537
fs.copyFileSync('chain/near.ts', 'test/temp_lib/chain/near.ts');
@@ -40,7 +42,7 @@ async function main() {
4042
try {
4143
const outputWasmPath = 'test/temp_out/test.wasm';
4244

43-
for (const file of ['test/bigInt.ts', 'test/bytes.ts', 'test/entity.ts'])
45+
for (const file of ['test/bigInt.ts', 'test/bytes.ts', 'test/entity.ts', 'test/yaml.ts'])
4446
await testFile(file, outputWasmPath);
4547
} catch (e) {
4648
console.error(e);
@@ -51,6 +53,7 @@ async function main() {
5153
fs.unlinkSync('test/temp_lib/common/json.ts');
5254
fs.unlinkSync('test/temp_lib/common/numbers.ts');
5355
fs.unlinkSync('test/temp_lib/common/value.ts');
56+
fs.unlinkSync('test/temp_lib/common/yaml.ts');
5457
fs.rmdirSync('test/temp_lib/common');
5558
fs.unlinkSync('test/temp_lib/chain/arweave.ts');
5659
fs.unlinkSync('test/temp_lib/chain/ethereum.ts');
@@ -87,7 +90,8 @@ async function testFile(sourceFile, outputWasmPath) {
8790
env: {
8891
memory,
8992
abort(messagePtr, fileNamePtr, lineNumber, columnNumber) {
90-
const fileSource = path.join(__dirname, '..', sourceFile);
93+
const __filename = fileURLToPath(import.meta.url);
94+
const fileSource = path.join(path.dirname(__filename), '..', sourceFile);
9195
let message = 'assertion failure';
9296
if (messagePtr !== 0) {
9397
message += `: ${getString(memory, messagePtr)}`;

‎packages/ts/test/yaml.ts

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import { TypedMap, YAMLValue } from './temp_lib/index';
2+
3+
export function testYAMLMethods(): void {
4+
let val: YAMLValue;
5+
6+
val = YAMLValue.newNull();
7+
assert(val.isNull());
8+
9+
val = YAMLValue.newBool(true);
10+
assert(val.isBool());
11+
assert(val.toBool() == true);
12+
13+
val = YAMLValue.newI64(1);
14+
assert(val.isNumber());
15+
assert(val.toNumber() == '1');
16+
assert(val.toI64() == 1);
17+
18+
val = YAMLValue.newU64(1);
19+
assert(val.isNumber());
20+
assert(val.toNumber() == '1');
21+
assert(val.toU64() == 1);
22+
23+
val = YAMLValue.newF64(1.2);
24+
assert(val.isNumber());
25+
assert(val.toNumber() == '1.2');
26+
assert(val.toF64() == 1.2);
27+
28+
val = YAMLValue.newString('a');
29+
assert(val.isString());
30+
assert(val.toString() == 'a');
31+
32+
val = YAMLValue.newArray([YAMLValue.newU64(1)]);
33+
assert(val.isArray());
34+
assert(val.toArray().length == 1);
35+
assert(val.toArray()[0].toNumber() == '1');
36+
37+
let obj: TypedMap<YAMLValue, YAMLValue> = new TypedMap();
38+
obj.set(YAMLValue.newString('a'), YAMLValue.newString('b'));
39+
40+
val = YAMLValue.newObject(obj);
41+
assert(val.isObject());
42+
assert(val.toObject().entries.length == 1);
43+
assert(val.toObject().mustGet(YAMLValue.newString('a')).toString() == 'b');
44+
45+
val = YAMLValue.newTagged('a', YAMLValue.newString('b'));
46+
assert(val.isTagged());
47+
assert((val.toTagged().tag = 'a'));
48+
assert(val.toTagged().value.toString() == 'b');
49+
}
50+
51+
export function testYAMLEqNeOverloads(): void {
52+
assert(YAMLValue.newBool(true) == YAMLValue.newBool(true));
53+
assert(!(YAMLValue.newBool(true) != YAMLValue.newBool(true)));
54+
assert(YAMLValue.newBool(true) != YAMLValue.newBool(false));
55+
56+
assert(YAMLValue.newU64(1) == YAMLValue.newU64(1));
57+
assert(!(YAMLValue.newU64(1) != YAMLValue.newU64(1)));
58+
assert(YAMLValue.newU64(1) != YAMLValue.newU64(2));
59+
60+
assert(YAMLValue.newString('a') == YAMLValue.newString('a'));
61+
assert(!(YAMLValue.newString('a') != YAMLValue.newString('a')));
62+
assert(YAMLValue.newString('a') != YAMLValue.newString('b'));
63+
64+
assert(YAMLValue.newArray([YAMLValue.newU64(1)]) == YAMLValue.newArray([YAMLValue.newU64(1)]));
65+
assert(!(YAMLValue.newArray([YAMLValue.newU64(1)]) != YAMLValue.newArray([YAMLValue.newU64(1)])));
66+
assert(
67+
YAMLValue.newArray([YAMLValue.newU64(1)]) !=
68+
YAMLValue.newArray([YAMLValue.newU64(1), YAMLValue.newU64(2)]),
69+
);
70+
assert(YAMLValue.newArray([YAMLValue.newU64(1)]) != YAMLValue.newArray([YAMLValue.newU64(2)]));
71+
72+
const objA: TypedMap<YAMLValue, YAMLValue> = new TypedMap();
73+
objA.set(YAMLValue.newString('a'), YAMLValue.newString('b'));
74+
75+
const objB: TypedMap<YAMLValue, YAMLValue> = new TypedMap();
76+
objB.set(YAMLValue.newString('a'), YAMLValue.newString('b'));
77+
78+
assert(YAMLValue.newObject(objA) == YAMLValue.newObject(objB));
79+
assert(!(YAMLValue.newObject(objA) != YAMLValue.newObject(objB)));
80+
81+
objA.set(YAMLValue.newString('c'), YAMLValue.newString('d'));
82+
assert(YAMLValue.newObject(objA) != YAMLValue.newObject(objB));
83+
84+
objB.set(YAMLValue.newString('c'), YAMLValue.newString('e'));
85+
assert(YAMLValue.newObject(objA) != YAMLValue.newObject(objB));
86+
87+
assert(
88+
YAMLValue.newTagged('a', YAMLValue.newString('b')) ==
89+
YAMLValue.newTagged('a', YAMLValue.newString('b')),
90+
);
91+
assert(
92+
!(
93+
YAMLValue.newTagged('a', YAMLValue.newString('b')) !=
94+
YAMLValue.newTagged('a', YAMLValue.newString('b'))
95+
),
96+
);
97+
assert(
98+
YAMLValue.newTagged('a', YAMLValue.newString('b')) !=
99+
YAMLValue.newTagged('c', YAMLValue.newString('d')),
100+
);
101+
}
102+
103+
export function testYAMLIndexOverload(): void {
104+
const objA: TypedMap<YAMLValue, YAMLValue> = new TypedMap();
105+
const objB: TypedMap<YAMLValue, YAMLValue> = new TypedMap();
106+
107+
objB.set(YAMLValue.newString('b'), YAMLValue.newString('c'));
108+
objA.set(YAMLValue.newString('a'), YAMLValue.newArray([YAMLValue.newObject(objB)]));
109+
110+
assert(YAMLValue.newObject(objA)['a']['0']['b'].toString() == 'c');
111+
}
112+
113+
export function testComplexYAMLParsing(): void {
114+
const metadata = new TypedMap<YAMLValue, YAMLValue>();
115+
metadata.set(
116+
YAMLValue.newString('created_at'),
117+
YAMLValue.newTagged('timestamp', YAMLValue.newString('2025-01-01')),
118+
);
119+
metadata.set(YAMLValue.newString('author'), YAMLValue.newNull());
120+
121+
const limits = new TypedMap<YAMLValue, YAMLValue>();
122+
limits.set(YAMLValue.newString('max_retry'), YAMLValue.newI64(3));
123+
limits.set(YAMLValue.newString('timeout'), YAMLValue.newI64(1000));
124+
125+
const settings = new TypedMap<YAMLValue, YAMLValue>();
126+
settings.set(YAMLValue.newString('debug'), YAMLValue.newBool(true));
127+
settings.set(YAMLValue.newString('mode'), YAMLValue.newString('production'));
128+
settings.set(YAMLValue.newString('limits'), YAMLValue.newObject(limits));
129+
130+
const tags = [
131+
YAMLValue.newString('important'),
132+
YAMLValue.newString('test'),
133+
YAMLValue.newString('v1'),
134+
];
135+
136+
const item1 = new TypedMap<YAMLValue, YAMLValue>();
137+
item1.set(YAMLValue.newString('name'), YAMLValue.newString('item1'));
138+
item1.set(YAMLValue.newString('value'), YAMLValue.newI64(100));
139+
140+
const item2 = new TypedMap<YAMLValue, YAMLValue>();
141+
item2.set(YAMLValue.newString('name'), YAMLValue.newString('item2'));
142+
item2.set(YAMLValue.newString('value'), YAMLValue.newI64(200));
143+
144+
const nestedArray = [YAMLValue.newObject(item1), YAMLValue.newObject(item2)];
145+
146+
// Create the root object
147+
const root = new TypedMap<YAMLValue, YAMLValue>();
148+
root.set(YAMLValue.newString('name'), YAMLValue.newString('Test Config'));
149+
root.set(YAMLValue.newString('version'), YAMLValue.newF64(1.0));
150+
root.set(YAMLValue.newString('enabled'), YAMLValue.newBool(true));
151+
root.set(YAMLValue.newString('count'), YAMLValue.newI64(42));
152+
root.set(YAMLValue.newString('pi'), YAMLValue.newF64(3.14159));
153+
root.set(YAMLValue.newString('settings'), YAMLValue.newObject(settings));
154+
root.set(YAMLValue.newString('tags'), YAMLValue.newArray(tags));
155+
root.set(YAMLValue.newString('nested_array'), YAMLValue.newArray(nestedArray));
156+
root.set(YAMLValue.newString('metadata'), YAMLValue.newObject(metadata));
157+
158+
const obj = YAMLValue.newObject(root);
159+
160+
// Now validate the structure
161+
assert(obj.isObject());
162+
const parsed = obj.toObject();
163+
164+
// Validate simple fields
165+
assert(parsed.mustGet(YAMLValue.newString('name')).toString() == 'Test Config');
166+
assert(parsed.mustGet(YAMLValue.newString('version')).toF64() == 1.0);
167+
assert(parsed.mustGet(YAMLValue.newString('enabled')).toBool() == true);
168+
assert(parsed.mustGet(YAMLValue.newString('count')).toI64() == 42);
169+
assert(parsed.mustGet(YAMLValue.newString('pi')).toF64() == 3.14159);
170+
171+
// Validate nested object (settings)
172+
const parsedSettings = parsed.mustGet(YAMLValue.newString('settings')).toObject();
173+
assert(parsedSettings.mustGet(YAMLValue.newString('debug')).toBool() == true);
174+
assert(parsedSettings.mustGet(YAMLValue.newString('mode')).toString() == 'production');
175+
176+
// Validate deeply nested object (settings.limits)
177+
const parsedLimits = parsedSettings.mustGet(YAMLValue.newString('limits')).toObject();
178+
assert(parsedLimits.mustGet(YAMLValue.newString('max_retry')).toI64() == 3);
179+
assert(parsedLimits.mustGet(YAMLValue.newString('timeout')).toI64() == 1000);
180+
181+
// Validate array of strings (tags)
182+
const parsedTags = parsed.mustGet(YAMLValue.newString('tags')).toArray();
183+
assert(parsedTags.length == 3);
184+
assert(parsedTags[0].toString() == 'important');
185+
assert(parsedTags[1].toString() == 'test');
186+
assert(parsedTags[2].toString() == 'v1');
187+
188+
// Validate array of objects (nested_array)
189+
const parsedNestedArray = parsed.mustGet(YAMLValue.newString('nested_array')).toArray();
190+
assert(parsedNestedArray.length == 2);
191+
192+
const parsedItem1 = parsedNestedArray[0].toObject();
193+
assert(parsedItem1.mustGet(YAMLValue.newString('name')).toString() == 'item1');
194+
assert(parsedItem1.mustGet(YAMLValue.newString('value')).toI64() == 100);
195+
196+
const parsedItem2 = parsedNestedArray[1].toObject();
197+
assert(parsedItem2.mustGet(YAMLValue.newString('name')).toString() == 'item2');
198+
assert(parsedItem2.mustGet(YAMLValue.newString('value')).toI64() == 200);
199+
200+
// Validate tagged value and null
201+
const parsedMetadata = parsed.mustGet(YAMLValue.newString('metadata')).toObject();
202+
assert(parsedMetadata.mustGet(YAMLValue.newString('created_at')).isTagged());
203+
const parsedCreatedAt = parsedMetadata.mustGet(YAMLValue.newString('created_at')).toTagged();
204+
assert(parsedCreatedAt.tag == 'timestamp');
205+
assert(parsedCreatedAt.value.toString() == '2025-01-01');
206+
assert(parsedMetadata.mustGet(YAMLValue.newString('author')).isNull());
207+
}

0 commit comments

Comments
 (0)
Please sign in to comment.