fix(HTTP Request Node): Support for dot notation in JSON body

This commit is contained in:
Michael Kret 2023-03-31 19:31:03 +03:00 committed by GitHub
parent d87736103d
commit b29cf9a249
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 7 deletions

View file

@ -1,6 +1,10 @@
import type { IDataObject, INodeExecutionData, IOAuth2Options } from 'n8n-workflow';
import type { OptionsWithUri } from 'request-promise-native';
import set from 'lodash.set';
export type BodyParameter = { name: string; value: string };
export type IAuthDataSanitizeKeys = {
[key: string]: string[];
};
@ -130,3 +134,25 @@ export const binaryContentTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/x-7z-compressed',
];
export type BodyParametersReducer = (
acc: IDataObject,
cur: { name: string; value: string },
) => IDataObject;
export const prepareRequestBody = (
parameters: BodyParameter[],
bodyType: string,
version: number,
defaultReducer: BodyParametersReducer,
) => {
if (bodyType === 'json' && version >= 4) {
return parameters.reduce((acc, entry) => {
const value = entry.value;
set(acc, entry.name, value);
return acc;
}, {} as IDataObject);
} else {
return parameters.reduce(defaultReducer, {});
}
};

View file

@ -14,13 +14,14 @@ export class HttpRequest extends VersionedNodeType {
group: ['output'],
subtitle: '={{$parameter["requestMethod"] + ": " + $parameter["url"]}}',
description: 'Makes an HTTP request and returns the response data',
defaultVersion: 3,
defaultVersion: 4,
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new HttpRequestV1(baseDescription),
2: new HttpRequestV2(baseDescription),
3: new HttpRequestV3(baseDescription),
4: new HttpRequestV3(baseDescription),
};
super(nodeVersions, baseDescription);

View file

@ -10,14 +10,17 @@ import type {
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import { BINARY_ENCODING, jsonParse, NodeApiError, NodeOperationError, sleep } from 'n8n-workflow';
import type { OptionsWithUri } from 'request-promise-native';
import type { IAuthDataSanitizeKeys } from '../GenericFunctions';
import type { BodyParameter, IAuthDataSanitizeKeys } from '../GenericFunctions';
import {
binaryContentTypes,
getOAuth2AdditionalParameters,
prepareRequestBody,
replaceNullValues,
sanitizeUiMessage,
} from '../GenericFunctions';
@ -35,7 +38,7 @@ export class HttpRequestV3 implements INodeType {
this.description = {
...baseDescription,
subtitle: '={{$parameter["method"] + ": " + $parameter["url"]}}',
version: 3,
version: [3, 4],
defaults: {
name: 'HTTP Request',
color: '#2200DD',
@ -879,6 +882,7 @@ export class HttpRequestV3 implements INodeType {
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const nodeVersion = this.getNode().typeVersion;
const fullResponseProperties = ['body', 'headers', 'statusCode', 'statusMessage'];
@ -960,9 +964,11 @@ export class HttpRequestV3 implements INodeType {
const sendBody = this.getNodeParameter('sendBody', itemIndex, false) as boolean;
const bodyContentType = this.getNodeParameter('contentType', itemIndex, '') as string;
const specifyBody = this.getNodeParameter('specifyBody', itemIndex, '') as string;
const bodyParameters = this.getNodeParameter('bodyParameters.parameters', itemIndex, []) as [
{ name: string; value: string },
];
const bodyParameters = this.getNodeParameter(
'bodyParameters.parameters',
itemIndex,
[],
) as BodyParameter[];
const jsonBodyParameter = this.getNodeParameter('jsonBody', itemIndex, '') as string;
const body = this.getNodeParameter('body', itemIndex, '') as string;
@ -1094,7 +1100,12 @@ export class HttpRequestV3 implements INodeType {
// Get parameters defined in the UI
if (sendBody && bodyParameters) {
if (specifyBody === 'keypair' || bodyContentType === 'multipart-form-data') {
requestOptions.body = bodyParameters.reduce(parametersToKeyValue, {});
requestOptions.body = prepareRequestBody(
bodyParameters,
bodyContentType,
nodeVersion,
parametersToKeyValue,
);
} else if (specifyBody === 'json') {
// body is specified using JSON
if (typeof jsonBodyParameter !== 'object' && jsonBodyParameter !== null) {

View file

@ -0,0 +1,37 @@
import { prepareRequestBody } from '../../GenericFunctions';
import type { BodyParameter, BodyParametersReducer } from '../../GenericFunctions';
describe('HTTP Node Utils, prepareRequestBody', () => {
it('should call default reducer', () => {
const bodyParameters: BodyParameter[] = [
{
name: 'foo.bar',
value: 'baz',
},
];
const defaultReducer: BodyParametersReducer = jest.fn();
prepareRequestBody(bodyParameters, 'json', 3, defaultReducer);
expect(defaultReducer).toBeCalledTimes(1);
expect(defaultReducer).toBeCalledWith({}, { name: 'foo.bar', value: 'baz' }, 0, [
{ name: 'foo.bar', value: 'baz' },
]);
});
it('should call process dot notations', () => {
const bodyParameters: BodyParameter[] = [
{
name: 'foo.bar.spam',
value: 'baz',
},
];
const defaultReducer: BodyParametersReducer = jest.fn();
const result = prepareRequestBody(bodyParameters, 'json', 4, defaultReducer);
expect(defaultReducer).toBeCalledTimes(0);
expect(result).toBeDefined();
expect(result).toEqual({ foo: { bar: { spam: 'baz' } } });
});
});