Skip to content

Commit 7d5c818

Browse files
feat: validation for handlers from manifest file (#1535)
* feat: validation for handlers from manifest file * chore(dependencies): updated changesets for modified dependencies * style run prettier --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 35fff8d commit 7d5c818

File tree

5 files changed

+243
-7
lines changed

5 files changed

+243
-7
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/graph-cli": patch
3+
---
4+
dependencies updates:
5+
- Added dependency [`@babel/core@^7.20.5` ↗︎](https://www.npmjs.com/package/@babel/core/v/7.20.5) (to `dependencies`)
6+
- Added dependency [`@babel/preset-typescript@^7.18.6` ↗︎](https://www.npmjs.com/package/@babel/preset-typescript/v/7.18.6) (to `dependencies`)
7+
- Added dependency [`memoizee@^0.4.15` ↗︎](https://www.npmjs.com/package/memoizee/v/0.4.15) (to `dependencies`)

.changeset/six-crabs-crash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphprotocol/graph-cli': minor
3+
---
4+
5+
add validation for handlers from subgraph manifest

packages/cli/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
"type-check": "tsc --noEmit"
2929
},
3030
"dependencies": {
31+
"@babel/core": "^7.20.5",
32+
"@babel/preset-typescript": "^7.18.6",
3133
"@float-capital/float-subgraph-uncrashable": "^0.0.0-alpha.4",
3234
"@oclif/core": "2.8.6",
3335
"@oclif/plugin-autocomplete": "^2.3.6",
@@ -48,6 +50,7 @@
4850
"ipfs-http-client": "55.0.0",
4951
"jayson": "4.0.0",
5052
"js-yaml": "3.14.1",
53+
"memoizee": "^0.4.15",
5154
"prettier": "1.19.1",
5255
"request": "2.88.2",
5356
"semver": "7.4.0",
@@ -58,10 +61,12 @@
5861
"yaml": "1.10.2"
5962
},
6063
"devDependencies": {
64+
"@types/babel__core": "^7.20.5",
6165
"@types/debug": "^4.1.7",
6266
"@types/fs-extra": "^9.0.13",
6367
"@types/jest": "^29.0.0",
6468
"@types/js-yaml": "^3.12.7",
69+
"@types/memoizee": "^0.4.11",
6570
"@types/semver": "^7.3.13",
6671
"@types/which": "^2.0.1",
6772
"copyfiles": "^2.4.1",

packages/cli/src/compiler/index.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import * as toolbox from 'gluegun';
66
import immutable from 'immutable';
77
import type { IPFSHTTPClient } from 'ipfs-http-client';
88
import yaml from 'js-yaml';
9+
import memo from 'memoizee';
10+
import { parseSync } from '@babel/core';
911
import { Spinner, step, withSpinner } from '../command-helpers/spinner';
1012
import debug from '../debug';
1113
import { applyMigrations } from '../migrations';
@@ -16,6 +18,18 @@ import * as asc from './asc';
1618

1719
const compilerDebug = debug('graph-cli:compiler');
1820

21+
/** memoize the reading of the file so we don't have to read it every time */
22+
const readFile = memo((filename: string) => fs.readFileSync(filename, 'utf-8'));
23+
24+
/** Memoized parser for Babel, so we can simply just read from cache */
25+
const babelAst = memo((filename: string) => {
26+
const data = readFile(filename);
27+
return parseSync(data, {
28+
presets: ['@babel/preset-typescript'],
29+
filename,
30+
});
31+
});
32+
1933
interface CompilerOptions {
2034
ipfs: any;
2135
subgraphManifest: string;
@@ -350,12 +364,19 @@ export default class Compiler {
350364

351365
try {
352366
const dataSourceName = dataSource.getIn(['name']);
353-
354367
const baseDir = this.sourceDir;
355368
const absoluteMappingPath = path.resolve(baseDir, mappingPath);
356369
const inputFile = path.relative(baseDir, absoluteMappingPath);
370+
357371
this._validateMappingContent(absoluteMappingPath);
358372

373+
const eventHandlers = dataSource.getIn(['mapping', 'eventHandlers']);
374+
// TODO: improve the types
375+
for (const eventHandler of (eventHandlers as any).toJS()) {
376+
compilerDebug('Validating Event Handler %s', eventHandler.handler);
377+
this._validateHandler(absoluteMappingPath, eventHandler.handler);
378+
}
379+
359380
// If the file has already been compiled elsewhere, just use that output
360381
// file and return early
361382
const inputCacheKey = this.cacheKeyForFile(absoluteMappingPath);
@@ -431,6 +452,13 @@ export default class Compiler {
431452
const inputFile = path.relative(baseDir, absoluteMappingPath);
432453
this._validateMappingContent(absoluteMappingPath);
433454

455+
const eventHandlers = templateName.getIn(['mapping', 'eventHandlers']);
456+
// TODO: improve the types
457+
for (const eventHandler of (eventHandlers as any).toJS()) {
458+
compilerDebug('Validating Template handler %s', eventHandler.handler);
459+
this._validateHandler(absoluteMappingPath, eventHandler.handler);
460+
}
461+
434462
// If the file has already been compiled elsewhere, just use that output
435463
// file and return early
436464
const inputCacheKey = this.cacheKeyForFile(absoluteMappingPath);
@@ -489,7 +517,7 @@ export default class Compiler {
489517
}
490518

491519
_validateMappingContent(filePath: string) {
492-
const data = fs.readFileSync(filePath);
520+
const data = readFile(filePath);
493521
if (this.blockIpfsMethods && (data.includes('ipfs.cat') || data.includes('ipfs.map'))) {
494522
throw Error(`
495523
Subgraph Studio does not support mappings with ipfs methods.
@@ -499,6 +527,31 @@ export default class Compiler {
499527
}
500528
}
501529

530+
_validateHandler(filePath: string, handlerName: string) {
531+
const baselAst = babelAst(filePath);
532+
533+
const body = baselAst?.program.body;
534+
535+
if (!body) {
536+
throw Error(`Could not parse ${filePath}`);
537+
}
538+
539+
const exportedFunctionNames = body
540+
.map(statement => {
541+
if (
542+
statement.type === 'ExportNamedDeclaration' &&
543+
statement?.declaration?.type === 'FunctionDeclaration'
544+
) {
545+
return statement.declaration.id?.name;
546+
}
547+
})
548+
.filter(Boolean);
549+
550+
if (!exportedFunctionNames.includes(handlerName)) {
551+
throw Error(`Could not find handler '${handlerName}' in ${filePath}`);
552+
}
553+
}
554+
502555
async writeSubgraphToOutputDirectory(protocol: Protocol, subgraph: immutable.Map<any, any>) {
503556
const displayDir = `${this.displayPath(this.options.outputDir)}${toolbox.filesystem.separator}`;
504557

0 commit comments

Comments
 (0)