Skip to content

Commit ccf8ae1

Browse files
authored
Merge pull request #19 from T-Systems-MMS/security_context_and_error_handling
Security context and error handling
2 parents 7a32742 + 486fa1e commit ccf8ae1

File tree

8 files changed

+160
-22
lines changed

8 files changed

+160
-22
lines changed

dist/formgroup.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,11 @@ export declare class TypedFormGroup<T> extends FormGroup {
4343
*/
4444
isValidatorRegistered(name: Extract<keyof T, string>, validatorName: string): boolean;
4545
/**
46-
* Returns an error key for the next error (<controlName>.<errorKey>).
46+
* Returns an error key for the next error.
4747
*
4848
* @param name control key of the form group
49-
* @param prefix to be prepend to the error key
5049
*/
51-
nextControlErrorKey(name: Extract<keyof T, string>, prefix?: string): string;
50+
nextControlErrorKey(name: Extract<keyof T, string>): string;
5251
/**
5352
* Dispatches errors to this control and to child controls using given error map.
5453
*

dist/index.es.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,11 @@ var TypedFormGroup = /** @class */ (function (_super) {
198198
this.registeredValidatorsMap[name].some(function (errorKey) { return errorKey === validatorName; }));
199199
};
200200
/**
201-
* Returns an error key for the next error (<controlName>.<errorKey>).
201+
* Returns an error key for the next error.
202202
*
203203
* @param name control key of the form group
204-
* @param prefix to be prepend to the error key
205204
*/
206-
TypedFormGroup.prototype.nextControlErrorKey = function (name, prefix) {
205+
TypedFormGroup.prototype.nextControlErrorKey = function (name) {
207206
var control = this.get(name);
208207
if (control && control.errors) {
209208
// try client side keys first for correct order
@@ -214,7 +213,7 @@ var TypedFormGroup = /** @class */ (function (_super) {
214213
error = Object.keys(control.errors).shift();
215214
}
216215
if (error) {
217-
return "" + (prefix ? prefix + "." : '') + name + "." + error;
216+
return error;
218217
}
219218
}
220219
return '';

dist/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,11 @@ var TypedFormGroup = /** @class */ (function (_super) {
202202
this.registeredValidatorsMap[name].some(function (errorKey) { return errorKey === validatorName; }));
203203
};
204204
/**
205-
* Returns an error key for the next error (<controlName>.<errorKey>).
205+
* Returns an error key for the next error.
206206
*
207207
* @param name control key of the form group
208-
* @param prefix to be prepend to the error key
209208
*/
210-
TypedFormGroup.prototype.nextControlErrorKey = function (name, prefix) {
209+
TypedFormGroup.prototype.nextControlErrorKey = function (name) {
211210
var control = this.get(name);
212211
if (control && control.errors) {
213212
// try client side keys first for correct order
@@ -218,7 +217,7 @@ var TypedFormGroup = /** @class */ (function (_super) {
218217
error = Object.keys(control.errors).shift();
219218
}
220219
if (error) {
221-
return "" + (prefix ? prefix + "." : '') + name + "." + error;
220+
return error;
222221
}
223222
}
224223
return '';

src/formgroup.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface NestedType {
1111
deepValue: string;
1212
}
1313

14-
test('Test FormGroupControl creation', () => {
14+
test('Test FormGroupControl creation #1', () => {
1515
const factory = new BaseFormControlFactory<TestType>(
1616
{ value: 'testValue', nested: { deepValue: 'deepTestValue' } },
1717
{ value: [['req', Validators.required]], nested: [] }
@@ -60,14 +60,14 @@ test('Test FormGroupControl creation', () => {
6060
expect(nestedGroup.hasControlErrors('deepValue')).toBe(true);
6161
expect(group.isValidatorRegistered('value', 'required')).toBe(false);
6262
expect(nestedGroup.isValidatorRegistered('deepValue', 'required')).toBe(false);
63-
expect(group.nextControlErrorKey('value')).toBe('value.req');
64-
expect(nestedGroup.nextControlErrorKey('deepValue')).toBe('deepValue.req');
63+
expect(group.nextControlErrorKey('value')).toBe('req');
64+
expect(nestedGroup.nextControlErrorKey('deepValue')).toBe('req');
6565

6666
valueControl.setErrors({ other: 'Other Error!' });
67-
expect(group.nextControlErrorKey('value')).toBe('value.other');
67+
expect(group.nextControlErrorKey('value')).toBe('other');
6868
});
6969

70-
test('Test FormGroupControl creation', () => {
70+
test('Test FormGroupControl creation #2', () => {
7171
const nestedFactory = new BaseFormControlFactory<NestedType>(
7272
{ deepValue: 'deepTestValue' },
7373
{ deepValue: [['req', Validators.required]] }

src/formgroup.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,11 @@ export class TypedFormGroup<T> extends FormGroup {
8888
}
8989

9090
/**
91-
* Returns an error key for the next error (<controlName>.<errorKey>).
91+
* Returns an error key for the next error.
9292
*
9393
* @param name control key of the form group
94-
* @param prefix to be prepend to the error key
9594
*/
96-
nextControlErrorKey(name: Extract<keyof T, string>, prefix?: string): string {
95+
nextControlErrorKey(name: Extract<keyof T, string>): string {
9796
const control = this.get(name);
9897
if (control && control.errors) {
9998
// try client side keys first for correct order
@@ -105,7 +104,7 @@ export class TypedFormGroup<T> extends FormGroup {
105104
error = Object.keys(control.errors).shift();
106105
}
107106
if (error) {
108-
return `${prefix ? `${prefix}.` : ''}${name}.${error}`;
107+
return error;
109108
}
110109
}
111110
return '';

src/mustache/api.module.mustache

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }}
2+
{{! Riesaer Str. 5, 01129 Dresden }}
3+
{{! All rights reserved. }}
4+
import { NgModule, ModuleWithProviders, SkipSelf, Optional } from '@angular/core';
5+
import { Configuration } from './configuration';
6+
{{#useHttpClient}}import { HttpClient } from '@angular/common/http';{{/useHttpClient}}
7+
{{^useHttpClient}}import { Http } from '@angular/http';{{/useHttpClient}}
8+
9+
{{#apiInfo}}
10+
{{#apis}}
11+
import { {{classname}} } from './{{importPath}}';
12+
{{/apis}}
13+
{{/apiInfo}}
14+
15+
@NgModule({
16+
imports: [],
17+
declarations: [],
18+
exports: [],
19+
providers: [
20+
{{#apiInfo}}{{#apis}}{{classname}}{{#hasMore}},
21+
{{/hasMore}}{{/apis}}{{/apiInfo}} ]
22+
})
23+
export class ApiModule {
24+
public static forRoot(configurationFactory: (...args: any[]) => Configuration, deps?: any[]): ModuleWithProviders {
25+
return {
26+
ngModule: ApiModule,
27+
providers: [ { provide: Configuration, useFactory: configurationFactory, deps } ]
28+
};
29+
}
30+
31+
constructor( @Optional() @SkipSelf() parentModule: ApiModule,
32+
@Optional() http: {{#useHttpClient}}HttpClient{{/useHttpClient}}{{^useHttpClient}}Http{{/useHttpClient}}) {
33+
if (parentModule) {
34+
throw new Error('ApiModule is already loaded. Import in your base AppModule only.');
35+
}
36+
if (!http) {
37+
throw new Error('You need to import the {{#useHttpClient}}HttpClientModule{{/useHttpClient}}{{^useHttpClient}}HttpModule{{/useHttpClient}} in your AppModule! \n' +
38+
'See also https://github.com/angular/angular/issues/20575');
39+
}
40+
}
41+
}

src/mustache/api.service.mustache

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Observable } from 'rxjs/Observab
2323
{{/useRxJS6}}
2424
{{#useRxJS6}}
2525
import { Observable } from 'rxjs';
26+
import { catchError } from 'rxjs/operators';
2627
{{/useRxJS6}}
2728
{{^useHttpClient}}
2829
import '../rxjs-operators';
@@ -311,7 +312,7 @@ export class {{classname}} {
311312

312313
{{/hasFormParams}}
313314
{{#useHttpClient}}
314-
return this.httpClient.{{httpMethod}}{{^isResponseFile}}<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>{{/isResponseFile}}(`${this.configuration.basePath}{{{path}}}`,{{#isBodyAllowed}}
315+
const handle = this.httpClient.{{httpMethod}}{{^isResponseFile}}<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>{{/isResponseFile}}(`${this.configuration.basePath}{{{path}}}`,{{#isBodyAllowed}}
315316
{{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}{{#hasFormParams}}convertFormParamsToString ? formParams.toString() : formParams{{/hasFormParams}}{{^hasFormParams}}null{{/hasFormParams}}{{/bodyParam}},{{/isBodyAllowed}}
316317
{
317318
{{#hasQueryParams}}
@@ -326,6 +327,10 @@ export class {{classname}} {
326327
reportProgress: reportProgress
327328
}
328329
);
330+
if(typeof this.configuration.errorHandler === 'function') {
331+
return handle.pipe(catchError(err => this.configuration.errorHandler(err, '{{operationIdOriginal}}')));
332+
}
333+
return handle;
329334
{{/useHttpClient}}
330335
{{^useHttpClient}}
331336
let requestOptions: RequestOptionsArgs = new RequestOptions({
@@ -350,7 +355,11 @@ export class {{classname}} {
350355
requestOptions = (<any>Object).assign(requestOptions, extraHttpRequestParams);
351356
}
352357

353-
return this.http.request(`${this.configuration.basePath}{{{path}}}`, requestOptions);
358+
const handle = this.http.request(`${this.configuration.basePath}{{{path}}}`, requestOptions);
359+
if(typeof this.configuration.errorHandler === 'function') {
360+
return handle.catch(err => this.configuration.errorHandler(err, '{{operationIdOriginal}}'));
361+
}
362+
return handle;
354363
{{/useHttpClient}}
355364
}
356365

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
{{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }}
2+
{{! Riesaer Str. 5, 01129 Dresden }}
3+
{{! All rights reserved. }}
4+
{{^useRxJS6}}
5+
import { Observable } from 'rxjs/Observable';
6+
{{/useRxJS6}}
7+
{{#useRxJS6}}
8+
import { Observable } from 'rxjs';
9+
{{/useRxJS6}}
10+
11+
export interface ConfigurationParameters {
12+
apiKeys?: {[ key: string ]: string};
13+
username?: string;
14+
password?: string;
15+
accessToken?: string | (() => string);
16+
errorHandler?: (err: any, operationName: string) => Observable<never>;
17+
basePath?: string;
18+
withCredentials?: boolean;
19+
}
20+
21+
export class Configuration {
22+
apiKeys?: {[ key: string ]: string};
23+
username?: string;
24+
password?: string;
25+
accessToken?: string | (() => string);
26+
errorHandler?: (err: any, operationName: string) => Observable<never>;
27+
basePath?: string;
28+
withCredentials?: boolean;
29+
30+
constructor(configurationParameters: ConfigurationParameters = {}) {
31+
this.apiKeys = configurationParameters.apiKeys;
32+
this.username = configurationParameters.username;
33+
this.password = configurationParameters.password;
34+
this.accessToken = configurationParameters.accessToken;
35+
this.errorHandler = configurationParameters.errorHandler;
36+
this.basePath = configurationParameters.basePath;
37+
this.withCredentials = configurationParameters.withCredentials;
38+
}
39+
40+
/**
41+
* Select the correct content-type to use for a request.
42+
* Uses {@link Configuration#isJsonMime} to determine the correct content-type.
43+
* If no content type is found return the first found type if the contentTypes is not empty
44+
* @param contentTypes - the array of content types that are available for selection
45+
* @returns the selected content-type or <code>undefined</code> if no selection could be made.
46+
*/
47+
public selectHeaderContentType (contentTypes: string[]): string | undefined {
48+
if (contentTypes.length === 0) {
49+
return undefined;
50+
}
51+
52+
let type = contentTypes.find(x => this.isJsonMime(x));
53+
if (type === undefined) {
54+
return contentTypes[0];
55+
}
56+
return type;
57+
}
58+
59+
/**
60+
* Select the correct accept content-type to use for a request.
61+
* Uses {@link Configuration#isJsonMime} to determine the correct accept content-type.
62+
* If no content type is found return the first found type if the contentTypes is not empty
63+
* @param accepts - the array of content types that are available for selection.
64+
* @returns the selected content-type or <code>undefined</code> if no selection could be made.
65+
*/
66+
public selectHeaderAccept(accepts: string[]): string | undefined {
67+
if (accepts.length === 0) {
68+
return undefined;
69+
}
70+
71+
let type = accepts.find(x => this.isJsonMime(x));
72+
if (type === undefined) {
73+
return accepts[0];
74+
}
75+
return type;
76+
}
77+
78+
/**
79+
* Check if the given MIME is a JSON MIME.
80+
* JSON MIME examples:
81+
* application/json
82+
* application/json; charset=UTF8
83+
* APPLICATION/JSON
84+
* application/vnd.company+json
85+
* @param mime - MIME (Multipurpose Internet Mail Extensions)
86+
* @return True if the given MIME is JSON, false otherwise.
87+
*/
88+
public isJsonMime(mime: string): boolean {
89+
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
90+
return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
91+
}
92+
}

0 commit comments

Comments
 (0)