From f80b310d3e01f7208e8c0900fa6eee5b1e241a22 Mon Sep 17 00:00:00 2001 From: belopash Date: Wed, 1 May 2024 13:53:40 +0500 Subject: [PATCH] feat: use env eval --- src/manifest.ts | 146 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 126 insertions(+), 20 deletions(-) diff --git a/src/manifest.ts b/src/manifest.ts index d938f1b..75a2579 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -1,5 +1,6 @@ +import { Expression, Parser } from '@subsquid/manifest-expr'; import yaml from 'js-yaml'; -import { defaultsDeep, get, isPlainObject, mapValues, set } from 'lodash'; +import { cloneDeep, defaultsDeep, get, isPlainObject, mapValues, set } from 'lodash'; import { manifestSchema } from './schema'; import { @@ -12,19 +13,19 @@ import { export interface Manifest extends ManifestValue {} export class Manifest { - constructor(value: Partial = {}) { + constructor(value: ManifestValue) { defaultsDeep(this, value); if (this.manifestVersion) { - value.manifest_version = value.manifestVersion; - delete value.manifestVersion; + this.manifest_version = this.manifestVersion; + delete this.manifestVersion; } // Duck typings and legacy manifests if (this.deploy?.processor === null) { this.deploy.processor = []; } else if (this.deploy?.processor && isPlainObject(this.deploy?.processor)) { - const proc = value?.deploy?.processor as unknown as ManifestProcessor; + const proc = this.deploy?.processor as unknown as ManifestProcessor; if (!proc.name) { proc.name = 'processor'; } @@ -38,7 +39,7 @@ export class Manifest { } if (this.scale) { - value = defaultsDeep(value, { + defaultsDeep(this, { scale: { dedicated: false, }, @@ -110,19 +111,20 @@ export class Manifest { } values(): ManifestValue { - return defaultsDeep( - {}, - { - manifest_version: this.manifest_version, - name: this.name, - description: this.description, - version: this.version, - build: this.build, - deploy: this.deploy, - scale: this.scale, - queries: this.queries, - }, - ); + return this.toObject(); + } + + toObject() { + return { + manifest_version: this.manifest_version, + name: this.name, + description: this.description, + version: this.version, + build: cloneDeep(this.build), + deploy: cloneDeep(this.deploy), + scale: cloneDeep(this.scale), + queries: cloneDeep(this.queries), + }; } toString(pretty = false) { @@ -133,6 +135,101 @@ export class Manifest { return yaml.dump(this.values()); } + eval(context: Record): ManifestValue { + const { error: parseError, value: parsed } = this.parse(); + if (parseError) { + throw parseError; + } + + const _eval = (env: Record | undefined, path: string) => { + return mapValues(env, (value, key) => { + try { + return value.eval(context); + } catch (e) { + throw new ManifestEvaluatingError('Unable to evaluate manifest', [ + getError(`${path}.${key}`, get(this as ManifestValue, `${path}.${key}`), e), + ]); + } + }); + }; + + return { + manifest_version: this.manifest_version, + name: this.name, + description: this.description, + version: this.version, + build: cloneDeep(this.build), + deploy: defaultsDeep( + { + env: _eval(parsed.env, 'deploy.env'), + init: { env: _eval(parsed.init.env, 'deploy.init.env') }, + api: { env: _eval(parsed.api.env, 'deploy.api.env') }, + processor: parsed.processor.map((p, index) => + defaultsDeep( + { + env: _eval(p.env, `deploy.processor.[${index}].env`), + }, + this.deploy?.processor?.[index], + ), + ), + }, + this.deploy, + ), + scale: cloneDeep(this.scale), + queries: cloneDeep(this.queries), + }; + } + + variables(path?: string[]) { + const res: Set = new Set(); + + const { error: parseError, value: parsed } = this.parse(); + if (parseError) { + throw parseError; + } + + const _variables = (env: Record | undefined) => { + mapValues(env, value => value.variables(path).forEach(v => res.add(v))); + }; + + _variables(parsed.env); + _variables(parsed.init.env); + _variables(parsed.api.env); + parsed.processor?.forEach(p => _variables(p.env)); + + return [...res]; + } + + private parse() { + const parser = new Parser(); + const errors: string[] = []; + + const _parse = (env: Record | undefined, path: string) => { + return mapValues(env, (value, key) => { + try { + return parser.parse(value); + } catch (e: unknown) { + errors.push(getError(`${path}.${key}`, value, e)); + return new Expression(['']); + } + }); + }; + + const res = { + env: _parse(this.deploy?.env, 'deploy.env'), + init: { env: _parse(this.deploy?.init?.env, 'deploy.init.env') }, + api: { env: _parse(this.deploy?.api?.env, 'deploy.api.env') }, + processor: + this.deploy?.processor?.map((p, index) => ({ + env: _parse(p.env, `deploy.processor.[${index}].env`), + })) || [], + }; + + return errors.length > 0 + ? { error: new ManifestEvaluatingError(`Unable to parse manifest`, errors) } + : { value: res }; + } + static validate(value: Partial, options: ManifestOptions = {}) { const res = manifestSchema.validate(value, { allowUnknown: options.validation?.allowUnknown, @@ -173,7 +270,7 @@ export class Manifest { } return { - value: new Manifest(res.value), + value: new Manifest(res.value!), }; } catch (e: unknown) { return { @@ -197,3 +294,12 @@ function getError(path: string, expression: string | undefined, error: any) { error instanceof Error ? error.message : error.toString(), ].join(': '); } + +export class ManifestEvaluatingError extends Error { + constructor( + message: string, + readonly details: string[], + ) { + super(message); + } +}