Skip to content

Commit

Permalink
perf(http): made errors in the http package tree shakeable
Browse files Browse the repository at this point in the history
This moves all the errors into a model that can be tree shaken away.
  • Loading branch information
sonukapoor committed Sep 26, 2022
1 parent 7a5ba93 commit bfaf793
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 22 deletions.
23 changes: 19 additions & 4 deletions packages/common/http/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {Injectable} from '@angular/core';
import {Observable, of} from 'rxjs';
import {concatMap, filter, map} from 'rxjs/operators';

import {RuntimeError} from '../../../core/src/errors';
import {RuntimeErrorCode} from '../../src/errors';

import {HttpHandler} from './backend';
import {HttpContext} from './context';
import {HttpHeaders} from './headers';
Expand Down Expand Up @@ -568,23 +571,32 @@ export class HttpClient {
return res$.pipe(map((res: HttpResponse<any>) => {
// Validate that the body is an ArrayBuffer.
if (res.body !== null && !(res.body instanceof ArrayBuffer)) {
throw new Error('Response is not an ArrayBuffer.');
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
'Response is not an ArrayBuffer.' :
'';
throw new RuntimeError(RuntimeErrorCode.INVALID_HTTP_RESPONSE, errorMessage);
}
return res.body;
}));
case 'blob':
return res$.pipe(map((res: HttpResponse<any>) => {
// Validate that the body is a Blob.
if (res.body !== null && !(res.body instanceof Blob)) {
throw new Error('Response is not a Blob.');
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
'Response is not a Blob.' :
'';
throw new RuntimeError(RuntimeErrorCode.INVALID_HTTP_RESPONSE, errorMessage);
}
return res.body;
}));
case 'text':
return res$.pipe(map((res: HttpResponse<any>) => {
// Validate that the body is a string.
if (res.body !== null && typeof res.body !== 'string') {
throw new Error('Response is not a string.');
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
'Response is not a string.' :
'';
throw new RuntimeError(RuntimeErrorCode.INVALID_HTTP_RESPONSE, errorMessage);
}
return res.body;
}));
Expand All @@ -598,7 +610,10 @@ export class HttpClient {
return res$;
default:
// Guard against new future observe types being added.
throw new Error(`Unreachable: unhandled observe type ${options.observe}}`);
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
`Unreachable: unhandled observe type ${options.observe}}` :
'';
throw new RuntimeError(RuntimeErrorCode.UNHANDLED_HTTP_OBSERVE_TYPE, errorMessage);
}
}

Expand Down
9 changes: 7 additions & 2 deletions packages/common/http/src/headers.ts
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

import {RuntimeError} from '../../../core/src/errors';
import {RuntimeErrorCode} from '../../src/errors';

interface Update {
name: string;
value?: string|string[];
Expand Down Expand Up @@ -271,9 +274,11 @@ function assertValidHeaders(headers: Record<string, unknown>):
asserts headers is Record<string, string|string[]> {
for (const [key, value] of Object.entries(headers)) {
if (typeof value !== 'string' && !Array.isArray(value)) {
throw new Error(
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
`Unexpected value of the \`${key}\` header provided. ` +
`Expecting either a string or an array, but got: \`${value}\`.`);
`Expecting either a string or an array, but got: \`${value}\`.` :
'';
throw new RuntimeError(RuntimeErrorCode.INVAILD_HTTP_HEADER, errorMessage);
}
}
}
16 changes: 12 additions & 4 deletions packages/common/http/src/jsonp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {DOCUMENT} from '@angular/common';
import {Inject, Injectable} from '@angular/core';
import {Observable, Observer} from 'rxjs';

import {RuntimeError} from '../../../core/src/errors';
import {RuntimeErrorCode} from '../../src/errors';

import {HttpBackend, HttpHandler} from './backend';
import {HttpRequest} from './request';
import {HttpErrorResponse, HttpEvent, HttpEventType, HttpResponse, HttpStatusCode} from './response';


// Every request made through JSONP needs a callback name that's unique across the
// whole page. Each request is assigned an id and the callback name is constructed
// from that. The next id to be assigned is tracked in a global variable here that
Expand Down Expand Up @@ -85,15 +87,21 @@ export class JsonpClientBackend implements HttpBackend {
// Firstly, check both the method and response type. If either doesn't match
// then the request was improperly routed here and cannot be handled.
if (req.method !== 'JSONP') {
throw new Error(JSONP_ERR_WRONG_METHOD);
const errorMessage =
(typeof ngDevMode === 'undefined' || ngDevMode) ? JSONP_ERR_WRONG_METHOD : '';
throw new RuntimeError(RuntimeErrorCode.INVALID_HTTP_REQUEST_METHOD, errorMessage);
} else if (req.responseType !== 'json') {
throw new Error(JSONP_ERR_WRONG_RESPONSE_TYPE);
const errorMessage =
(typeof ngDevMode === 'undefined' || ngDevMode) ? JSONP_ERR_WRONG_RESPONSE_TYPE : '';
throw new RuntimeError(RuntimeErrorCode.INVALID_HTTP_RESPONSE, errorMessage);
}

// Check the request headers. JSONP doesn't support headers and
// cannot set any that were supplied.
if (req.headers.keys().length > 0) {
throw new Error(JSONP_ERR_HEADERS_NOT_SUPPORTED);
const errorMessage =
(typeof ngDevMode === 'undefined' || ngDevMode) ? JSONP_ERR_HEADERS_NOT_SUPPORTED : '';
throw new RuntimeError(RuntimeErrorCode.UNEXPECTED_HTTP_JSON_HEADER, errorMessage);
}

// Everything else happens inside the Observable boundary.
Expand Down
8 changes: 7 additions & 1 deletion packages/common/http/src/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

import {RuntimeError} from '../../../core/src/errors';
import {RuntimeErrorCode} from '../../src/errors';

/**
* A codec for encoding and decoding parameters in URLs.
*
Expand Down Expand Up @@ -157,7 +160,10 @@ export class HttpParams {
this.encoder = options.encoder || new HttpUrlEncodingCodec();
if (!!options.fromString) {
if (!!options.fromObject) {
throw new Error(`Cannot specify both fromString and fromObject.`);
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
'Cannot specify both fromString and fromObject.' :
'';
throw new RuntimeError(RuntimeErrorCode.INVALID_HTTP_PARAMS, errorMessage);
}
this.map = paramParser(options.fromString, this.encoder);
} else if (!!options.fromObject) {
Expand Down
10 changes: 7 additions & 3 deletions packages/common/http/src/xhr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {XhrFactory} from '@angular/common';
import {Injectable} from '@angular/core';
import {Observable, Observer} from 'rxjs';

import {RuntimeError} from '../../../core/src/errors';
import {RuntimeErrorCode} from '../../src/errors';

import {HttpBackend} from './backend';
import {HttpHeaders} from './headers';
import {HttpRequest} from './request';
import {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpJsonParseError, HttpResponse, HttpStatusCode, HttpUploadProgressEvent} from './response';


const XSSI_PREFIX = /^\)\]\}',?\n/;

/**
Expand Down Expand Up @@ -52,8 +54,10 @@ export class HttpXhrBackend implements HttpBackend {
// Quick check to give a better error message when a user attempts to use
// HttpClient.jsonp() without installing the HttpClientJsonpModule
if (req.method === 'JSONP') {
throw new Error(
`Attempted to construct Jsonp request without HttpClientJsonpModule installed.`);
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
'Attempted to construct Jsonp request without HttpClientJsonpModule installed.' :
'';
throw new RuntimeError(RuntimeErrorCode.MISSING_HTTP_CLIENT_JSON_MODULE, errorMessage);
}

// Everything happens on Observable subscription.
Expand Down
5 changes: 4 additions & 1 deletion packages/common/http/test/client_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {HttpClient} from '@angular/common/http/src/client';
import {HttpErrorResponse, HttpEventType, HttpResponse, HttpStatusCode} from '@angular/common/http/src/response';
import {HttpClientTestingBackend} from '@angular/common/http/testing/src/backend';
import {RuntimeErrorCode} from '@angular/common/src/errors';
import {toArray} from 'rxjs/operators';

{
Expand Down Expand Up @@ -223,7 +224,9 @@ import {toArray} from 'rxjs/operators';
client.request('GET', '/test', {headers: {foo: null!}}).subscribe();
expect(() => backend.expectOne('/test').request.headers.has('random-header'))
.toThrowError(
'Unexpected value of the `foo` header provided. ' +
`NG0${
RuntimeErrorCode
.INVAILD_HTTP_HEADER}: Unexpected value of the \`foo\` header provided. ` +
'Expecting either a string or an array, but got: `null`.');
});
});
Expand Down
9 changes: 7 additions & 2 deletions packages/common/http/test/headers_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {HttpHeaders} from '@angular/common/http/src/headers';
import {RuntimeErrorCode} from '@angular/common/src/errors';

{
describe('HttpHeaders', () => {
Expand Down Expand Up @@ -53,7 +54,9 @@ import {HttpHeaders} from '@angular/common/http/src/headers';
const headers = new HttpHeaders({foo: null!});
expect(() => headers.get('foo'))
.toThrowError(
'Unexpected value of the `foo` header provided. ' +
`NG0${
RuntimeErrorCode
.INVAILD_HTTP_HEADER}: Unexpected value of the \`foo\` header provided. ` +
'Expecting either a string or an array, but got: `null`.');
});

Expand All @@ -63,7 +66,9 @@ import {HttpHeaders} from '@angular/common/http/src/headers';
const headers = new HttpHeaders({bar: undefined!});
expect(() => headers.get('bar'))
.toThrowError(
'Unexpected value of the `bar` header provided. ' +
`NG0${
RuntimeErrorCode
.INVAILD_HTTP_HEADER}: Unexpected value of the \`bar\` header provided. ` +
'Expecting either a string or an array, but got: `undefined`.');
});
});
Expand Down
15 changes: 10 additions & 5 deletions packages/common/http/test/jsonp_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {HttpHeaders} from '@angular/common/http/src/headers';
import {JSONP_ERR_HEADERS_NOT_SUPPORTED, JSONP_ERR_NO_CALLBACK, JSONP_ERR_WRONG_METHOD, JSONP_ERR_WRONG_RESPONSE_TYPE, JsonpClientBackend} from '@angular/common/http/src/jsonp';
import {HttpRequest} from '@angular/common/http/src/request';
import {HttpErrorResponse, HttpEventType} from '@angular/common/http/src/response';
import {RuntimeErrorCode} from '@angular/common/src/errors';
import {toArray} from 'rxjs/operators';

import {MockDocument} from './jsonp_mock';
Expand Down Expand Up @@ -78,14 +79,18 @@ const SAMPLE_REQ = new HttpRequest<never>('JSONP', '/test');
describe('throws an error', () => {
it('when request method is not JSONP',
() => expect(() => backend.handle(SAMPLE_REQ.clone<never>({method: 'GET'})))
.toThrowError(JSONP_ERR_WRONG_METHOD));
.toThrowError(`NG0${RuntimeErrorCode.INVALID_HTTP_REQUEST_METHOD}: ${
JSONP_ERR_WRONG_METHOD}`));
it('when response type is not json',
() => expect(() => backend.handle(SAMPLE_REQ.clone<never>({responseType: 'text'})))
.toThrowError(JSONP_ERR_WRONG_RESPONSE_TYPE));
.toThrowError(`NG0${RuntimeErrorCode.INVALID_HTTP_RESPONSE}: ${
JSONP_ERR_WRONG_RESPONSE_TYPE}`));
it('when headers are set in request',
() => expect(() => backend.handle(SAMPLE_REQ.clone<never>({
headers: new HttpHeaders({'Content-Type': 'application/json'})
}))).toThrowError(JSONP_ERR_HEADERS_NOT_SUPPORTED));
() => expect(
() => backend.handle(SAMPLE_REQ.clone<never>(
{headers: new HttpHeaders({'Content-Type': 'application/json'})})))
.toThrowError(`NG0${RuntimeErrorCode.UNEXPECTED_HTTP_JSON_HEADER}: ${
JSONP_ERR_HEADERS_NOT_SUPPORTED}`));
it('when callback is never called', done => {
backend.handle(SAMPLE_REQ).subscribe(undefined, (err: HttpErrorResponse) => {
expect(err.status).toBe(0);
Expand Down
9 changes: 9 additions & 0 deletions packages/common/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export const enum RuntimeErrorCode {
// NgForOf errors
NG_FOR_MISSING_DIFFER = -2200,

// HTTP Errors
INVALID_HTTP_RESPONSE = 2800,
INVAILD_HTTP_HEADER = 2801,
INVALID_HTTP_REQUEST_METHOD = 2802,
INVALID_HTTP_PARAMS = 2803,
UNHANDLED_HTTP_OBSERVE_TYPE = 2804,
UNEXPECTED_HTTP_JSON_HEADER = 2805,
MISSING_HTTP_CLIENT_JSON_MODULE = 2806,

// Image directive errors
UNEXPECTED_SRC_ATTR = 2950,
UNEXPECTED_SRCSET_ATTR = 2951,
Expand Down

0 comments on commit bfaf793

Please sign in to comment.