1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const lodash = require('lodash');
|
8 | const BigNumber = require('bignumber.js');
|
9 | const { Hex, TxHash, Hex32, Address, EpochNumber, BlockHash } = require('./type');
|
10 |
|
11 |
|
12 | class 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 |
|
63 | class 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 |
|
77 | class 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 |
|
93 | function 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 |
|
114 | parse.boolean = v => Boolean(Number(v));
|
115 | parse.number = Number;
|
116 | parse.bigNumber = BigNumber;
|
117 |
|
118 | parse.null = parse(v => v).validate(lodash.isNull);
|
119 | parse.uint = parse(Number).validate(v => v >= 0).validate(Number.isInteger);
|
120 |
|
121 | parse.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),
|
129 | });
|
130 |
|
131 | parse.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 |
|
142 | parse.receipt = parse({
|
143 | index: parse.number,
|
144 | epochNumber: parse.number,
|
145 | outcomeStatus: parse.number,
|
146 | gasUsed: parse.bigNumber,
|
147 | logs: [
|
148 | {
|
149 |
|
150 | data: data => (Array.isArray(data) ? Hex(Buffer.from(data)) : data),
|
151 | },
|
152 | ],
|
153 | });
|
154 |
|
155 | parse.logs = parse([
|
156 | {
|
157 | epochNumber: parse.number,
|
158 | logIndex: parse.number,
|
159 | transactionIndex: parse.number,
|
160 | transactionLogIndex: parse.number,
|
161 | },
|
162 | ]);
|
163 |
|
164 |
|
165 | parse.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 |
|
174 | module.exports = parse;
|