From 06af7d7e7b6b3f5b854555771db3b60b9484d4b3 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Thu, 27 May 2021 11:04:54 +0200 Subject: [PATCH] fix(@angular-devkit/core): handle async schema validations --- .../angular_devkit/core/src/_golden-api.d.ts | 2 +- .../angular/cli/models/architect-command.ts | 2 +- .../core/src/json/schema/interface.ts | 2 +- .../core/src/json/schema/registry.ts | 46 +++++++++++++------ .../core/src/json/schema/registry_spec.ts | 6 +-- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/goldens/public-api/angular_devkit/core/src/_golden-api.d.ts b/goldens/public-api/angular_devkit/core/src/_golden-api.d.ts index 35a04b710a1e..78160bfb5849 100644 --- a/goldens/public-api/angular_devkit/core/src/_golden-api.d.ts +++ b/goldens/public-api/angular_devkit/core/src/_golden-api.d.ts @@ -793,7 +793,7 @@ export interface SchemaValidator { (data: JsonValue, options?: SchemaValidatorOptions): Observable; } -export declare type SchemaValidatorError = ErrorObject; +export declare type SchemaValidatorError = Partial; export interface SchemaValidatorOptions { applyPostTransforms?: boolean; diff --git a/packages/angular/cli/models/architect-command.ts b/packages/angular/cli/models/architect-command.ts index 462243fe31a3..d98ddbabe340 100644 --- a/packages/angular/cli/models/architect-command.ts +++ b/packages/angular/cli/models/architect-command.ts @@ -281,7 +281,7 @@ export abstract class ArchitectCommand< const newErrors: schema.SchemaValidatorError[] = []; for (const schemaError of e.errors) { if (schemaError.keyword === 'additionalProperties') { - const unknownProperty = schemaError.params.additionalProperty; + const unknownProperty = schemaError.params?.additionalProperty; if (unknownProperty in options) { const dashes = unknownProperty.length === 1 ? '-' : '--'; this.logger.fatal(`Unknown option: '${dashes}${unknownProperty}'`); diff --git a/packages/angular_devkit/core/src/json/schema/interface.ts b/packages/angular_devkit/core/src/json/schema/interface.ts index 80c7349ff05d..e56a45d34f35 100644 --- a/packages/angular_devkit/core/src/json/schema/interface.ts +++ b/packages/angular_devkit/core/src/json/schema/interface.ts @@ -19,7 +19,7 @@ export interface SchemaValidatorResult { errors?: SchemaValidatorError[]; } -export type SchemaValidatorError = ErrorObject; +export type SchemaValidatorError = Partial; export interface SchemaValidatorOptions { applyPreTransforms?: boolean; diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts index 69333feb6dfb..652b2327eef2 100644 --- a/packages/angular_devkit/core/src/json/schema/registry.ts +++ b/packages/angular_devkit/core/src/json/schema/registry.ts @@ -63,16 +63,18 @@ export class SchemaValidationException extends BaseException { const messages = errors.map((err) => { let message = `Data path ${JSON.stringify(err.instancePath)} ${err.message}`; - switch (err.keyword) { - case 'additionalProperties': - message += `(${err.params.additionalProperty})`; - break; - - case 'enum': - message += `. Allowed values are: ${(err.params.allowedValues as string[] | undefined) - ?.map((v) => `"${v}"`) - .join(', ')}`; - break; + if (err.params) { + switch (err.keyword) { + case 'additionalProperties': + message += `(${err.params.additionalProperty})`; + break; + + case 'enum': + message += `. Allowed values are: ${(err.params.allowedValues as string[] | undefined) + ?.map((v) => `"${v}"`) + .join(', ')}`; + break; + } } return message + '.'; @@ -300,12 +302,17 @@ export class CoreSchemaRegistry implements SchemaRegistry { }; this._ajv.removeSchema(schema); - let validator: ValidateFunction; + try { this._currentCompilationSchemaInfo = schemaInfo; validator = this._ajv.compile(schema); - } catch { + } catch (e) { + // This should eventually be refactored so that we we handle race condition where the same schema is validated at the same time. + if (!(e instanceof Ajv.MissingRefError)) { + throw e; + } + validator = await this._ajv.compileAsync(schema); } finally { this._currentCompilationSchemaInfo = undefined; @@ -361,9 +368,18 @@ export class CoreSchemaRegistry implements SchemaRegistry { } // Validate using ajv - const success = await validator.call(validationContext, data); - if (!success) { - return { data, success, errors: validator.errors ?? [] }; + try { + const success = await validator.call(validationContext, data); + + if (!success) { + return { data, success, errors: validator.errors ?? [] }; + } + } catch (error) { + if (error instanceof Ajv.ValidationError) { + return { data, success: false, errors: error.errors }; + } + + throw error; } // Apply post-validation transforms diff --git a/packages/angular_devkit/core/src/json/schema/registry_spec.ts b/packages/angular_devkit/core/src/json/schema/registry_spec.ts index d64d96238d09..aa9825585e66 100644 --- a/packages/angular_devkit/core/src/json/schema/registry_spec.ts +++ b/packages/angular_devkit/core/src/json/schema/registry_spec.ts @@ -180,9 +180,7 @@ describe('CoreSchemaRegistry', () => { mergeMap((validator) => validator(data)), map((result) => { expect(result.success).toBe(false); - expect(result.errors?.[0].message).toContain( - 'must NOT have additional properties', - ); + expect(result.errors?.[0].message).toContain('must NOT have additional properties'); expect(result.errors?.[0].keyword).toBe('additionalProperties'); }), ) @@ -279,7 +277,7 @@ describe('CoreSchemaRegistry', () => { expect(result.errors && (result.errors[0].params as any).format).toBe('is-hotdog'); }), ) - .toPromise() + .toPromise(); }); it('supports smart defaults', (done) => {