feat: Add support for custom HTTP status code and headers to Cloud Function response with Express-style syntax (#9980)

This commit is contained in:
Copilot
2025-12-14 15:24:51 +01:00
committed by GitHub
parent c500fc46db
commit 8eeab8dc57
3 changed files with 330 additions and 8 deletions

View File

@@ -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);
});
}
}

View File

@@ -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.