Commit e3cd5ac0 authored by Harold Boissenin's avatar Harold Boissenin
Browse files

initial commit

No related merge requests found
Showing with 514 additions and 0 deletions
+514 -0
README.md 0 → 100644
# @devatscience/errors
Source library to generate angular package ready for NPM
## How to
Once the changes are complete :
- Update the package.json file
- Increase version `"version": "0.1.1"`
- Run `npm build errors` to generate a **dist** folder that will contain all the required NPM files (typeScript & compiled es5 js)
- Move to the generated library **dist** folder `cd .../dist/errors`
- Check the package.json file
- Name should be `"name": "@devatscience/errors"`, version `"version": "0.1.1"`
- Run `npm publish --access public` to send it on npmjs
## Library content
- error-handler.service (requires `primeng/components/common/messageservice`)
- error.module
- generic-error.component
- not-found-error.component
- error types
- types/app.error
- types/clear-errors.function
- types/constraint-violations.class
- types/http.error
- types/interfaces
- types/normalize-error.function
- types/null.error
- types/type-guards.function
- types/unhandled.error
- types/validation.error
## Peer dependencies
"@angular/common": "^8.2.0",
"@angular/core": "^8.2.0",
"@angular/forms": "^8.2.0",
"@angular/router": "^8.2.0",
"lodash": "^4.17.15",
"primeng": "^8.0.2"
karma.conf.js 0 → 100644
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/error-types'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
{
"$schema": "node_modules/ng-packagr/ng-package.schema.json",
"dest": "dist/error-types",
"lib": {
"entryFile": "src/src.ts"
}
}
package.json 0 → 100644
{
"name": "@devatscience/errors",
"version": "0.1.0",
"peerDependencies": {
"@angular/common": "^8.2.0",
"@angular/core": "^8.2.0",
"@angular/forms": "^8.2.0",
"@angular/router": "^8.2.0",
"lodash": "^4.17.15",
"primeng": "^8.0.2"
}
}
import { ErrorHandler, Injectable } from '@angular/core';
import { MessageService } from 'primeng/components/common/messageservice';
@Injectable()
export class ErrorHandlerService implements ErrorHandler {
private readonly _console: Console = console;
public constructor(private readonly messageService: MessageService) {}
public handleError(error: any): void {
const errorLogger = this.getErrorLogger(error);
const original = this.getOriginalError(error);
const context = this.getDebugContext(error);
const message = (original || error).message || `${error}`;
errorLogger(this._console, 'ERROR', message);
if (original) {
errorLogger(this._console, 'ORIGINAL ERROR', original);
}
if (context) {
errorLogger(this._console, 'CONTEXT', context);
}
this.messageService.add({
severity: 'error',
summary: (original || error).constructor.name,
detail: message,
life: 15,
closable: true,
data: error,
});
}
private getDebugContext(error?: Error): any {
return error ? (error as any).ngDebugContext || this.getDebugContext(this.getOriginalError(error)) : null;
}
private getOriginalError(error: Error): Error {
let original = error;
while (original) {
original = (original as any).ngOriginalError;
}
return original;
}
private getErrorLogger(error: Error): (console: Console, ...values: any[]) => void {
return (error as any).ngErrorLogger || this.defaultErrorLogger;
}
private defaultErrorLogger(console: Console, ...values: any[]) {
(<any>console.error)(...values);
}
}
import { ErrorHandler, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ErrorHandlerService } from './error-handler.service';
import { GenericErrorComponent } from './generic-error.component';
import { NotFoundErrorComponent } from './not-found-error.component';
const routes: Routes = [
{
path: 'error/404',
pathMatch: 'full',
component: NotFoundErrorComponent,
},
{ path: 'error/**', component: GenericErrorComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [{ provide: ErrorHandler, useClass: ErrorHandlerService }],
declarations: [GenericErrorComponent, NotFoundErrorComponent],
})
export class ErrorModule {}
<div class="card ui-message ui-messages-error">
<h1>
<i class="ui-messages-icon fa fa-exclamation-triangle"></i>
<span i18n class="ui-messages-summary">Erreur !</span>
</h1>
<p i18n class="ui-messages-detail">Une erreur s'est produite !</p>
</div>
import { Component } from '@angular/core';
@Component({
selector: 'lib-generic-error',
templateUrl: './generic-error.component.html',
})
export class GenericErrorComponent {}
export * from './error-handler.service';
export * from './error.module';
export * from './generic-error.component';
export * from './not-found-error.component';
<div class="card ui-message ui-messages-error">
<h1>
<i class="ui-messages-icon fa fa-exclamation-triangle"></i>
<span i18n class="ui-messages-summary">Page non trouvée !</span>
</h1>
<p i18n class="ui-messages-detail">La ressource n'a pas été trouvée.</p>
</div>
import { Component } from '@angular/core';
@Component({
selector: 'lib-not-found-error',
templateUrl: './not-found-error.component.html',
})
export class NotFoundErrorComponent {}
import { AppError } from './app.error';
describe('AppError', () => {
let baseErr: Error;
let err: AppError<Error>;
beforeEach(() => {
baseErr = new Error('Bam !');
err = new AppError(baseErr, baseErr.message, baseErr.stack, false);
});
it('.isNullError should be false', () => {
expect(err.isNullError).toBe(false);
});
describe('.dispatch()', () => {
it('should set error message', () => {
const handler = jasmine.createSpyObj('handler', ['setError']);
handler.setError.and.returnValue(true);
expect(err.dispatch(handler)).toBeTruthy();
expect(handler.setError).toHaveBeenCalledWith('Bam !');
});
it('should set validation errors as fallback', () => {
const handler = jasmine.createSpyObj('handler', ['setErrors']);
handler.setErrors.and.returnValue(true);
expect(err.dispatch(handler)).toBeTruthy();
expect(handler.setErrors).toHaveBeenCalledWith({ message: 'Bam !' });
});
it('should log error', () => {
const handler = jasmine.createSpyObj('handler', ['logError']);
handler.logError.and.returnValue(true);
expect(err.dispatch(handler)).toBeTruthy();
expect(handler.logError).toHaveBeenCalledWith('Bam !', 'Error', baseErr.stack);
});
});
});
import { DispatchableError } from './interfaces';
import { isErrorLogger, isErrorMessageHandler, isValidationErrorHandler } from './type-guards.function';
/**
* Erreur "enveloppant" une autre erreur.
*/
export class AppError<T> implements DispatchableError {
public readonly isNullError = false;
public constructor(
public readonly original: T,
public readonly message: string,
public readonly stack?: string,
public readonly transient = false
) {}
public get name(): string {
return this.getName();
}
public dispatch(handler: any): boolean {
return (
(isErrorLogger(handler) && handler.logError(this.message, this.name, this.stack)) ||
(isErrorMessageHandler(handler) && handler.setError(this.message)) ||
(isValidationErrorHandler(handler) && handler.setErrors({ message: this.message }))
);
}
protected getName(): string {
return (this.original instanceof Error && this.original.name) || this.constructor.name;
}
}
import { clearErrors } from './clear-errors.function';
describe('clearErrors', () => {
it('should clear errors using all ErrorHandler methods', () => {
const handler = jasmine.createSpyObj('handler', ['setError', 'setConstraintViolations', 'setErrors']);
handler.setError.and.returnValue(true);
handler.setErrors.and.returnValue(true);
handler.setConstraintViolations.and.returnValue(true);
expect(clearErrors(handler)).toBeTruthy();
expect(handler.setError).toHaveBeenCalledWith(null);
expect(handler.setErrors).toHaveBeenCalledWith(null);
expect(handler.setConstraintViolations).toHaveBeenCalledWith(null);
});
it('should prefer clearErrors', () => {
const handler = jasmine.createSpyObj('handler', [
'setError',
'setConstraintViolations',
'setErrors',
'clearErrors',
]);
handler.clearErrors.and.returnValue(true);
expect(clearErrors(handler)).toBeTruthy();
expect(handler.clearErrors).toHaveBeenCalled();
expect(handler.setError).not.toHaveBeenCalled();
expect(handler.setErrors).not.toHaveBeenCalled();
expect(handler.setConstraintViolations).not.toHaveBeenCalled();
});
});
import {
isConstraintViolationHandler,
isErrorClearer,
isErrorMessageHandler,
isValidationErrorHandler,
} from './type-guards.function';
/**
* Supprime les erreurs d'un handler.
*/
export function clearErrors(handler: any): boolean {
if (isErrorClearer(handler) && handler.clearErrors()) {
return true;
}
let handled = false;
if (isErrorMessageHandler(handler) && handler.setError(null)) {
handled = true;
}
if (isValidationErrorHandler(handler) && handler.setErrors(null)) {
handled = true;
}
if (isConstraintViolationHandler(handler) && handler.setConstraintViolations(null)) {
handled = true;
}
return handled;
}
import { HttpErrorResponse } from '@angular/common/http';
import { ConstraintViolations } from './constraint-violations.class';
describe('ConstraintViolations', () => {
const violations = [
{
propertyPath: 'name',
message: 'This propperty cannot be blank',
},
];
let err: ConstraintViolations;
beforeEach(() => {
const orig = new HttpErrorResponse({
error: { violations },
status: 400,
statusText: 'Constraint violations',
});
err = new ConstraintViolations(orig, orig.statusText, orig.error.violations);
});
it('.isNullError should be false', () => {
expect(err.isNullError).toBe(false);
});
describe('.dispatch()', () => {
it('should set error message', () => {
const handler = jasmine.createSpyObj('handler', ['setError']);
handler.setError.and.returnValue(true);
expect(err.dispatch(handler)).toBeTruthy();
expect(handler.setError).toHaveBeenCalledWith('Constraint violations');
});
it('should set constraint violations', () => {
const handler = jasmine.createSpyObj('handler', ['setConstraintViolations']);
handler.setConstraintViolations.and.returnValue(true);
expect(err.dispatch(handler)).toBeTruthy();
expect(handler.setConstraintViolations).toHaveBeenCalledWith(violations);
});
});
});
import { HttpErrorResponse } from '@angular/common/http';
import { BadRequestError } from './http.error';
import { ConstraintViolation } from './interfaces';
import { isConstraintViolationHandler } from './type-guards.function';
/**
* Erreur de validation retournée par api-platform.
*/
export class ConstraintViolations extends BadRequestError {
public constructor(original: HttpErrorResponse, message: string, public readonly violations: ConstraintViolation[]) {
super(original, message);
}
public dispatch(handler: any): boolean {
return (
(isConstraintViolationHandler(handler) && handler.setConstraintViolations(this.violations)) ||
super.dispatch(handler)
);
}
public static create(violations: ConstraintViolation[]): ConstraintViolations {
return new ConstraintViolations(
new HttpErrorResponse({
status: 400,
statusText: 'Constraint violations',
}),
'Constraint violations',
violations
);
}
}
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { AppError } from './app.error';
/**
* Erreur basée sur une réponse HTTP d'erreur.
*/
export class HTTPError extends AppError<HttpErrorResponse> {
public constructor(original: HttpErrorResponse, message?: string) {
super(original, message || original.message || original.statusText, undefined, original.status >= 500);
}
public get status(): number {
return this.original.status;
}
public get statusText(): string {
return this.original.statusText;
}
public get headers(): HttpHeaders {
return this.original.headers;
}
public get url(): string {
return this.original.url;
}
public get error(): string {
return this.original.error;
}
}
/**
* Erreur 403: Forbidden
*/
export class AccessDeniedError extends HTTPError {}
/**
* Erreur 401: Authentication Required
*/
export class AuthenticationError extends HTTPError {}
/**
* Erreur 400: Bad Request
*/
export class BadRequestError extends HTTPError {}
export * from './app.error';
export * from './clear-errors.function';
export * from './constraint-violations.class';
export * from './http.error';
export * from './interfaces';
export * from './normalize-error.function';
export * from './null.error';
export * from './type-guards.function';
export * from './unhandled.error';
export * from './validation.error';
import { ValidationErrors } from '@angular/forms';
/**
* Rapport d'erreur de validation
*/
export interface ConstraintViolation {
readonly propertyPath: string;
readonly message: string;
}
/**
* Types de gestionnaires d'erreurs.
*/
export interface ErrorMessageHandler {
setError(message: string | null): boolean;
}
export interface ConstraintViolationHandler {
setConstraintViolations(violations: ConstraintViolation[] | null): boolean;
}
export interface ValidationErrorHandler {
setErrors(errors: ValidationErrors | null): boolean;
}
export interface ErrorLogger {
logError(message: string, name: string, stack?: string, context?: any): boolean;
}
export interface ErrorClearer {
clearErrors(): boolean;
}
export type ErrorHandler = ErrorMessageHandler | ConstraintViolationHandler | ValidationErrorHandler | ErrorLogger;
export interface DispatchableError extends Error {
readonly isNullError: boolean;
dispatch(handler: ErrorHandler): boolean;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment