UNPKG

4.56 kBJavaScriptView Raw
1/*
2 NOTE:
3 - check Hex array before Hex, eg: `parse([BlockHash]).or(parse(BlockHash))`
4 - check null before number, eg: `(parse.null).or(parse.number)`
5 */
6
7const lodash = require('lodash');
8const BigNumber = require('bignumber.js');
9const { Hex, TxHash, Hex32, Address, EpochNumber, BlockHash } = require('./type');
10
11// ============================================================================
12class Parser {
13 constructor(func, { required = false, default: _default } = {}) {
14 this.func = func;
15 this.required = required;
16 this.default = _default !== undefined ? func(_default) : undefined;
17
18 return new Proxy(this.call.bind(this), {
19 get: (_, key) => this[key],
20 });
21 }
22
23 call(value) {
24 if (value === undefined) {
25 value = this.default;
26 }
27
28 if (value === undefined) {
29 if (this.required) {
30 throw new Error(`value is required, got ${value}`);
31 }
32 return undefined;
33 }
34
35 return this.func(value);
36 }
37
38 parse(func) {
39 return new Parser((...args) => func(this(...args)));
40 }
41
42 validate(func) {
43 return new Parser(value => {
44 value = this(value);
45 if (!func(value)) {
46 throw new Error(`do not match validator ${func}`);
47 }
48 return value;
49 });
50 }
51
52 or(func) {
53 return new Parser(value => {
54 try {
55 return this(value);
56 } catch (e) {
57 return func(value);
58 }
59 });
60 }
61}
62
63class ArrayParser extends Parser {
64 constructor(func, options) {
65 super(
66 array => {
67 if (!Array.isArray(array)) {
68 throw new Error(`expected a array, got ${array}`);
69 }
70 return array.map(func);
71 },
72 options,
73 );
74 }
75}
76
77class ObjectParser extends Parser {
78 constructor(keyToFunc, options) {
79 super(
80 object => {
81 if (!lodash.isPlainObject(object)) {
82 throw new Error(`expected a plain object, got ${object}`);
83 }
84
85 const picked = lodash.mapValues(keyToFunc, (func, key) => func(object[key]));
86 return lodash.defaults(picked, object);
87 },
88 options,
89 );
90 }
91}
92
93function parse(schema, options = {}) {
94 if (schema instanceof Parser) {
95 return schema;
96 }
97
98 if (lodash.isFunction(schema)) {
99 return new Parser(schema, options);
100 }
101
102 if (Array.isArray(schema)) {
103 return new ArrayParser(parse(schema[0]), options);
104 }
105
106 if (lodash.isPlainObject(schema)) {
107 return new ObjectParser(lodash.mapValues(schema, v => parse(v)), options);
108 }
109
110 throw new Error(`unknown schema type "${typeof schema}"`);
111}
112
113// ============================================================================
114parse.boolean = v => Boolean(Number(v));
115parse.number = Number;
116parse.bigNumber = BigNumber;
117
118parse.null = parse(v => v).validate(lodash.isNull);
119parse.uint = parse(Number).validate(v => v >= 0).validate(Number.isInteger);
120
121parse.transaction = parse({
122 nonce: parse.number,
123 value: parse.bigNumber,
124 gasPrice: parse.bigNumber,
125 gas: parse.bigNumber,
126 v: parse.number,
127 transactionIndex: (parse.null).or(parse.number),
128 status: (parse.null).or(parse.number), // XXX: might be remove in rpc returned
129});
130
131parse.block = parse({
132 epochNumber: parse.number,
133 height: parse.number,
134 size: parse.number,
135 timestamp: parse.number,
136 gasLimit: parse.bigNumber,
137 difficulty: parse.bigNumber,
138 transactions: [(parse.transaction).or(TxHash)],
139 stable: (parse.null).or(parse.boolean),
140});
141
142parse.receipt = parse({
143 index: parse.number, // XXX: number already in rpc return
144 epochNumber: parse.number, // XXX: number already in rpc return
145 outcomeStatus: parse.number, // XXX: number already in rpc return
146 gasUsed: parse.bigNumber,
147 logs: [
148 {
149 // FIXME: getTransactionReceipt returned log.data is array of number
150 data: data => (Array.isArray(data) ? Hex(Buffer.from(data)) : data),
151 },
152 ],
153});
154
155parse.logs = parse([
156 {
157 epochNumber: parse.number,
158 logIndex: parse.number,
159 transactionIndex: parse.number,
160 transactionLogIndex: parse.number,
161 },
162]);
163
164// ----------------------------------------------------------------------------
165parse.getLogs = parse({
166 limit: Hex.fromNumber,
167 fromEpoch: EpochNumber,
168 toEpoch: EpochNumber,
169 blockHashes: parse([BlockHash]).or(parse(BlockHash)),
170 address: parse([Address]).or(parse(Address)),
171 topics: [(parse.null).or(parse([Hex32])).or(parse(Hex32))],
172});
173
174module.exports = parse;