2022-07-19 10:27:31 +02:00
|
|
|
const axios = require('axios');
|
2021-01-24 13:14:13 +01:00
|
|
|
const FormData = require('form-data')
|
|
|
|
const fs = require('fs')
|
2022-11-01 10:01:33 +01:00
|
|
|
const url = require('url');
|
2020-07-30 18:27:27 +02:00
|
|
|
|
|
|
|
const METHOD_GET = 'GET'
|
|
|
|
const METHOD_POST = 'POST'
|
|
|
|
|
2022-11-01 10:01:33 +01:00
|
|
|
const HEADER_CONTENT_TYPE = 'Content-Type'
|
|
|
|
|
|
|
|
const CONTENT_TYPE_URLENCODED = 'application/x-www-form-urlencoded'
|
|
|
|
|
2021-03-19 17:28:53 +01:00
|
|
|
/**
|
|
|
|
* @param {Object} param0
|
|
|
|
* @param {string} param0.method HTTP Method
|
2022-07-19 10:27:31 +02:00
|
|
|
* @param {axios.AxiosRequestConfig} param0.instanceConfig
|
2021-03-19 17:28:53 +01:00
|
|
|
* @param {string} param0.data Request Body as string, default {}
|
|
|
|
* @param {string} param0.files Map of Request Files (name: absolute path) as JSON String, default: {}
|
2022-02-10 10:14:07 +07:00
|
|
|
* @param {string} param0.file Single request file (absolute path)
|
2021-03-19 17:28:53 +01:00
|
|
|
* @param {*} param0.actions
|
|
|
|
* @param {number[]} param0.ignoredCodes Prevent Action to fail if the API response with one of this StatusCodes
|
|
|
|
* @param {boolean} param0.preventFailureOnNoResponse Prevent Action to fail if the API respond without Response
|
|
|
|
* @param {boolean} param0.escapeData Escape unescaped JSON content in data
|
|
|
|
*
|
2022-11-03 21:35:53 +01:00
|
|
|
* @returns {Promise<void>}
|
2021-03-19 17:28:53 +01:00
|
|
|
*/
|
2022-07-19 10:27:31 +02:00
|
|
|
const request = async({ method, instanceConfig, data, files, file, actions, ignoredCodes, preventFailureOnNoResponse, escapeData }) => {
|
2020-07-30 18:27:27 +02:00
|
|
|
try {
|
2020-10-07 18:29:38 +02:00
|
|
|
if (escapeData) {
|
|
|
|
data = data.replace(/"[^"]*"/g, (match) => {
|
2021-02-19 11:36:10 +01:00
|
|
|
return match.replace(/[\n\r]\s*/g, "\\n");
|
2020-10-07 18:29:38 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-16 21:37:11 +01:00
|
|
|
if (method === METHOD_GET) {
|
|
|
|
data = undefined;
|
|
|
|
}
|
2020-07-30 18:27:27 +02:00
|
|
|
|
2021-01-24 13:14:13 +01:00
|
|
|
if (files && files !== '{}') {
|
2022-02-10 10:14:07 +07:00
|
|
|
let filesJson = convertToJSON(files)
|
|
|
|
let dataJson = convertToJSON(data)
|
2021-01-24 13:14:13 +01:00
|
|
|
|
|
|
|
if (Object.keys(filesJson).length > 0) {
|
|
|
|
try {
|
|
|
|
data = convertToFormData(dataJson, filesJson)
|
|
|
|
instanceConfig = await updateConfig(instanceConfig, data, actions)
|
|
|
|
} catch(error) {
|
|
|
|
actions.setFailed({ message: `Unable to convert Data and Files into FormData: ${error.message}`, data: dataJson, files: filesJson })
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-10 10:14:07 +07:00
|
|
|
// Only consider file if neither data nor files provided
|
|
|
|
if ((!data || data === '{}') && (!files || files === '{}') && file) {
|
|
|
|
data = fs.createReadStream(file)
|
|
|
|
updateConfigForFile(instanceConfig, file, actions)
|
|
|
|
}
|
|
|
|
|
2022-11-01 10:01:33 +01:00
|
|
|
if (instanceConfig.headers[HEADER_CONTENT_TYPE] === CONTENT_TYPE_URLENCODED) {
|
|
|
|
let dataJson = convertToJSON(data)
|
2022-11-03 20:24:38 +01:00
|
|
|
if (typeof dataJson === 'object' && Object.keys(dataJson).length) {
|
2022-11-01 10:01:33 +01:00
|
|
|
data = (new url.URLSearchParams(dataJson)).toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-30 18:27:27 +02:00
|
|
|
const requestData = {
|
|
|
|
method,
|
2021-02-19 11:36:10 +01:00
|
|
|
data,
|
|
|
|
maxContentLength: Infinity,
|
|
|
|
maxBodyLength: Infinity
|
2020-07-30 18:27:27 +02:00
|
|
|
}
|
|
|
|
|
2021-01-24 13:52:05 +01:00
|
|
|
actions.debug('Instance Configuration: ' + JSON.stringify(instanceConfig))
|
|
|
|
|
|
|
|
const instance = axios.create(instanceConfig);
|
|
|
|
|
2020-07-30 18:27:27 +02:00
|
|
|
actions.debug('Request Data: ' + JSON.stringify(requestData))
|
|
|
|
|
|
|
|
const response = await instance.request(requestData)
|
|
|
|
|
|
|
|
actions.setOutput('response', JSON.stringify(response.data))
|
2022-08-17 08:56:02 +01:00
|
|
|
|
|
|
|
actions.setOutput('headers', response.headers)
|
2020-07-30 18:27:27 +02:00
|
|
|
} catch (error) {
|
2022-06-04 10:52:53 +02:00
|
|
|
if ((typeof error === 'object') && (error.isAxiosError === true)) {
|
|
|
|
const { name, message, code, response } = error
|
|
|
|
actions.setOutput('requestError', JSON.stringify({ name, message, code, status: response && response.status ? response.status : null }));
|
2020-07-30 18:27:27 +02:00
|
|
|
}
|
|
|
|
|
2021-03-19 17:28:53 +01:00
|
|
|
if (error.response && ignoredCodes.includes(error.response.status)) {
|
|
|
|
actions.warning(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
|
|
|
} else if (error.response) {
|
|
|
|
actions.setFailed(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
2020-07-30 18:27:27 +02:00
|
|
|
} else if (error.request && !preventFailureOnNoResponse) {
|
|
|
|
actions.setFailed(JSON.stringify({ error: "no response received" }));
|
|
|
|
} else if (error.request && preventFailureOnNoResponse) {
|
|
|
|
actions.warning(JSON.stringify(error));
|
|
|
|
} else {
|
2020-10-07 18:29:38 +02:00
|
|
|
actions.setFailed(JSON.stringify({ message: error.message, data }));
|
2020-07-30 18:27:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-19 17:28:53 +01:00
|
|
|
/**
|
|
|
|
* @param {string} value
|
|
|
|
*
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2021-01-24 13:14:13 +01:00
|
|
|
const convertToJSON = (value) => {
|
|
|
|
try {
|
2022-10-05 17:29:30 +02:00
|
|
|
return JSON.parse(value) || {}
|
2021-01-24 13:14:13 +01:00
|
|
|
} catch(e) {
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-19 17:28:53 +01:00
|
|
|
/**
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {Object} files
|
|
|
|
*
|
|
|
|
* @returns {FormData}
|
|
|
|
*/
|
2021-01-24 13:14:13 +01:00
|
|
|
const convertToFormData = (data, files) => {
|
|
|
|
formData = new FormData()
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
|
|
formData.append(key, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(files)) {
|
|
|
|
formData.append(key, fs.createReadStream(value))
|
|
|
|
}
|
|
|
|
|
|
|
|
return formData
|
|
|
|
}
|
|
|
|
|
2021-03-19 17:28:53 +01:00
|
|
|
/**
|
|
|
|
* @param {{ baseURL: string; timeout: number; headers: { [name: string]: string } }} instanceConfig
|
|
|
|
* @param {FormData} formData
|
|
|
|
* @param {*} actions
|
|
|
|
*
|
2022-11-03 21:35:53 +01:00
|
|
|
* @returns {Promise<{ baseURL: string; timeout: number; headers: { [name: string]: string } }>}
|
2021-03-19 17:28:53 +01:00
|
|
|
*/
|
2021-01-24 13:14:13 +01:00
|
|
|
const updateConfig = async (instanceConfig, formData, actions) => {
|
|
|
|
try {
|
2021-01-24 13:52:05 +01:00
|
|
|
const formHeaders = formData.getHeaders()
|
|
|
|
const contentType = formHeaders['content-type']
|
|
|
|
|
|
|
|
delete formHeaders['content-type']
|
|
|
|
|
2021-01-24 13:14:13 +01:00
|
|
|
return {
|
|
|
|
...instanceConfig,
|
|
|
|
headers: {
|
|
|
|
...instanceConfig.headers,
|
2021-01-24 13:52:05 +01:00
|
|
|
...formHeaders,
|
|
|
|
'Content-Length': await contentLength(formData),
|
|
|
|
'Content-Type': contentType
|
2021-01-24 13:14:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(error) {
|
|
|
|
actions.setFailed({ message: `Unable to read Content-Length: ${error.message}`, data, files })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-10 10:14:07 +07:00
|
|
|
/**
|
|
|
|
* @param instanceConfig
|
|
|
|
* @param filePath
|
|
|
|
* @param {*} actions
|
|
|
|
*
|
|
|
|
* @returns {{ baseURL: string; timeout: number; headers: { [name: string]: string } }}
|
|
|
|
*/
|
|
|
|
const updateConfigForFile = (instanceConfig, filePath, actions) => {
|
|
|
|
try {
|
|
|
|
const { size } = fs.statSync(filePath)
|
|
|
|
|
|
|
|
return {
|
|
|
|
...instanceConfig,
|
|
|
|
headers: {
|
|
|
|
...instanceConfig.headers,
|
|
|
|
'Content-Length': size,
|
|
|
|
'Content-Type': 'application/octet-stream'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(error) {
|
|
|
|
actions.setFailed({ message: `Unable to read Content-Length: ${error.message}`, data, files })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-19 17:28:53 +01:00
|
|
|
/**
|
|
|
|
* @param {FormData} formData
|
|
|
|
*
|
|
|
|
* @returns {Promise<number>}
|
|
|
|
*/
|
2021-01-24 13:14:13 +01:00
|
|
|
const contentLength = (formData) => new Promise((resolve, reject) => {
|
|
|
|
formData.getLength((err, length) => {
|
|
|
|
if (err) {
|
|
|
|
reject (err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(length)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-07-30 18:27:27 +02:00
|
|
|
module.exports = {
|
|
|
|
request,
|
|
|
|
METHOD_POST,
|
|
|
|
METHOD_GET,
|
|
|
|
}
|