Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.

Commit 68da660

Browse files
hanslBrocco
authored andcommitted
feat(@angular-devkit/core): add post transforms to registry
And move _clean to a post transform.
1 parent bd01ef7 commit 68da660

File tree

4 files changed

+115
-76
lines changed

4 files changed

+115
-76
lines changed

packages/angular_devkit/core/src/json/schema/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@
88
export * from './interface';
99
export * from './registry';
1010
export * from './visitor';
11+
12+
import * as transforms from './transforms';
13+
14+
export { transforms };

packages/angular_devkit/core/src/json/schema/registry.ts

+66-72
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ import {
2121
SchemaValidator,
2222
SchemaValidatorResult,
2323
} from './interface';
24-
import { JsonPointer, JsonVisitor, parseJsonPointer, visitJson, visitJsonSchema } from './visitor';
24+
import { addUndefinedDefaults } from './transforms';
25+
import { JsonVisitor, visitJson } from './visitor';
2526

2627

2728
export class CoreSchemaRegistry implements SchemaRegistry {
2829
private _ajv: ajv.Ajv;
2930
private _uriCache = new Map<string, JsonObject>();
3031
private _pre = new PartiallyOrderedSet<JsonVisitor>();
32+
private _post = new PartiallyOrderedSet<JsonVisitor>();
3133

3234
constructor(formats: SchemaFormat[] = []) {
3335
/**
@@ -48,74 +50,8 @@ export class CoreSchemaRegistry implements SchemaRegistry {
4850
});
4951

5052
this._ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
51-
}
52-
53-
private _clean(
54-
data: any, // tslint:disable-line:no-any
55-
schema: JsonObject,
56-
validate: ajv.ValidateFunction,
57-
parentDataCache: WeakMap<object, any>, // tslint:disable-line:no-any
58-
) {
59-
visitJsonSchema(
60-
schema,
61-
(currentSchema: object, pointer: JsonPointer, parentSchema?: object, index?: string) => {
62-
// If we're at the root, skip.
63-
if (parentSchema === undefined || index === undefined) {
64-
return;
65-
}
66-
67-
const parsedPointer = parseJsonPointer(pointer);
68-
// Every other path fragment is either 'properties', 'items', 'allOf', ...
69-
const nonPropertyParsedPP = parsedPointer.filter((_, i) => !(i % 2));
70-
// Skip if it's part of a definitions or too complex for us to analyze.
71-
if (nonPropertyParsedPP.some(f => f == 'definitions' || f == 'allOf' || f == 'anyOf')) {
72-
return;
73-
}
74-
75-
let maybeParentData = parentDataCache.get(parentSchema);
76-
if (!maybeParentData) {
77-
// Every other path fragment is either 'properties' or 'items' in this model.
78-
const parentDataPointer = parsedPointer.filter((_, i) => i % 2);
79-
80-
// Find the parentData from the list.
81-
maybeParentData = data;
82-
for (const index of parentDataPointer.slice(0, -1)) {
83-
if (maybeParentData[index] === undefined) {
84-
// tslint:disable-next-line:no-any
85-
if (parentSchema.hasOwnProperty('items') || (parentSchema as any)['type'] == 'array') {
86-
maybeParentData[index] = [];
87-
} else {
88-
maybeParentData[index] = {};
89-
}
90-
}
91-
maybeParentData = maybeParentData[index];
92-
}
93-
parentDataCache.set(parentSchema, maybeParentData);
94-
}
95-
96-
if (currentSchema.hasOwnProperty('$ref')) {
97-
const $ref = (currentSchema as { $ref: string })['$ref'];
98-
const refHash = $ref.split('#', 2)[1];
99-
const refUrl = $ref.startsWith('#') ? $ref : $ref.split('#', 1);
100-
101-
let refVal = validate;
102-
if (!$ref.startsWith('#')) {
103-
// tslint:disable-next-line:no-any
104-
refVal = (validate.refVal as any)[(validate.refs as any)[refUrl[0]]];
105-
}
106-
if (refHash) {
107-
// tslint:disable-next-line:no-any
108-
refVal = (refVal.refVal as any)[(refVal.refs as any)['#' + refHash]];
109-
}
11053

111-
maybeParentData[index] = {};
112-
this._clean(maybeParentData[index], refVal.schema as JsonObject, refVal, parentDataCache);
113-
114-
return;
115-
} else if (!maybeParentData.hasOwnProperty(index)) {
116-
maybeParentData[index] = undefined;
117-
}
118-
});
54+
this.addPostTransform(addUndefinedDefaults);
11955
}
12056

12157
private _fetch(uri: string): Promise<JsonObject> {
@@ -160,7 +96,41 @@ export class CoreSchemaRegistry implements SchemaRegistry {
16096
this._pre.add(visitor, deps);
16197
}
16298

163-
compile(schema: Object): Observable<SchemaValidator> {
99+
/**
100+
* Add a transformation step after the validation of any Json. The JSON will not be validated
101+
* after the POST, so if transformations are not compatible with the Schema it will not result
102+
* in an error.
103+
* @param {JsonVisitor} visitor The visitor to transform every value.
104+
* @param {JsonVisitor[]} deps A list of other visitors to run before.
105+
*/
106+
addPostTransform(visitor: JsonVisitor, deps?: JsonVisitor[]) {
107+
this._post.add(visitor, deps);
108+
}
109+
110+
protected _resolver(
111+
ref: string,
112+
validate: ajv.ValidateFunction,
113+
): { context?: ajv.ValidateFunction, schema?: JsonObject } {
114+
if (!validate) {
115+
return {};
116+
}
117+
118+
const refHash = ref.split('#', 2)[1];
119+
const refUrl = ref.startsWith('#') ? ref : ref.split('#', 1);
120+
121+
if (!ref.startsWith('#')) {
122+
// tslint:disable-next-line:no-any
123+
validate = (validate.refVal as any)[(validate.refs as any)[refUrl[0]]];
124+
}
125+
if (validate && refHash) {
126+
// tslint:disable-next-line:no-any
127+
validate = (validate.refVal as any)[(validate.refs as any)['#' + refHash]];
128+
}
129+
130+
return { context: validate, schema: validate && validate.schema as JsonObject };
131+
}
132+
133+
compile(schema: JsonObject): Observable<SchemaValidator> {
164134
// Supports both synchronous and asynchronous compilation, by trying the synchronous
165135
// version first, then if refs are missing this will fails.
166136
// We also add any refs from external fetched schemas so that those will also be used
@@ -193,7 +163,9 @@ export class CoreSchemaRegistry implements SchemaRegistry {
193163
let dataObs = observableOf(data);
194164
this._pre.forEach(visitor =>
195165
dataObs = dataObs.pipe(
196-
concatMap(data => visitJson(data as JsonValue, visitor)),
166+
concatMap(data => {
167+
return visitJson(data as JsonValue, visitor, schema, this._resolver, validate);
168+
}),
197169
),
198170
);
199171

@@ -206,14 +178,36 @@ export class CoreSchemaRegistry implements SchemaRegistry {
206178
: fromPromise((result as PromiseLike<boolean>)
207179
.then(result => [updatedData, result]));
208180
}),
181+
switchMap(([data, valid]) => {
182+
if (valid) {
183+
let dataObs = observableOf(data);
184+
this._post.forEach(visitor =>
185+
dataObs = dataObs.pipe(
186+
concatMap(data => {
187+
return visitJson(
188+
data as JsonValue,
189+
visitor,
190+
schema,
191+
this._resolver,
192+
validate,
193+
);
194+
}),
195+
),
196+
);
197+
198+
return dataObs.pipe(
199+
map(data => [data, valid]),
200+
);
201+
} else {
202+
return observableOf([data, valid]);
203+
}
204+
}),
209205
map(([data, valid]) => {
210206
if (valid) {
211207
// tslint:disable-next-line:no-any
212208
const schemaDataMap = new WeakMap<object, any>();
213209
schemaDataMap.set(schema, data);
214210

215-
this._clean(data, schema as JsonObject, validate, schemaDataMap);
216-
217211
return { data, success: true } as SchemaValidatorResult;
218212
}
219213

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { JsonArray, JsonObject, JsonValue } from '..';
9+
import { JsonPointer } from './visitor';
10+
11+
12+
export function addUndefinedDefaults(
13+
value: JsonValue | undefined,
14+
_pointer: JsonPointer,
15+
schema?: JsonObject,
16+
_root?: JsonObject | JsonArray,
17+
): JsonValue | undefined {
18+
if (value === undefined && schema) {
19+
if (schema.items || schema.type == 'array') {
20+
return [];
21+
}
22+
if (schema.properties || schema.type == 'object') {
23+
const newValue: JsonObject = {};
24+
for (const propName of Object.getOwnPropertyNames(schema.properties || {})) {
25+
newValue[propName] = undefined as any; // tslint:disable-line:no-any
26+
}
27+
28+
return newValue;
29+
}
30+
} else if (schema
31+
&& typeof value == 'object' && value
32+
&& (schema.properties || schema.type == 'object')
33+
) {
34+
for (const propName of Object.getOwnPropertyNames(schema.properties || {})) {
35+
(value as JsonObject)[propName] = (propName in value)
36+
? (value as JsonObject)[propName]
37+
: undefined as any; // tslint:disable-line:no-any
38+
}
39+
}
40+
41+
return value;
42+
}

packages/angular_devkit/schematics/src/formats/format-validator.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
9-
import { schema } from '@angular-devkit/core';
8+
import { JsonObject, JsonValue, schema } from '@angular-devkit/core';
109
import { Observable } from 'rxjs/Observable';
1110
import { mergeMap } from 'rxjs/operators';
1211

1312

1413
export function formatValidator(
15-
data: Object,
16-
dataSchema: Object,
14+
data: JsonValue,
15+
dataSchema: JsonObject,
1716
formats: schema.SchemaFormat[],
1817
): Observable<schema.SchemaValidatorResult> {
1918
const registry = new schema.CoreSchemaRegistry();

0 commit comments

Comments
 (0)