Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.1.0] Post-Execution-Functions will now be executed, even if the handler failed #333

Merged
merged 1 commit into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
node_modules
dist
*.test.ts
18 changes: 6 additions & 12 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
module.exports = {
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
parserOptions: {
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
ecmaFeatures: {
}
},
settings: {
},
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
rules: {
sourceType: 'module', // Allows for the use of imports
ecmaFeatures: {},
},
settings: {},
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
rules: {},
};
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

strategy:
matrix:
node-version: [ 15.x, 16.x ]
node-version: [ 16.x, 18.x ]

steps:
- uses: actions/checkout@v4
Expand Down
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ package-lock.json
*.ejs
dist
coverage
dist/
config/*
5 changes: 4 additions & 1 deletion .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ module.exports = {
trailingComma: "all",
singleQuote: true,
printWidth: 120,
tabWidth: 4
tabWidth: 4,
importOrder: ["^[./]"],
importOrderSeparation: true,
importOrderSortSpecifiers: true
};
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.1.0 (19.09.2023)
+ Post-Execution-Functions will now be executed, even if the handler failed
- The middlewareWithErrorHandling was removed. To regain the same functionality you can now pass an option-flag, called disableErrorHandling

## 2.0.0 (01.04.2023)
- Added auto-logging functionality to the library that enhances searchability of saved log statements in Azure AppInsights by storing context properties in commonProperties.
- Removed the MiddlewareFunction-Type in favor for the azure-built-in one
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Context } from '@azure/functions';
import middleware from '../../src/middleware';

import { middleware } from '../../src';
import headerAuthentication from '../../src/headerAuthentication';

const functionHandler = async (context: Context): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Context, ContextBindingData } from '@azure/functions';
import middleware from '../../src/middleware';

import { middleware } from '../../src';
import authorization from '../../src/jwtAuthorization';

const functionHandler = async (context: Context): Promise<void> => {
Expand Down
5 changes: 3 additions & 2 deletions example/test-validation-function/test-validation-function.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Context, HttpRequest } from '@azure/functions';
import middleware from '../../src/middleware';
import * as Joi from 'joi';
import validation from '../../src/validation';
import { ObjectSchema } from 'joi';

import { middleware } from '../../src';
import validation from '../../src/validation';

const schema: ObjectSchema = Joi.object({
name: Joi.string().min(3).max(30).required(),
}).required();
Expand Down
1 change: 1 addition & 0 deletions integration-test/header-authentication-test.integration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';

import waitTillFunctionReady from './waitTillFunctionReady';

describe('The example azure function is started and the header authentication should execute the request', () => {
Expand Down
1 change: 1 addition & 0 deletions integration-test/jwt-authorization-test.integration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';

import waitTillFunctionReady from './waitTillFunctionReady';

// Token generated with https://jwt.io/ containing the "userId" "c8e65ca7-a008-4b1c-b52a-4ad0ee417017"
Expand Down
1 change: 1 addition & 0 deletions integration-test/validation-test.integration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';

import waitTillFunctionReady from './waitTillFunctionReady';

describe('The example azure function is started and the JOI validation should', () => {
Expand Down
100 changes: 98 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@senacor/azure-function-middleware",
"version": "2.0.1",
"version": "2.1.0",
"description": "Middleware for azure functions to handle authentication, authorization, error handling and logging",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -25,6 +25,7 @@
"author": "[email protected]",
"license": "MIT",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
Expand Down
4 changes: 2 additions & 2 deletions src/appInsights/Logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SeverityLevel } from 'applicationinsights/out/Declarations/Contracts';
import { TelemetryClient } from 'applicationinsights';
import { Logger } from '@azure/functions';
import { TelemetryClient } from 'applicationinsights';
import { SeverityLevel } from 'applicationinsights/out/Declarations/Contracts';

const consoleDefaultLog = (message: string): void => {
console.log(message);
Expand Down
3 changes: 2 additions & 1 deletion src/appInsights/appInsightsWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Context, HttpRequest } from '@azure/functions';
import * as appInsights from 'applicationinsights';
import { createAppInsightsLogger, consoleLogger } from './Logger';
import { TelemetryClient } from 'applicationinsights';

import { consoleLogger, createAppInsightsLogger } from './Logger';

const telemetryClients: { [key: string]: TelemetryClient } = {};

const isDisabled =
Expand Down Expand Up @@ -49,7 +50,7 @@
context.log = createAppInsightsLogger(telemetryClient);

// excluding headers because it contains sensible data
const { headers, ...includedProperties } = context.bindingData;

Check warning on line 53 in src/appInsights/appInsightsWrapper.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

'headers' is assigned a value but never used

Check warning on line 53 in src/appInsights/appInsightsWrapper.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'headers' is assigned a value but never used

telemetryClient.commonProperties = {
...includedProperties,
Expand Down
File renamed without changes.
39 changes: 39 additions & 0 deletions src/error/errorHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Context } from '@azure/functions';
import { mock } from 'jest-mock-extended';

import { ApplicationError } from './ApplicationError';
import { errorHandler as sut } from './errorHandler';

describe('Error-Handler should', () => {
const contextMock = mock<Context>();

beforeEach(() => {
jest.restoreAllMocks();

contextMock.log.error = jest.fn();
});

test('return an provided ApplicationError', () => {
const res = sut(new ApplicationError('', 400, { error: 'critical' }), contextMock);
expect(res.status).toStrictEqual(400);
expect(res.body).toStrictEqual({ error: 'critical' });
});

test('return an default error-message, if no errorResponseHandler was provided', () => {
const res = sut('Error!', contextMock);
expect(res.status).toStrictEqual(500);
expect(res.body).toStrictEqual({ message: 'Internal server error' });
});

test('use the errorResponseHandler, if an errorResponseHandler was provided', () => {
const errorResponseHandler = () => ({
status: 409,
body: {
message: 'conflict!',
},
});
const res = sut('Error!', contextMock, { errorResponseHandler });
expect(res.status).toStrictEqual(409);
expect(res.body).toStrictEqual({ message: 'conflict!' });
});
});
79 changes: 79 additions & 0 deletions src/error/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Context } from '@azure/functions';

import { Options } from '../middleware';
import { ApplicationError } from './ApplicationError';

type ErrorWithMessage = {
message: string;
stack?: string;
};

const isErrorWithMessage = (error: unknown): error is ErrorWithMessage => {
return (
typeof error === 'object' &&
error !== null &&
'message' in error &&
typeof (error as Record<string, unknown>).message === 'string'
);
};

const logErrorObject = (error: object | null, context: Context) => {
if (error === null) {
context.log.error('The provided error was eq to null - unable to log a specific error-message');
return;
}

if (isErrorWithMessage(error)) {
context.log.error({ message: error.message, stack: error.stack });
} else {
try {
const errorAsJson = JSON.stringify(error);
if (errorAsJson === '{}') {
context.log.error(error.toString());
} else {
context.log.error(errorAsJson); // Log the JSON string of the error object
}
maaaNu marked this conversation as resolved.
Show resolved Hide resolved
} catch (_) {
//Fallback in case there's an error stringify
context.log.error(error.toString());
}
}
};

export const errorHandler = (
error: unknown,
context: Context,
opts?: Options,
): {
[key: string]: unknown;
} => {
if (error instanceof ApplicationError) {
context.log.error(`Received application error with message ${error.message}`);
return {
status: error.status,
body: error.body,
};
}

switch (typeof error) {
case 'string':
context.log.error(error);
break;
case 'object':
logErrorObject(error, context);
break;
default:
context.log(`The error object has a type, that is not suitable for logging: ${typeof error}`);
}

if (opts?.errorResponseHandler === undefined) {
return {
status: 500,
body: {
message: 'Internal server error',
},
};
} else {
return opts.errorResponseHandler(error);
}
};
Loading