ESLint: fix rxjs/no-implicit-any-catch

In most cases we can deal with the untyped errors by introducing an explicit instanceof check

DspaceRestService includes an unsafe catch/rethrow → made it explicitly typed as `any`, but it should be revisited at some point
This commit is contained in:
Yury Bondarenko
2023-06-28 16:30:38 +02:00
parent 07259ca342
commit c0f43bc585
17 changed files with 106 additions and 76 deletions

View File

@@ -427,6 +427,15 @@ export class UnsetUserAsIdleAction implements Action {
public type: string = AuthActionTypes.UNSET_USER_AS_IDLE;
}
/**
* Authentication error actions that include Error payloads.
*/
export type AuthErrorActionsWithErrorPayload
= AuthenticatedErrorAction
| AuthenticationErrorAction
| LogOutErrorAction
| RetrieveAuthenticatedEpersonErrorAction;
/**
* Actions type.
* @type {AuthActions}
@@ -434,9 +443,7 @@ export class UnsetUserAsIdleAction implements Action {
export type AuthActions
= AuthenticateAction
| AuthenticatedAction
| AuthenticatedErrorAction
| AuthenticatedSuccessAction
| AuthenticationErrorAction
| AuthenticationSuccessAction
| CheckAuthenticationTokenAction
| CheckAuthenticationTokenCookieAction
@@ -453,10 +460,9 @@ export type AuthActions
| RetrieveAuthMethodsErrorAction
| RetrieveTokenAction
| RetrieveAuthenticatedEpersonAction
| RetrieveAuthenticatedEpersonErrorAction
| RetrieveAuthenticatedEpersonSuccessAction
| SetRedirectUrlAction
| RedirectAfterLoginSuccessAction
| SetUserAsIdleAction
| UnsetUserAsIdleAction;
| UnsetUserAsIdleAction
| AuthErrorActionsWithErrorPayload;

View File

@@ -1,6 +1,7 @@
import {
Injectable,
NgZone,
Type,
} from '@angular/core';
// import @ngrx
import {
@@ -50,6 +51,7 @@ import {
AuthenticatedSuccessAction,
AuthenticationErrorAction,
AuthenticationSuccessAction,
AuthErrorActionsWithErrorPayload,
CheckAuthenticationTokenCookieAction,
LogOutErrorAction,
LogOutSuccessAction,
@@ -81,6 +83,16 @@ const IDLE_TIMER_IGNORE_TYPES: string[]
= [...Object.values(AuthActionTypes).filter((t: string) => t !== AuthActionTypes.UNSET_USER_AS_IDLE),
...Object.values(RequestActionTypes), ...Object.values(NotificationsActionTypes)];
export function errorToAuthAction$<T extends AuthErrorActionsWithErrorPayload>(actionType: Type<T>, error: unknown): Observable<T> {
if (error instanceof Error) {
return observableOf(new actionType(error));
}
// If we caught something that's not an Error: complain & drop type safety
console.warn('AuthEffects caught non-Error object:', error);
return observableOf(new actionType(error as any));
}
@Injectable()
export class AuthEffects {
@@ -94,7 +106,7 @@ export class AuthEffects {
return this.authService.authenticate(action.payload.email, action.payload.password).pipe(
take(1),
map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)),
catchError((error) => observableOf(new AuthenticationErrorAction(error))),
catchError((error: unknown) => errorToAuthAction$(AuthenticationErrorAction, error)),
);
}),
));
@@ -109,7 +121,8 @@ export class AuthEffects {
switchMap((action: AuthenticatedAction) => {
return this.authService.authenticatedUser(action.payload).pipe(
map((userHref: string) => new AuthenticatedSuccessAction((userHref !== null), action.payload, userHref)),
catchError((error) => observableOf(new AuthenticatedErrorAction(error))));
catchError((error: unknown) => errorToAuthAction$(AuthenticatedErrorAction, error)),
);
}),
));
@@ -155,7 +168,8 @@ export class AuthEffects {
}
return user$.pipe(
map((user: EPerson) => new RetrieveAuthenticatedEpersonSuccessAction(user.id)),
catchError((error) => observableOf(new RetrieveAuthenticatedEpersonErrorAction(error))));
catchError((error: unknown) => errorToAuthAction$(RetrieveAuthenticatedEpersonErrorAction, error)),
);
}),
));
@@ -163,7 +177,7 @@ export class AuthEffects {
switchMap(() => {
return this.authService.hasValidAuthenticationToken().pipe(
map((token: AuthTokenInfo) => new AuthenticatedAction(token)),
catchError((error) => observableOf(new CheckAuthenticationTokenCookieAction())),
catchError((error: unknown) => observableOf(new CheckAuthenticationTokenCookieAction())),
);
}),
));
@@ -181,7 +195,7 @@ export class AuthEffects {
return new RetrieveAuthMethodsAction(response);
}
}),
catchError((error) => observableOf(new AuthenticatedErrorAction(error))),
catchError((error: unknown) => errorToAuthAction$(AuthenticatedErrorAction, error)),
);
}),
));
@@ -192,7 +206,7 @@ export class AuthEffects {
return this.authService.refreshAuthenticationToken(null).pipe(
take(1),
map((token: AuthTokenInfo) => new AuthenticationSuccessAction(token)),
catchError((error) => observableOf(new AuthenticationErrorAction(error))),
catchError((error: unknown) => errorToAuthAction$(AuthenticationErrorAction, error)),
);
}),
));
@@ -201,7 +215,7 @@ export class AuthEffects {
switchMap((action: RefreshTokenAction) => {
return this.authService.refreshAuthenticationToken(action.payload).pipe(
map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)),
catchError((error) => observableOf(new RefreshTokenErrorAction())),
catchError((error: unknown) => observableOf(new RefreshTokenErrorAction())),
);
}),
));
@@ -245,8 +259,8 @@ export class AuthEffects {
switchMap(() => {
this.authService.stopImpersonating();
return this.authService.logout().pipe(
map((value) => new LogOutSuccessAction()),
catchError((error) => observableOf(new LogOutErrorAction(error))),
map(() => new LogOutSuccessAction()),
catchError((error: unknown) => errorToAuthAction$(LogOutErrorAction, error)),
);
}),
));
@@ -272,7 +286,7 @@ export class AuthEffects {
return this.authService.retrieveAuthMethodsFromAuthStatus(action.payload)
.pipe(
map((authMethodModels: AuthMethod[]) => new RetrieveAuthMethodsSuccessAction(authMethodModels)),
catchError((error) => observableOf(new RetrieveAuthMethodsErrorAction())),
catchError(() => observableOf(new RetrieveAuthMethodsErrorAction())),
);
}),
));

View File

@@ -296,7 +296,7 @@ export class AuthInterceptor implements HttpInterceptor {
return response;
}
}),
catchError((error, caught) => {
catchError((error: unknown, caught) => {
// Intercept an error response
if (error instanceof HttpErrorResponse) {

View File

@@ -58,8 +58,8 @@ export class RequestEffects {
return this.restApi.request(request.method, request.href, body, request.options, request.isMultipart).pipe(
map((data: RawRestResponse) => this.injector.get(request.getResponseParser()).parse(request, data)),
map((response: ParsedResponse) => new RequestSuccessAction(request.uuid, response.statusCode, response.link, response.unCacheableObject)),
catchError((error: RequestError) => {
if (hasValue(error.statusCode)) {
catchError((error: unknown) => {
if (error instanceof RequestError) {
// if it's an error returned by the server, complete the request
return [new RequestErrorAction(request.uuid, error.statusCode, error.message)];
} else {

View File

@@ -42,7 +42,7 @@ export class RootDataService extends BaseDataService<Root> {
*/
checkServerAvailability(): Observable<boolean> {
return this.restService.get(this.halService.getRootHref()).pipe(
catchError((err ) => {
catchError((err: unknown) => {
console.error(err);
return observableOf(false);
}),

View File

@@ -39,7 +39,7 @@ export class SignpostingDataService {
const baseUrl = `${this.appConfig.rest.baseUrl}`;
return this.restService.get(`${baseUrl}/signposting/links/${uuid}`).pipe(
catchError((err) => {
catchError((err: unknown) => {
return observableOf([]);
}),
map((res: RawRestResponse) => res.statusCode === 200 ? res.payload as SignpostingLink[] : []),

View File

@@ -1,4 +1,7 @@
import { HttpHeaders } from '@angular/common/http';
import {
HttpErrorResponse,
HttpHeaders,
} from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController,
@@ -19,11 +22,14 @@ describe('DspaceRestService', () => {
let dspaceRestService: DspaceRestService;
let httpMock: HttpTestingController;
const url = 'http://www.dspace.org/';
const mockError: any = {
statusCode: 0,
const mockError = new HttpErrorResponse({
status: 0,
statusText: 'Unknown Error',
message: 'Http failure response for http://www.dspace.org/: 0 ',
};
error: {
message: 'Http failure response for http://www.dspace.org/: 0 ',
},
});
beforeEach(() => {
TestBed.configureTestingModule({
@@ -61,24 +67,28 @@ describe('DspaceRestService', () => {
req.flush(mockPayload, { status: mockStatusCode, statusText: mockStatusText });
});
it('should throw an error', () => {
dspaceRestService.get(url).subscribe(() => undefined, (err) => {
expect(err).toEqual(mockError);
dspaceRestService.get(url).subscribe(() => undefined, (err: unknown) => {
expect(err).toEqual(jasmine.objectContaining({
statusCode: 0,
statusText: 'Unknown Error',
message: 'Http failure response for http://www.dspace.org/: 0 ',
}));
});
const req = httpMock.expectOne(url);
expect(req.request.method).toBe('GET');
req.error(mockError);
req.error({ error: mockError } as ErrorEvent);
});
it('should log an error', () => {
spyOn(console, 'log');
dspaceRestService.get(url).subscribe(() => undefined, (err) => {
dspaceRestService.get(url).subscribe(() => undefined, (err: unknown) => {
expect(console.log).toHaveBeenCalled();
});
const req = httpMock.expectOne(url);
expect(req.request.method).toBe('GET');
req.error(mockError);
req.error({ error: mockError } as ErrorEvent);
});
it('when no content-type header is provided, it should use application/json', () => {

View File

@@ -1,8 +1,8 @@
import {
HttpClient,
HttpErrorResponse,
HttpHeaders,
HttpParams,
HttpResponse,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
@@ -16,9 +16,9 @@ import {
import {
hasNoValue,
hasValue,
isNotEmpty,
} from '../../shared/empty.util';
import { RequestError } from '../data/request-error.model';
import { RestRequestMethod } from '../data/rest-request-method';
import { DSpaceObject } from '../shared/dspace-object.model';
import { RawRestResponse } from './raw-rest-response.model';
@@ -53,24 +53,7 @@ export class DspaceRestService {
* An Observable<string> containing the response from the server
*/
get(absoluteURL: string): Observable<RawRestResponse> {
const requestOptions = {
observe: 'response' as any,
headers: new HttpHeaders({ 'Content-Type': DEFAULT_CONTENT_TYPE }),
};
return this.http.get(absoluteURL, requestOptions).pipe(
map((res: HttpResponse<any>) => ({
payload: res.body,
statusCode: res.status,
statusText: res.statusText,
})),
catchError((err) => {
console.log('Error: ', err);
return observableThrowError({
statusCode: err.status,
statusText: err.statusText,
message: (hasValue(err.error) && isNotEmpty(err.error.message)) ? err.error.message : err.message,
});
}));
return this.request(RestRequestMethod.GET, absoluteURL);
}
/**
@@ -126,17 +109,23 @@ export class DspaceRestService {
statusCode: res.status,
statusText: res.statusText,
})),
catchError((err) => {
if (hasValue(err.status)) {
return observableThrowError({
statusCode: err.status,
statusText: err.statusText,
message: (hasValue(err.error) && isNotEmpty(err.error.message)) ? err.error.message : err.message,
});
catchError((err: unknown) => observableThrowError(() => {
console.log('Error: ', err);
if (err instanceof HttpErrorResponse) {
const error = new RequestError(
(isNotEmpty(err?.error?.message)) ? err.error.message : err.message,
);
error.statusCode = err.status;
error.statusText = err.statusText;
return error;
} else {
return observableThrowError(err);
console.error('Cannot construct RequestError from', err);
return err;
}
}));
})),
);
}
/**

View File

@@ -47,7 +47,7 @@ export class LinkHeadService {
renderer.appendChild(head, link);
return renderer;
} catch (e) {
console.error('Error within linkService : ', e);
console.error('Error within linkService: ', e);
}
}
@@ -73,7 +73,9 @@ export class LinkHeadService {
renderer.removeChild(head, link);
}
} catch (e) {
console.log('Error while removing tag ' + e.message);
if (e instanceof Error) {
console.error('Error while removing tag: ' + e.message);
}
}
}
}

View File

@@ -11,6 +11,7 @@ import { TestBed } from '@angular/core/testing';
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
import { HttpXsrfTokenExtractorMock } from '../../shared/mocks/http-xsrf-token-extractor.mock';
import { RequestError } from '../data/request-error.model';
import { RestRequestMethod } from '../data/rest-request-method';
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
import { CookieService } from '../services/cookie.service';
@@ -153,12 +154,13 @@ describe(`XsrfInterceptor`, () => {
const mockErrorMessage = 'CSRF token mismatch';
service.request(RestRequestMethod.GET, 'server/api/core/items').subscribe({
error: (error) => {
error: (error: unknown) => {
expect(error).toBeTruthy();
expect(error instanceof RequestError).toBeTrue();
// ensure mock error (added in below flush() call) is returned.
expect(error.statusCode).toBe(mockErrorCode);
expect(error.statusText).toBe(mockErrorText);
expect((error as RequestError).statusCode).toBe(mockErrorCode);
expect((error as RequestError).statusText).toBe(mockErrorText);
// ensure our XSRF-TOKEN cookie exists & has the same value as the new DSPACE-XSRF-TOKEN header
expect(cookieService.get('XSRF-TOKEN')).not.toBeNull();

View File

@@ -102,7 +102,7 @@ export class XsrfInterceptor implements HttpInterceptor {
}
}
}),
catchError((error) => {
catchError((error: unknown) => {
if (error instanceof HttpErrorResponse) {
// For every error that comes back, also check for the custom
// DSPACE-XSRF-TOKEN header sent from the backend.

View File

@@ -180,7 +180,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
filter((item: Item) => isNotEmpty(item.bundles)),
mergeMap((item: Item) => item.bundles),
getFirstSucceededRemoteDataWithNotEmptyPayload(),
catchError((error) => {
catchError((error: unknown) => {
console.error(error);
return observableOf(buildPaginatedList(null, []));
}),
@@ -229,7 +229,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
private getBundleBitstreams(bundle: Bundle): Observable<PaginatedList<Bitstream>> {
return bundle.bitstreams.pipe(
getFirstSucceededRemoteDataPayload(),
catchError((error) => {
catchError((error: unknown) => {
console.error(error);
return observableOf(buildPaginatedList(null, []));
}),

View File

@@ -204,7 +204,7 @@ export class SubscriptionModalComponent implements OnInit {
}
this.processing$.next(false);
},
error: err => {
error: (err: unknown) => {
this.processing$.next(false);
},
});

View File

@@ -262,7 +262,7 @@ export class SubmissionObjectEffects {
switchMap(([action, state]: [DepositSubmissionAction, any]) => {
return this.submissionService.depositSubmission(state.submission.objects[action.payload.submissionId].selfUrl).pipe(
map(() => new DepositSubmissionSuccessAction(action.payload.submissionId)),
catchError((error) => observableOf(new DepositSubmissionErrorAction(action.payload.submissionId))));
catchError((error: unknown) => observableOf(new DepositSubmissionErrorAction(action.payload.submissionId))));
})));
/**

View File

@@ -341,7 +341,9 @@ export class SubmissionSectionFormComponent extends SectionModelComponent {
message: msg,
path: '/sections/' + this.sectionData.id,
};
console.error(e.stack);
if (e instanceof Error) {
console.error(e.stack);
}
this.sectionService.setSectionError(this.submissionId, this.sectionData.id, sectionError);
}
}

View File

@@ -29,7 +29,9 @@ import { TestScheduler } from 'rxjs/testing';
import { environment } from '../../environments/environment';
import { storeModuleConfig } from '../app.reducer';
import { ErrorResponse } from '../core/cache/response.models';
import { RequestService } from '../core/data/request.service';
import { RequestError } from '../core/data/request-error.model';
import { HttpOptions } from '../core/dspace-rest/dspace-rest.service';
import { RouteService } from '../core/services/route.service';
import { Item } from '../core/shared/item.model';
@@ -959,11 +961,12 @@ describe('SubmissionService test suite', () => {
});
it('should catch error from REST endpoint', () => {
const requestError = new RequestError('Internal Server Error');
requestError.statusCode = 500;
const errorResponse = new ErrorResponse(requestError);
(service as any).restService.getDataById.and.callFake(
() => observableThrowError({
statusCode: 500,
errorMessage: 'Internal Server Error',
}),
() => observableThrowError(errorResponse),
);
service.retrieveSubmission('826').subscribe((r) => {

View File

@@ -576,8 +576,10 @@ export class SubmissionService {
find((submissionObjects: SubmissionObject[]) => isNotUndefined(submissionObjects)),
map((submissionObjects: SubmissionObject[]) => createSuccessfulRemoteDataObject(
submissionObjects[0])),
catchError((errorResponse: ErrorResponse) => {
return createFailedRemoteDataObject$<SubmissionObject>(errorResponse.errorMessage, errorResponse.statusCode);
catchError((errorResponse: unknown) => {
if (errorResponse instanceof ErrorResponse) {
return createFailedRemoteDataObject$<SubmissionObject>(errorResponse.errorMessage, errorResponse.statusCode);
}
}),
);
}