diff --git a/.bumpy/decorator-error-once.md b/.bumpy/decorator-error-once.md new file mode 100644 index 000000000..d9dd31c70 --- /dev/null +++ b/.bumpy/decorator-error-once.md @@ -0,0 +1,5 @@ +--- +"varlock": patch +--- + +An error from a decorator's value (for example `@required=forEnv(prod)` when the current environment is not set) is reported once instead of twice diff --git a/packages/varlock/src/env-graph/lib/decorators.ts b/packages/varlock/src/env-graph/lib/decorators.ts index 10674d7af..5f4350fe2 100644 --- a/packages/varlock/src/env-graph/lib/decorators.ts +++ b/packages/varlock/src/env-graph/lib/decorators.ts @@ -210,9 +210,11 @@ export abstract class DecoratorInstance { resolvedValue?: any; isResolved = false; + /** set when resolving threw, so a later call does not run the resolver and record the same error again */ + private resolveFailed = false; async resolve() { - if (this.isResolved) return this.resolvedValue; + if (this.isResolved || this.resolveFailed) return this.resolvedValue; await this.process(); // decorators that skip value-resolver creation (see process()) have nothing to resolve @@ -229,6 +231,7 @@ export abstract class DecoratorInstance { this.resolvedValue = await this.decValueResolver.resolve(); } catch (err) { this._errors.push(err as any); + this.resolveFailed = true; return; } diff --git a/packages/varlock/src/env-graph/test/decorator-error-once.test.ts b/packages/varlock/src/env-graph/test/decorator-error-once.test.ts new file mode 100644 index 000000000..00c716585 --- /dev/null +++ b/packages/varlock/src/env-graph/test/decorator-error-once.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest'; +import outdent from 'outdent'; +import { EnvGraph } from '../index'; +import { DotEnvFileDataSource } from '../lib/data-source'; + +describe('a decorator whose resolver throws', () => { + it('reports the error once', async () => { + const g = new EnvGraph(); + await g.setRootDataSource(new DotEnvFileDataSource('.env.schema', { + overrideContents: outdent` + # @currentEnv=$APP_ENV + # @defaultRequired=false + # --- + # @type=enum(dev, prod) + APP_ENV= + # @required=forEnv(prod) + API_URL= + `, + })); + await g.finishLoad(); + await g.resolveEnvValues(); + expect(g.configSchema.API_URL.errors.map((e) => e.message)).toEqual(['forEnv(): current environment is not set']); + }); +});