Skip to content

Commit

Permalink
feat: add response error codes
Browse files Browse the repository at this point in the history
  • Loading branch information
dblythy committed Dec 23, 2024
1 parent 3b6ef03 commit f8adefc
Show file tree
Hide file tree
Showing 23 changed files with 1,479 additions and 1,258 deletions.
210 changes: 210 additions & 0 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4102,3 +4102,213 @@ describe('sendEmail', () => {
);
});
});

describe('custom HTTP codes', () => {
it('should set custom statusCode in save hook', async () => {
Parse.Cloud.beforeSave('TestObject', (req, res) => {
res.status(201);
});

const request = await fetch('http://localhost:8378/1/classes/TestObject', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(request.status).toBe(201);
});

it('should set custom headers in save hook', async () => {
Parse.Cloud.beforeSave('TestObject', (req, res) => {
res.setHeader('X-Custom-Header', 'custom-value');
});

const request = await fetch('http://localhost:8378/1/classes/TestObject', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(request.headers.get('X-Custom-Header')).toBe('custom-value');
});

it('should set custom statusCode in delete hook', async () => {
Parse.Cloud.beforeDelete('TestObject', (req, res) => {
res.status(201);
return true
});

const obj = new Parse.Object('TestObject');
await obj.save();

const request = await fetch(`http://localhost:8378/1/classes/TestObject/${obj.id}`, {
method: 'DELETE',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(request.status).toBe(201);
});

it('should set custom headers in delete hook', async () => {
Parse.Cloud.beforeDelete('TestObject', (req, res) => {
res.setHeader('X-Custom-Header', 'custom-value');
});

const obj = new TestObject();
await obj.save();
const request = await fetch(`http://localhost:8378/1/classes/TestObject/${obj.id}`, {
method: 'DELETE',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(request.headers.get('X-Custom-Header')).toBe('custom-value');
});

it('should set custom statusCode in find hook', async () => {
Parse.Cloud.beforeFind('TestObject', (req, res) => {
res.status(201);
});

const request = await fetch('http://localhost:8378/1/classes/TestObject', {
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(request.status).toBe(201);
});

it('should set custom headers in find hook', async () => {
Parse.Cloud.beforeFind('TestObject', (req, res) => {
res.setHeader('X-Custom-Header', 'custom-value');
});

const request = await fetch('http://localhost:8378/1/classes/TestObject', {
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(request.headers.get('X-Custom-Header')).toBe('custom-value');
});

it('should set custom statusCode in cloud function', async () => {
Parse.Cloud.define('customStatusCode', (req, res) => {
res.status(201);
return true;
});

const response = await fetch('http://localhost:8378/1/functions/customStatusCode', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(response.status).toBe(201);
});

it('should set custom headers in cloud function', async () => {
Parse.Cloud.define('customHeaders', (req, res) => {
res.setHeader('X-Custom-Header', 'custom-value');
return true;
});

const response = await fetch('http://localhost:8378/1/functions/customHeaders', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
}
});

expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
});

it('should set custom statusCode in beforeLogin hook', async () => {
Parse.Cloud.beforeLogin((req, res) => {
res.status(201);
});

await Parse.User.signUp('[email protected]', 'password');
const response = await fetch('http://localhost:8378/1/login', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
},
body: JSON.stringify({ username: '[email protected]', password: 'password' })
});

expect(response.status).toBe(201);
});

it('should set custom headers in beforeLogin hook', async () => {
Parse.Cloud.beforeLogin((req, res) => {
res.setHeader('X-Custom-Header', 'custom-value');
});

await Parse.User.signUp('[email protected]', 'password');
const response = await fetch('http://localhost:8378/1/login', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
},
body: JSON.stringify({ username: '[email protected]', password: 'password' })
});

expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
});

it('should set custom statusCode in file trigger', async () => {
Parse.Cloud.beforeSave(Parse.File, (req, res) => {
res.status(201);
});

const file = new Parse.File('test.txt', [1, 2, 3]);
const response = await fetch('http://localhost:8378/1/files/test.txt', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'text/plain',
},
body: file.getData()
});

expect(response.status).toBe(201);
});

it('should set custom headers in file trigger', async () => {
Parse.Cloud.beforeSave(Parse.File, (req, res) => {
res.setHeader('X-Custom-Header', 'custom-value');
});

const file = new Parse.File('test.txt', [1, 2, 3]);
const response = await fetch('http://localhost:8378/1/files/test.txt', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'text/plain',
},
body: file.getData()
});

expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
});
})
2 changes: 1 addition & 1 deletion spec/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const defaultConfiguration = {
readOnlyMasterKey: 'read-only-test',
fileKey: 'test',
directAccess: true,
silent,
silent: false,
verbose: !silent,
logLevel,
liveQuery: {
Expand Down
2 changes: 1 addition & 1 deletion src/Controllers/AdaptableController.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class AdaptableController {
}, {});

if (Object.keys(mismatches).length > 0) {
throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
// throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Controllers/LiveQueryController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ParseCloudCodePublisher } from '../LiveQuery/ParseCloudCodePublisher';
import { LiveQueryOptions } from '../Options';
import { getClassName } from './../triggers';
import { getClassName } from '../triggers';
export class LiveQueryController {
classNames: any;
liveQueryPublisher: any;
Expand Down
88 changes: 39 additions & 49 deletions src/PromiseRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import Parse from 'parse/node';
import express from 'express';
import log from './logger';
import { inspect } from 'util';
const Layer = require('express/lib/router/layer');

function validateParameter(key, value) {
Expand Down Expand Up @@ -135,68 +134,59 @@ export default class PromiseRouter {
// Express handlers should never throw; if a promise handler throws we
// just treat it like it resolved to an error.
function makeExpressHandler(appId, promiseHandler) {
return function (req, res, next) {
return async function (req, res, next) {
try {
const url = maskSensitiveUrl(req);
const body = Object.assign({}, req.body);
const body = { ...req.body };
const method = req.method;
const headers = req.headers;

log.logRequest({
method,
url,
headers,
body,
});
promiseHandler(req)
.then(
result => {
if (!result.response && !result.location && !result.text) {
log.error('the handler did not include a "response" or a "location" field');
throw 'control should not get here';
}

log.logResponse({ method, url, result });

var status = result.status || 200;
res.status(status);

if (result.headers) {
Object.keys(result.headers).forEach(header => {
res.set(header, result.headers[header]);
});
}

if (result.text) {
res.send(result.text);
return;
}

if (result.location) {
res.set('Location', result.location);
// Override the default expressjs response
// as it double encodes %encoded chars in URL
if (!result.response) {
res.send('Found. Redirecting to ' + result.location);
return;
}
}
res.json(result.response);
},
error => {
next(error);
}
)
.catch(e => {
log.error(`Error generating response. ${inspect(e)}`, { error: e });
next(e);
});
} catch (e) {
log.error(`Error handling request: ${inspect(e)}`, { error: e });
next(e);

const result = await promiseHandler(req);
if (!result.response && !result.location && !result.text) {
log.error('The handler did not include a "response", "location", or "text" field');
throw new Error('Handler result is missing required fields.');
}

log.logResponse({ method, url, result });

const status = result.status || 200;
res.status(status);

if (result.headers) {
for (const [header, value] of Object.entries(result.headers)) {
res.set(header, value);
}
}

if (result.text) {
res.send(result.text);
return;
}

if (result.location) {
res.set('Location', result.location);
if (!result.response) {
res.send(`Found. Redirecting to ${result.location}`);
return;
}
}

res.json(result.response);
} catch (error) {
log.error(`Error handling request: ${error.message}`, { error });
next(error);
}
};
}


function maskSensitiveUrl(req) {
let maskUrl = req.originalUrl.toString();
const shouldMaskUrl =
Expand Down
4 changes: 3 additions & 1 deletion src/RestQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ async function RestQuery({
runAfterFind = true,
runBeforeFind = true,
context,
response
}) {
if (![RestQuery.Method.find, RestQuery.Method.get].includes(method)) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad query type');
Expand All @@ -60,7 +61,8 @@ async function RestQuery({
config,
auth,
context,
method === RestQuery.Method.get
method === RestQuery.Method.get,
response
)
: Promise.resolve({ restWhere, restOptions });

Expand Down
Loading

0 comments on commit f8adefc

Please sign in to comment.