Skip to content

Commit b551854

Browse files
filipesilvaalexeagle
authored andcommitted
fix(@angular-devkit/architect): error run on input schema error (#14315)
Fix #14269
1 parent 7f53420 commit b551854

File tree

2 files changed

+49
-13
lines changed

2 files changed

+49
-13
lines changed

packages/angular_devkit/architect/src/architect.ts

+37-13
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import { analytics, experimental, json, logging } from '@angular-devkit/core';
9-
import { Observable, from, of } from 'rxjs';
10-
import { concatMap, first, map, shareReplay, switchMap } from 'rxjs/operators';
9+
import { Observable, from, merge, of, onErrorResumeNext } from 'rxjs';
10+
import {
11+
concatMap,
12+
first,
13+
ignoreElements,
14+
last,
15+
map,
16+
shareReplay,
17+
switchMap,
18+
takeUntil,
19+
} from 'rxjs/operators';
1120
import {
1221
BuilderInfo,
1322
BuilderInput,
@@ -39,7 +48,8 @@ function _createJobHandlerFromBuilderInfo(
3948
};
4049

4150
function handler(argument: json.JsonObject, context: experimental.jobs.JobHandlerContext) {
42-
const inboundBus = context.inboundBus.pipe(
51+
// Add input validation to the inbound bus.
52+
const inboundBusWithInputValidation = context.inboundBus.pipe(
4353
concatMap(message => {
4454
if (message.kind === experimental.jobs.JobInboundMessageKind.Input) {
4555
const v = message.value as BuilderInput;
@@ -51,14 +61,12 @@ function _createJobHandlerFromBuilderInfo(
5161
// Validate v against the options schema.
5262
return registry.compile(info.optionSchema).pipe(
5363
concatMap(validation => validation(options)),
54-
map(result => {
55-
if (result.success) {
56-
return { ...v, options: result.data } as BuilderInput;
57-
} else if (result.errors) {
58-
throw new json.schema.SchemaValidationException(result.errors);
59-
} else {
60-
return v;
64+
map(({ data, success, errors }) => {
65+
if (success) {
66+
return { ...v, options: data } as BuilderInput;
6167
}
68+
69+
throw new json.schema.SchemaValidationException(errors);
6270
}),
6371
map(value => ({ ...message, value })),
6472
);
@@ -71,7 +79,11 @@ function _createJobHandlerFromBuilderInfo(
7179
shareReplay(1),
7280
);
7381

74-
return from(host.loadBuilder(info)).pipe(
82+
// Make an inboundBus that completes instead of erroring out.
83+
// We'll merge the errors into the output instead.
84+
const inboundBus = onErrorResumeNext(inboundBusWithInputValidation);
85+
86+
const output = from(host.loadBuilder(info)).pipe(
7587
concatMap(builder => {
7688
if (builder === null) {
7789
throw new Error(`Cannot load builder for builderInfo ${JSON.stringify(info, null, 2)}`);
@@ -94,7 +106,19 @@ function _createJobHandlerFromBuilderInfo(
94106
}),
95107
);
96108
}),
109+
// Share subscriptions to the output, otherwise the the handler will be re-run.
110+
shareReplay(),
97111
);
112+
113+
// Separate the errors from the inbound bus into their own observable that completes when the
114+
// builder output does.
115+
const inboundBusErrors = inboundBusWithInputValidation.pipe(
116+
ignoreElements(),
117+
takeUntil(onErrorResumeNext(output.pipe(last()))),
118+
);
119+
120+
// Return the builder output plus any input errors.
121+
return merge(inboundBusErrors, output);
98122
}
99123

100124
return of(Object.assign(handler, { jobDescription }) as BuilderJobHandler);
@@ -281,9 +305,9 @@ function _validateOptionsFactory(host: ArchitectHost, registry: json.schema.Sche
281305
switchMap(({ data, success, errors }) => {
282306
if (success) {
283307
return of(data as json.JsonObject);
284-
} else {
285-
throw new json.schema.SchemaValidationException(errors);
286308
}
309+
310+
throw new json.schema.SchemaValidationException(errors);
287311
}),
288312
).toPromise();
289313
},

packages/angular_devkit/architect/src/index_spec.ts

+12
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,18 @@ describe('architect', () => {
191191
}
192192
});
193193

194+
it('reports errors in options', async () => {
195+
const builderName = 'options:error';
196+
const builder = createBuilder(() => ({ success: true }));
197+
const optionSchema = { type: 'object', additionalProperties: false };
198+
testArchitectHost.addBuilder(builderName, builder, '', optionSchema);
199+
200+
const run = await architect.scheduleBuilder(builderName, { extraProp: true });
201+
await expectAsync(run.result).toBeRejectedWith(
202+
jasmine.objectContaining({ message: jasmine.stringMatching('extraProp') }));
203+
await run.stop();
204+
});
205+
194206
it('exposes getTargetOptions() properly', async () => {
195207
const goldenOptions = {
196208
value: 'value',

0 commit comments

Comments
 (0)