mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
BackendSrv: only add content-type on POST, PUT requests (#22910)
* BackendSrv: only add content-type on POST, PUT requests Fixes #22869 * Tests: imports polyfill for Headers
This commit is contained in:
parent
87ffa258e7
commit
8d5c6053db
@ -1,4 +1,3 @@
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import { from, merge, MonoTypeOperatorFunction, Observable, of, Subject, throwError } from 'rxjs';
|
||||
import { catchError, filter, map, mergeMap, retryWhen, share, takeUntil, tap, throwIfEmpty } from 'rxjs/operators';
|
||||
import { fromFetch } from 'rxjs/fetch';
|
||||
@ -14,6 +13,7 @@ import { ContextSrv, contextSrv } from './context_srv';
|
||||
import { coreModule } from 'app/core/core_module';
|
||||
import { Emitter } from '../utils/emitter';
|
||||
import { DataSourceResponse } from '../../types/events';
|
||||
import { parseInitFromOptions, parseUrlFromOptions } from '../utils/fetch';
|
||||
|
||||
export interface DatasourceRequestOptions {
|
||||
retry?: number;
|
||||
@ -54,18 +54,6 @@ enum CancellationType {
|
||||
dataSourceRequest,
|
||||
}
|
||||
|
||||
function serializeParams(data: Record<string, any>): string {
|
||||
return Object.keys(data)
|
||||
.map(key => {
|
||||
const value = data[key];
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(arrayValue => `${encodeURIComponent(key)}=${encodeURIComponent(arrayValue)}`).join('&');
|
||||
}
|
||||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||
})
|
||||
.join('&');
|
||||
}
|
||||
|
||||
export interface BackendSrvDependencies {
|
||||
fromFetch: (input: string | Request, init?: RequestInit) => Observable<Response>;
|
||||
appEvents: Emitter;
|
||||
@ -580,62 +568,3 @@ coreModule.factory('backendSrv', () => backendSrv);
|
||||
// Used for testing and things that really need BackendSrv
|
||||
export const backendSrv = new BackendSrv();
|
||||
export const getBackendSrv = (): BackendSrv => backendSrv;
|
||||
|
||||
export const parseUrlFromOptions = (options: BackendSrvRequest): string => {
|
||||
const cleanParams = omitBy(options.params, v => v === undefined || (v && v.length === 0));
|
||||
const serializedParams = serializeParams(cleanParams);
|
||||
return options.params && serializedParams.length ? `${options.url}?${serializedParams}` : options.url;
|
||||
};
|
||||
|
||||
export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => {
|
||||
const method = options.method;
|
||||
const headers = parseHeaders(options);
|
||||
const isAppJson = isContentTypeApplicationJson(headers);
|
||||
const body = parseBody(options, isAppJson);
|
||||
|
||||
return {
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseHeaders = (options: BackendSrvRequest) => {
|
||||
const headers = new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
});
|
||||
|
||||
if (options && options.headers) {
|
||||
Object.keys(options.headers).forEach(key => {
|
||||
headers.set(key, options.headers[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
export const isContentTypeApplicationJson = (headers: Headers) => {
|
||||
if (!headers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contentType = headers.get('content-type');
|
||||
if (contentType && contentType.toLowerCase() === 'application/json') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const parseBody = (options: BackendSrvRequest, isAppJson: boolean) => {
|
||||
if (!options) {
|
||||
return options;
|
||||
}
|
||||
|
||||
if (!options.data || typeof options.data === 'string') {
|
||||
return options.data;
|
||||
}
|
||||
|
||||
return isAppJson ? JSON.stringify(options.data) : new URLSearchParams(options.data);
|
||||
};
|
||||
|
@ -3,15 +3,7 @@ import { Observable, of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
import {
|
||||
BackendSrv,
|
||||
getBackendSrv,
|
||||
isContentTypeApplicationJson,
|
||||
parseBody,
|
||||
parseHeaders,
|
||||
parseInitFromOptions,
|
||||
parseUrlFromOptions,
|
||||
} from '../services/backend_srv';
|
||||
import { BackendSrv, getBackendSrv } from '../services/backend_srv';
|
||||
import { Emitter } from '../utils/emitter';
|
||||
import { ContextSrv, User } from '../services/context_srv';
|
||||
import { CoreEvents } from '../../types';
|
||||
@ -27,7 +19,6 @@ const getTestContext = (overides?: object) => {
|
||||
redirected: false,
|
||||
type: 'basic',
|
||||
url: 'http://localhost:3000/api/some-mock',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
const props = { ...defaults, ...overides };
|
||||
const textMock = jest.fn().mockResolvedValue(JSON.stringify(props.data));
|
||||
@ -40,7 +31,6 @@ const getTestContext = (overides?: object) => {
|
||||
redirected: false,
|
||||
type: 'basic',
|
||||
url: 'http://localhost:3000/api/some-mock',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return of(mockedResponse);
|
||||
});
|
||||
@ -358,9 +348,6 @@ describe('backendSrv', () => {
|
||||
const result = await backendSrv.datasourceRequest({ url, method: 'GET', silent: true });
|
||||
expect(result).toEqual({
|
||||
data: { test: 'hello world' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
ok: true,
|
||||
redirected: false,
|
||||
status: 200,
|
||||
@ -373,7 +360,6 @@ describe('backendSrv', () => {
|
||||
body: undefined,
|
||||
headers: {
|
||||
map: {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
},
|
||||
@ -391,9 +377,6 @@ describe('backendSrv', () => {
|
||||
const result = await backendSrv.datasourceRequest({ url, method: 'GET' });
|
||||
const expectedResult = {
|
||||
data: { test: 'hello world' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
ok: true,
|
||||
redirected: false,
|
||||
status: 200,
|
||||
@ -406,7 +389,6 @@ describe('backendSrv', () => {
|
||||
body: undefined as any,
|
||||
headers: {
|
||||
map: {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
},
|
||||
@ -432,11 +414,6 @@ describe('backendSrv', () => {
|
||||
status: 200,
|
||||
statusText: 'Ok',
|
||||
text: () => Promise.resolve(JSON.stringify(slowData)),
|
||||
headers: {
|
||||
map: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
},
|
||||
redirected: false,
|
||||
type: 'basic',
|
||||
url,
|
||||
@ -449,11 +426,6 @@ describe('backendSrv', () => {
|
||||
status: 200,
|
||||
statusText: 'Ok',
|
||||
text: () => Promise.resolve(JSON.stringify(fastData)),
|
||||
headers: {
|
||||
map: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
},
|
||||
redirected: false,
|
||||
type: 'basic',
|
||||
url,
|
||||
@ -469,11 +441,6 @@ describe('backendSrv', () => {
|
||||
const fastResponse = await backendSrv.datasourceRequest(options);
|
||||
expect(fastResponse).toEqual({
|
||||
data: { message: 'Fast Request' },
|
||||
headers: {
|
||||
map: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
},
|
||||
ok: true,
|
||||
redirected: false,
|
||||
status: 200,
|
||||
@ -486,7 +453,6 @@ describe('backendSrv', () => {
|
||||
body: undefined,
|
||||
headers: {
|
||||
map: {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
},
|
||||
@ -504,7 +470,6 @@ describe('backendSrv', () => {
|
||||
body: undefined,
|
||||
headers: {
|
||||
map: {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
},
|
||||
@ -641,86 +606,3 @@ describe('backendSrv', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseUrlFromOptions', () => {
|
||||
it.each`
|
||||
params | url | expected
|
||||
${undefined} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
${{ key: 'value' }} | ${'api/dashboard'} | ${'api/dashboard?key=value'}
|
||||
${{ key: undefined }} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
${{ firstKey: 'first value', secondValue: 'second value' }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value&secondValue=second%20value'}
|
||||
${{ firstKey: 'first value', secondValue: undefined }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value'}
|
||||
${{ id: [1, 2, 3] }} | ${'api/dashboard'} | ${'api/dashboard?id=1&id=2&id=3'}
|
||||
${{ id: [] }} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
`(
|
||||
"when called with params: '$params' and url: '$url' then result should be '$expected'",
|
||||
({ params, url, expected }) => {
|
||||
expect(parseUrlFromOptions({ params, url })).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseInitFromOptions', () => {
|
||||
it.each`
|
||||
method | expected
|
||||
${undefined} | ${{ method: undefined, headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
||||
${'GET'} | ${{ method: 'GET', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
||||
${'POST'} | ${{ method: 'POST', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
||||
${'monkey'} | ${{ method: 'monkey', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
||||
`(
|
||||
"when called with method: '$method', headers: '$headers' and data: '$data' then result should be '$expected'",
|
||||
({ method, expected }) => {
|
||||
expect(parseInitFromOptions({ method, data: { id: '0' }, url: '' })).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseHeaders', () => {
|
||||
it.each`
|
||||
options | expected
|
||||
${undefined} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ propKey: 'some prop value' }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ headers: { 'content-type': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ headers: { 'cOnTent-tYpe': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ headers: { 'content-type': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
|
||||
${{ headers: { 'cOnTent-tYpe': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
|
||||
${{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded' } }}
|
||||
${{ headers: { Accept: 'text/plain' } }} | ${{ map: { accept: 'text/plain', 'content-type': 'application/json' } }}
|
||||
${{ headers: { Auth: 'Basic asdasdasd' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json', auth: 'Basic asdasdasd' } }}
|
||||
`("when called with options: '$options' then the result should be '$expected'", ({ options, expected }) => {
|
||||
expect(parseHeaders(options)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isContentTypeApplicationJson', () => {
|
||||
it.each`
|
||||
headers | expected
|
||||
${undefined} | ${false}
|
||||
${new Headers({ 'cOnTent-tYpe': 'application/json' })} | ${true}
|
||||
${new Headers({ 'content-type': 'AppLiCatIon/JsOn' })} | ${true}
|
||||
${new Headers({ 'cOnTent-tYpe': 'AppLiCatIon/JsOn' })} | ${true}
|
||||
${new Headers({ 'content-type': 'application/x-www-form-urlencoded' })} | ${false}
|
||||
${new Headers({ auth: 'Basic akdjasdkjalksdjasd' })} | ${false}
|
||||
`("when called with headers: 'headers' then the result should be '$expected'", ({ headers, expected }) => {
|
||||
expect(isContentTypeApplicationJson(headers)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseBody', () => {
|
||||
it.each`
|
||||
options | isAppJson | expected
|
||||
${undefined} | ${false} | ${undefined}
|
||||
${undefined} | ${true} | ${undefined}
|
||||
${{ data: undefined }} | ${false} | ${undefined}
|
||||
${{ data: undefined }} | ${true} | ${undefined}
|
||||
${{ data: 'some data' }} | ${false} | ${'some data'}
|
||||
${{ data: 'some data' }} | ${true} | ${'some data'}
|
||||
${{ data: { id: '0' } }} | ${false} | ${new URLSearchParams({ id: '0' })}
|
||||
${{ data: { id: '0' } }} | ${true} | ${'{"id":"0"}'}
|
||||
`(
|
||||
"when called with options: '$options' and isAppJson: '$isAppJson' then the result should be '$expected'",
|
||||
({ options, isAppJson, expected }) => {
|
||||
expect(parseBody(options, isAppJson)).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
101
public/app/core/utils/fetch.test.ts
Normal file
101
public/app/core/utils/fetch.test.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import 'whatwg-fetch'; // fetch polyfill needed for PhantomJs rendering
|
||||
import {
|
||||
isContentTypeApplicationJson,
|
||||
parseBody,
|
||||
parseHeaders,
|
||||
parseInitFromOptions,
|
||||
parseUrlFromOptions,
|
||||
} from './fetch';
|
||||
|
||||
describe('parseUrlFromOptions', () => {
|
||||
it.each`
|
||||
params | url | expected
|
||||
${undefined} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
${{ key: 'value' }} | ${'api/dashboard'} | ${'api/dashboard?key=value'}
|
||||
${{ key: undefined }} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
${{ firstKey: 'first value', secondValue: 'second value' }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value&secondValue=second%20value'}
|
||||
${{ firstKey: 'first value', secondValue: undefined }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value'}
|
||||
${{ id: [1, 2, 3] }} | ${'api/dashboard'} | ${'api/dashboard?id=1&id=2&id=3'}
|
||||
${{ id: [] }} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
`(
|
||||
"when called with params: '$params' and url: '$url' then result should be '$expected'",
|
||||
({ params, url, expected }) => {
|
||||
expect(parseUrlFromOptions({ params, url })).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseInitFromOptions', () => {
|
||||
it.each`
|
||||
method | data | expected
|
||||
${undefined} | ${undefined} | ${{ method: undefined, headers: { map: { accept: 'application/json, text/plain, */*' } }, body: undefined }}
|
||||
${'GET'} | ${undefined} | ${{ method: 'GET', headers: { map: { accept: 'application/json, text/plain, */*' } }, body: undefined }}
|
||||
${'POST'} | ${{ id: '0' }} | ${{ method: 'POST', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
||||
${'PUT'} | ${{ id: '0' }} | ${{ method: 'PUT', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
||||
${'monkey'} | ${undefined} | ${{ method: 'monkey', headers: { map: { accept: 'application/json, text/plain, */*' } }, body: undefined }}
|
||||
`(
|
||||
"when called with method: '$method' and data: '$data' then result should be '$expected'",
|
||||
({ method, data, expected }) => {
|
||||
expect(parseInitFromOptions({ method, data, url: '' })).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseHeaders', () => {
|
||||
it.each`
|
||||
options | expected
|
||||
${undefined} | ${{ map: { accept: 'application/json, text/plain, */*' } }}
|
||||
${{ propKey: 'some prop value' }} | ${{ map: { accept: 'application/json, text/plain, */*' } }}
|
||||
${{ method: 'GET' }} | ${{ map: { accept: 'application/json, text/plain, */*' } }}
|
||||
${{ method: 'POST' }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ method: 'PUT' }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ headers: { 'content-type': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ method: 'GET', headers: { 'content-type': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ method: 'POST', headers: { 'content-type': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ method: 'PUT', headers: { 'content-type': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ headers: { 'cOnTent-tYpe': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
||||
${{ headers: { 'content-type': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
|
||||
${{ headers: { 'cOnTent-tYpe': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
|
||||
${{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded' } }}
|
||||
${{ method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded' } }}
|
||||
${{ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded' } }}
|
||||
${{ method: 'PUT', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded' } }}
|
||||
${{ headers: { Accept: 'text/plain' } }} | ${{ map: { accept: 'text/plain' } }}
|
||||
${{ headers: { Auth: 'Basic asdasdasd' } }} | ${{ map: { accept: 'application/json, text/plain, */*', auth: 'Basic asdasdasd' } }}
|
||||
`("when called with options: '$options' then the result should be '$expected'", ({ options, expected }) => {
|
||||
expect(parseHeaders(options)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isContentTypeApplicationJson', () => {
|
||||
it.each`
|
||||
headers | expected
|
||||
${undefined} | ${false}
|
||||
${new Headers({ 'cOnTent-tYpe': 'application/json' })} | ${true}
|
||||
${new Headers({ 'content-type': 'AppLiCatIon/JsOn' })} | ${true}
|
||||
${new Headers({ 'cOnTent-tYpe': 'AppLiCatIon/JsOn' })} | ${true}
|
||||
${new Headers({ 'content-type': 'application/x-www-form-urlencoded' })} | ${false}
|
||||
${new Headers({ auth: 'Basic akdjasdkjalksdjasd' })} | ${false}
|
||||
`("when called with headers: 'headers' then the result should be '$expected'", ({ headers, expected }) => {
|
||||
expect(isContentTypeApplicationJson(headers)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseBody', () => {
|
||||
it.each`
|
||||
options | isAppJson | expected
|
||||
${undefined} | ${false} | ${undefined}
|
||||
${undefined} | ${true} | ${undefined}
|
||||
${{ data: undefined }} | ${false} | ${undefined}
|
||||
${{ data: undefined }} | ${true} | ${undefined}
|
||||
${{ data: 'some data' }} | ${false} | ${'some data'}
|
||||
${{ data: 'some data' }} | ${true} | ${'some data'}
|
||||
${{ data: { id: '0' } }} | ${false} | ${new URLSearchParams({ id: '0' })}
|
||||
${{ data: { id: '0' } }} | ${true} | ${'{"id":"0"}'}
|
||||
`(
|
||||
"when called with options: '$options' and isAppJson: '$isAppJson' then the result should be '$expected'",
|
||||
({ options, isAppJson, expected }) => {
|
||||
expect(parseBody(options, isAppJson)).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
107
public/app/core/utils/fetch.ts
Normal file
107
public/app/core/utils/fetch.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { BackendSrvRequest } from '@grafana/runtime';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
|
||||
export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => {
|
||||
const method = options.method;
|
||||
const headers = parseHeaders(options);
|
||||
const isAppJson = isContentTypeApplicationJson(headers);
|
||||
const body = parseBody(options, isAppJson);
|
||||
|
||||
return {
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
};
|
||||
|
||||
interface HeaderParser {
|
||||
canParse: (options: BackendSrvRequest) => boolean;
|
||||
parse: (headers: Headers) => Headers;
|
||||
}
|
||||
|
||||
const defaultHeaderParser: HeaderParser = {
|
||||
canParse: () => true,
|
||||
parse: headers => {
|
||||
const accept = headers.get('accept');
|
||||
if (accept) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
headers.set('accept', 'application/json, text/plain, */*');
|
||||
return headers;
|
||||
},
|
||||
};
|
||||
|
||||
const parseHeaderByMethodFactory = (methodPredicate: string): HeaderParser => ({
|
||||
canParse: options => {
|
||||
const method = options?.method ? options?.method.toLowerCase() : '';
|
||||
return method === methodPredicate;
|
||||
},
|
||||
parse: headers => {
|
||||
const contentType = headers.get('content-type');
|
||||
if (contentType) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
headers.set('content-type', 'application/json');
|
||||
return headers;
|
||||
},
|
||||
});
|
||||
|
||||
const postHeaderParser: HeaderParser = parseHeaderByMethodFactory('post');
|
||||
const putHeaderParser: HeaderParser = parseHeaderByMethodFactory('put');
|
||||
|
||||
const headerParsers = [postHeaderParser, putHeaderParser, defaultHeaderParser];
|
||||
|
||||
export const parseHeaders = (options: BackendSrvRequest) => {
|
||||
const headers = options?.headers ? new Headers(options.headers) : new Headers();
|
||||
const parsers = headerParsers.filter(parser => parser.canParse(options));
|
||||
const combinedHeaders = parsers.reduce((prev, parser) => {
|
||||
return parser.parse(prev);
|
||||
}, headers);
|
||||
|
||||
return combinedHeaders;
|
||||
};
|
||||
|
||||
export const isContentTypeApplicationJson = (headers: Headers) => {
|
||||
if (!headers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contentType = headers.get('content-type');
|
||||
if (contentType && contentType.toLowerCase() === 'application/json') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const parseBody = (options: BackendSrvRequest, isAppJson: boolean) => {
|
||||
if (!options) {
|
||||
return options;
|
||||
}
|
||||
|
||||
if (!options.data || typeof options.data === 'string') {
|
||||
return options.data;
|
||||
}
|
||||
|
||||
return isAppJson ? JSON.stringify(options.data) : new URLSearchParams(options.data);
|
||||
};
|
||||
|
||||
function serializeParams(data: Record<string, any>): string {
|
||||
return Object.keys(data)
|
||||
.map(key => {
|
||||
const value = data[key];
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(arrayValue => `${encodeURIComponent(key)}=${encodeURIComponent(arrayValue)}`).join('&');
|
||||
}
|
||||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||
})
|
||||
.join('&');
|
||||
}
|
||||
|
||||
export const parseUrlFromOptions = (options: BackendSrvRequest): string => {
|
||||
const cleanParams = omitBy(options.params, v => v === undefined || (v && v.length === 0));
|
||||
const serializedParams = serializeParams(cleanParams);
|
||||
return options.params && serializedParams.length ? `${options.url}?${serializedParams}` : options.url;
|
||||
};
|
Loading…
Reference in New Issue
Block a user