feat: Add support for custom HTTP status code and headers to Cloud Function response with Express-style syntax (#9980)
This commit is contained in:
@@ -103,20 +103,52 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
});
|
||||
}
|
||||
|
||||
static createResponseObject(resolve, reject) {
|
||||
return {
|
||||
static createResponseObject(resolve, reject, statusCode = null) {
|
||||
let httpStatusCode = statusCode;
|
||||
const customHeaders = {};
|
||||
let responseSent = false;
|
||||
const responseObject = {
|
||||
success: function (result) {
|
||||
resolve({
|
||||
if (responseSent) {
|
||||
throw new Error('Cannot call success() after response has already been sent. Make sure to call success() or error() only once per cloud function execution.');
|
||||
}
|
||||
responseSent = true;
|
||||
const response = {
|
||||
response: {
|
||||
result: Parse._encode(result),
|
||||
},
|
||||
});
|
||||
};
|
||||
if (httpStatusCode !== null) {
|
||||
response.status = httpStatusCode;
|
||||
}
|
||||
if (Object.keys(customHeaders).length > 0) {
|
||||
response.headers = customHeaders;
|
||||
}
|
||||
resolve(response);
|
||||
},
|
||||
error: function (message) {
|
||||
if (responseSent) {
|
||||
throw new Error('Cannot call error() after response has already been sent. Make sure to call success() or error() only once per cloud function execution.');
|
||||
}
|
||||
responseSent = true;
|
||||
const error = triggers.resolveError(message);
|
||||
// If a custom status code was set, attach it to the error
|
||||
if (httpStatusCode !== null) {
|
||||
error.status = httpStatusCode;
|
||||
}
|
||||
reject(error);
|
||||
},
|
||||
status: function (code) {
|
||||
httpStatusCode = code;
|
||||
return responseObject;
|
||||
},
|
||||
header: function (key, value) {
|
||||
customHeaders[key] = value;
|
||||
return responseObject;
|
||||
},
|
||||
_isResponseSent: () => responseSent,
|
||||
};
|
||||
return responseObject;
|
||||
}
|
||||
static handleCloudFunction(req) {
|
||||
const functionName = req.params.functionName;
|
||||
@@ -143,7 +175,7 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
const userString = req.auth && req.auth.user ? req.auth.user.id : undefined;
|
||||
const { success, error } = FunctionsRouter.createResponseObject(
|
||||
const responseObject = FunctionsRouter.createResponseObject(
|
||||
result => {
|
||||
try {
|
||||
if (req.config.logLevels.cloudFunctionSuccess !== 'silent') {
|
||||
@@ -184,14 +216,37 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
}
|
||||
}
|
||||
);
|
||||
const { success, error } = responseObject;
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return triggers.maybeRunValidator(request, functionName, req.auth);
|
||||
})
|
||||
.then(() => {
|
||||
return theFunction(request);
|
||||
// Check if function expects 2 parameters (req, res) - Express style
|
||||
if (theFunction.length >= 2) {
|
||||
return theFunction(request, responseObject);
|
||||
} else {
|
||||
// Traditional style - single parameter
|
||||
return theFunction(request);
|
||||
}
|
||||
})
|
||||
.then(success, error);
|
||||
.then(result => {
|
||||
// For Express-style functions, only send response if not already sent
|
||||
if (theFunction.length >= 2) {
|
||||
if (!responseObject._isResponseSent()) {
|
||||
// If Express-style function returns a value without calling res.success/error
|
||||
if (result !== undefined) {
|
||||
success(result);
|
||||
}
|
||||
// If no response sent and no value returned, this is an error in user code
|
||||
// but we don't handle it here to maintain backward compatibility
|
||||
}
|
||||
} else {
|
||||
// For traditional functions, always call success with the result (even if undefined)
|
||||
success(result);
|
||||
}
|
||||
}, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,22 +107,49 @@ var ParseCloud = {};
|
||||
*
|
||||
* **Available in Cloud Code only.**
|
||||
*
|
||||
* **Traditional Style:**
|
||||
* ```
|
||||
* Parse.Cloud.define('functionName', (request) => {
|
||||
* // code here
|
||||
* return result;
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.define('functionName', (request) => {
|
||||
* // code here
|
||||
* return result;
|
||||
* }, { ...validationObject });
|
||||
* ```
|
||||
*
|
||||
* **Express Style with Custom HTTP Status Codes:**
|
||||
* ```
|
||||
* Parse.Cloud.define('functionName', (request, response) => {
|
||||
* // Set custom HTTP status code and send response
|
||||
* response.status(201).success({ message: 'Created' });
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.define('unauthorizedFunction', (request, response) => {
|
||||
* if (!request.user) {
|
||||
* response.status(401).error('Unauthorized');
|
||||
* } else {
|
||||
* response.success({ data: 'OK' });
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.define('withCustomHeaders', (request, response) => {
|
||||
* response.header('X-Custom-Header', 'value').success({ data: 'OK' });
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.define('errorFunction', (request, response) => {
|
||||
* response.error('Something went wrong');
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @static
|
||||
* @memberof Parse.Cloud
|
||||
* @param {String} name The name of the Cloud Function
|
||||
* @param {Function} data The Cloud Function to register. This function can be an async function and should take one parameter a {@link Parse.Cloud.FunctionRequest}.
|
||||
* @param {Function} data The Cloud Function to register. This function can be an async function and should take one parameter a {@link Parse.Cloud.FunctionRequest}, or two parameters (request, response) for Express-style functions where response is a {@link Parse.Cloud.FunctionResponse}.
|
||||
* @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.FunctionRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.define = function (functionName, handler, validationHandler) {
|
||||
@@ -788,9 +815,22 @@ module.exports = ParseCloud;
|
||||
* @property {Boolean} master If true, means the master key was used.
|
||||
* @property {Parse.User} user If set, the user that made the request.
|
||||
* @property {Object} params The params passed to the cloud function.
|
||||
* @property {String} ip The IP address of the client making the request.
|
||||
* @property {Object} headers The original HTTP headers for the request.
|
||||
* @property {Object} log The current logger inside Parse Server.
|
||||
* @property {String} functionName The name of the cloud function.
|
||||
* @property {Object} context The context of the cloud function call.
|
||||
* @property {Object} config The Parse Server config.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @interface Parse.Cloud.FunctionResponse
|
||||
* @property {function} success Call this function to return a successful response with an optional result. Usage: `response.success(result)`
|
||||
* @property {function} error Call this function to return an error response with an error message. Usage: `response.error(message)`
|
||||
* @property {function} status Call this function to set a custom HTTP status code for the response. Returns the response object for chaining. Usage: `response.status(code).success(result)` or `response.status(code).error(message)`
|
||||
* @property {function} header Call this function to set a custom HTTP header for the response. Returns the response object for chaining. Usage: `response.header('X-Custom-Header', 'value').success(result)`
|
||||
*/
|
||||
|
||||
/**
|
||||
* @interface Parse.Cloud.JobRequest
|
||||
* @property {Object} params The params passed to the background job.
|
||||
|
||||
Reference in New Issue
Block a user