blob: eccb6df186c42977282aff6879ba8b9f830eba44 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import rison from 'rison';
import {
SupersetClient,
Payload as SupersetPayload,
JsonObject,
JsonValue,
ParseMethod,
Endpoint,
Method,
RequestBase,
} from '../../../connection';
import handleError, { ErrorInput } from './handleError';
import { SupersetApiRequestOptions, SupersetApiErrorPayload, ParsedResponseType } from './types';
const validRequestTypes = new Set(['form', 'json', 'search', 'rison']);
interface SupersetApiFactoryOptions extends Omit<RequestBase, 'url'> {
/**
* API endpoint, must be relative.
*/
endpoint: Endpoint;
/**
* Request method: 'GET' | 'POST' | 'DELETE' | 'PUT' | ...
*/
method: Method;
/**
* How to send the payload:
* - form: set request.body as FormData
* - json: as JSON string with request Content-Type header set to application/json
* - search: add to search params
*/
requestType?: 'form' | 'json' | 'search' | 'rison';
}
function isPayloadless(method?: Method) {
return !method || method === 'GET' || method === 'DELETE' || method === 'HEAD';
}
/**
* Generate an API caller with predefined configs/typing and consistent
* return values.
*/
export default function makeApi<
Payload = SupersetPayload,
Result = JsonObject,
T extends ParseMethod = ParseMethod
>({
endpoint,
method,
requestType: requestType_,
responseType,
processResponse,
...requestOptions
}: SupersetApiFactoryOptions & {
/**
* How to parse response, choose from: 'json' | 'text' | 'raw'.
*/
responseType?: T;
/**
* Further process parsed response
*/
processResponse?(result: ParsedResponseType<T>): Result;
}) {
// use `search` payload (searchParams) when it's a GET request
const requestType = requestType_ || (isPayloadless(method) ? 'search' : 'json');
if (!validRequestTypes.has(requestType)) {
throw new Error(
`Invalid request payload type, choose from: ${[...validRequestTypes].join(' | ')}`,
);
}
async function request(
payload: Payload,
{ client = SupersetClient }: SupersetApiRequestOptions = { client: SupersetClient },
): Promise<Result> {
try {
const requestConfig = {
...requestOptions,
method,
endpoint,
};
if (requestType === 'search') {
requestConfig.searchParams = payload;
} else if (requestType === 'rison') {
requestConfig.endpoint = `${endpoint}?q=${rison.encode(payload)}`;
} else if (requestType === 'form') {
requestConfig.postPayload = payload;
} else {
requestConfig.jsonPayload = payload;
}
let result: JsonValue | Response;
const response = await client.request({ ...requestConfig, parseMethod: 'raw' });
if (responseType === 'text') {
result = await response.text();
} else if (responseType === 'raw' || responseType === null) {
result = response;
} else {
result = await response.json();
// if response json has an "error" field
if (result && typeof result === 'object' && 'error' in result) {
return handleError(result as SupersetApiErrorPayload);
}
}
const typedResult = result as ParsedResponseType<T>;
return (processResponse ? processResponse(typedResult) : typedResult) as Result;
} catch (error) {
return handleError(error as ErrorInput);
}
}
request.method = method;
request.endpoint = endpoint;
request.requestType = requestType;
return request;
}