Skip to content

Commit f8adefc

Browse files
committed
feat: add response error codes
1 parent 3b6ef03 commit f8adefc

23 files changed

+1479
-1258
lines changed

spec/CloudCode.spec.js

+210
Original file line numberDiff line numberDiff line change
@@ -4102,3 +4102,213 @@ describe('sendEmail', () => {
41024102
);
41034103
});
41044104
});
4105+
4106+
describe('custom HTTP codes', () => {
4107+
it('should set custom statusCode in save hook', async () => {
4108+
Parse.Cloud.beforeSave('TestObject', (req, res) => {
4109+
res.status(201);
4110+
});
4111+
4112+
const request = await fetch('http://localhost:8378/1/classes/TestObject', {
4113+
method: 'POST',
4114+
headers: {
4115+
'X-Parse-Application-Id': 'test',
4116+
'X-Parse-REST-API-Key': 'rest',
4117+
}
4118+
});
4119+
4120+
expect(request.status).toBe(201);
4121+
});
4122+
4123+
it('should set custom headers in save hook', async () => {
4124+
Parse.Cloud.beforeSave('TestObject', (req, res) => {
4125+
res.setHeader('X-Custom-Header', 'custom-value');
4126+
});
4127+
4128+
const request = await fetch('http://localhost:8378/1/classes/TestObject', {
4129+
method: 'POST',
4130+
headers: {
4131+
'X-Parse-Application-Id': 'test',
4132+
'X-Parse-REST-API-Key': 'rest',
4133+
}
4134+
});
4135+
4136+
expect(request.headers.get('X-Custom-Header')).toBe('custom-value');
4137+
});
4138+
4139+
it('should set custom statusCode in delete hook', async () => {
4140+
Parse.Cloud.beforeDelete('TestObject', (req, res) => {
4141+
res.status(201);
4142+
return true
4143+
});
4144+
4145+
const obj = new Parse.Object('TestObject');
4146+
await obj.save();
4147+
4148+
const request = await fetch(`http://localhost:8378/1/classes/TestObject/${obj.id}`, {
4149+
method: 'DELETE',
4150+
headers: {
4151+
'X-Parse-Application-Id': 'test',
4152+
'X-Parse-REST-API-Key': 'rest',
4153+
}
4154+
});
4155+
4156+
expect(request.status).toBe(201);
4157+
});
4158+
4159+
it('should set custom headers in delete hook', async () => {
4160+
Parse.Cloud.beforeDelete('TestObject', (req, res) => {
4161+
res.setHeader('X-Custom-Header', 'custom-value');
4162+
});
4163+
4164+
const obj = new TestObject();
4165+
await obj.save();
4166+
const request = await fetch(`http://localhost:8378/1/classes/TestObject/${obj.id}`, {
4167+
method: 'DELETE',
4168+
headers: {
4169+
'X-Parse-Application-Id': 'test',
4170+
'X-Parse-REST-API-Key': 'rest',
4171+
}
4172+
});
4173+
4174+
expect(request.headers.get('X-Custom-Header')).toBe('custom-value');
4175+
});
4176+
4177+
it('should set custom statusCode in find hook', async () => {
4178+
Parse.Cloud.beforeFind('TestObject', (req, res) => {
4179+
res.status(201);
4180+
});
4181+
4182+
const request = await fetch('http://localhost:8378/1/classes/TestObject', {
4183+
headers: {
4184+
'X-Parse-Application-Id': 'test',
4185+
'X-Parse-REST-API-Key': 'rest',
4186+
}
4187+
});
4188+
4189+
expect(request.status).toBe(201);
4190+
});
4191+
4192+
it('should set custom headers in find hook', async () => {
4193+
Parse.Cloud.beforeFind('TestObject', (req, res) => {
4194+
res.setHeader('X-Custom-Header', 'custom-value');
4195+
});
4196+
4197+
const request = await fetch('http://localhost:8378/1/classes/TestObject', {
4198+
headers: {
4199+
'X-Parse-Application-Id': 'test',
4200+
'X-Parse-REST-API-Key': 'rest',
4201+
}
4202+
});
4203+
4204+
expect(request.headers.get('X-Custom-Header')).toBe('custom-value');
4205+
});
4206+
4207+
it('should set custom statusCode in cloud function', async () => {
4208+
Parse.Cloud.define('customStatusCode', (req, res) => {
4209+
res.status(201);
4210+
return true;
4211+
});
4212+
4213+
const response = await fetch('http://localhost:8378/1/functions/customStatusCode', {
4214+
method: 'POST',
4215+
headers: {
4216+
'X-Parse-Application-Id': 'test',
4217+
'X-Parse-REST-API-Key': 'rest',
4218+
}
4219+
});
4220+
4221+
expect(response.status).toBe(201);
4222+
});
4223+
4224+
it('should set custom headers in cloud function', async () => {
4225+
Parse.Cloud.define('customHeaders', (req, res) => {
4226+
res.setHeader('X-Custom-Header', 'custom-value');
4227+
return true;
4228+
});
4229+
4230+
const response = await fetch('http://localhost:8378/1/functions/customHeaders', {
4231+
method: 'POST',
4232+
headers: {
4233+
'X-Parse-Application-Id': 'test',
4234+
'X-Parse-REST-API-Key': 'rest',
4235+
}
4236+
});
4237+
4238+
expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
4239+
});
4240+
4241+
it('should set custom statusCode in beforeLogin hook', async () => {
4242+
Parse.Cloud.beforeLogin((req, res) => {
4243+
res.status(201);
4244+
});
4245+
4246+
await Parse.User.signUp('[email protected]', 'password');
4247+
const response = await fetch('http://localhost:8378/1/login', {
4248+
method: 'POST',
4249+
headers: {
4250+
'X-Parse-Application-Id': 'test',
4251+
'X-Parse-REST-API-Key': 'rest',
4252+
},
4253+
body: JSON.stringify({ username: '[email protected]', password: 'password' })
4254+
});
4255+
4256+
expect(response.status).toBe(201);
4257+
});
4258+
4259+
it('should set custom headers in beforeLogin hook', async () => {
4260+
Parse.Cloud.beforeLogin((req, res) => {
4261+
res.setHeader('X-Custom-Header', 'custom-value');
4262+
});
4263+
4264+
await Parse.User.signUp('[email protected]', 'password');
4265+
const response = await fetch('http://localhost:8378/1/login', {
4266+
method: 'POST',
4267+
headers: {
4268+
'X-Parse-Application-Id': 'test',
4269+
'X-Parse-REST-API-Key': 'rest',
4270+
},
4271+
body: JSON.stringify({ username: '[email protected]', password: 'password' })
4272+
});
4273+
4274+
expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
4275+
});
4276+
4277+
it('should set custom statusCode in file trigger', async () => {
4278+
Parse.Cloud.beforeSave(Parse.File, (req, res) => {
4279+
res.status(201);
4280+
});
4281+
4282+
const file = new Parse.File('test.txt', [1, 2, 3]);
4283+
const response = await fetch('http://localhost:8378/1/files/test.txt', {
4284+
method: 'POST',
4285+
headers: {
4286+
'X-Parse-Application-Id': 'test',
4287+
'X-Parse-REST-API-Key': 'rest',
4288+
'Content-Type': 'text/plain',
4289+
},
4290+
body: file.getData()
4291+
});
4292+
4293+
expect(response.status).toBe(201);
4294+
});
4295+
4296+
it('should set custom headers in file trigger', async () => {
4297+
Parse.Cloud.beforeSave(Parse.File, (req, res) => {
4298+
res.setHeader('X-Custom-Header', 'custom-value');
4299+
});
4300+
4301+
const file = new Parse.File('test.txt', [1, 2, 3]);
4302+
const response = await fetch('http://localhost:8378/1/files/test.txt', {
4303+
method: 'POST',
4304+
headers: {
4305+
'X-Parse-Application-Id': 'test',
4306+
'X-Parse-REST-API-Key': 'rest',
4307+
'Content-Type': 'text/plain',
4308+
},
4309+
body: file.getData()
4310+
});
4311+
4312+
expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
4313+
});
4314+
})

spec/helper.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ const defaultConfiguration = {
112112
readOnlyMasterKey: 'read-only-test',
113113
fileKey: 'test',
114114
directAccess: true,
115-
silent,
115+
silent: false,
116116
verbose: !silent,
117117
logLevel,
118118
liveQuery: {

src/Controllers/AdaptableController.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class AdaptableController {
6060
}, {});
6161

6262
if (Object.keys(mismatches).length > 0) {
63-
throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
63+
// throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
6464
}
6565
}
6666
}

src/Controllers/LiveQueryController.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ParseCloudCodePublisher } from '../LiveQuery/ParseCloudCodePublisher';
22
import { LiveQueryOptions } from '../Options';
3-
import { getClassName } from './../triggers';
3+
import { getClassName } from '../triggers';
44
export class LiveQueryController {
55
classNames: any;
66
liveQueryPublisher: any;

src/PromiseRouter.js

+39-49
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import Parse from 'parse/node';
99
import express from 'express';
1010
import log from './logger';
11-
import { inspect } from 'util';
1211
const Layer = require('express/lib/router/layer');
1312

1413
function validateParameter(key, value) {
@@ -135,68 +134,59 @@ export default class PromiseRouter {
135134
// Express handlers should never throw; if a promise handler throws we
136135
// just treat it like it resolved to an error.
137136
function makeExpressHandler(appId, promiseHandler) {
138-
return function (req, res, next) {
137+
return async function (req, res, next) {
139138
try {
140139
const url = maskSensitiveUrl(req);
141-
const body = Object.assign({}, req.body);
140+
const body = { ...req.body };
142141
const method = req.method;
143142
const headers = req.headers;
143+
144144
log.logRequest({
145145
method,
146146
url,
147147
headers,
148148
body,
149149
});
150-
promiseHandler(req)
151-
.then(
152-
result => {
153-
if (!result.response && !result.location && !result.text) {
154-
log.error('the handler did not include a "response" or a "location" field');
155-
throw 'control should not get here';
156-
}
157-
158-
log.logResponse({ method, url, result });
159-
160-
var status = result.status || 200;
161-
res.status(status);
162-
163-
if (result.headers) {
164-
Object.keys(result.headers).forEach(header => {
165-
res.set(header, result.headers[header]);
166-
});
167-
}
168-
169-
if (result.text) {
170-
res.send(result.text);
171-
return;
172-
}
173-
174-
if (result.location) {
175-
res.set('Location', result.location);
176-
// Override the default expressjs response
177-
// as it double encodes %encoded chars in URL
178-
if (!result.response) {
179-
res.send('Found. Redirecting to ' + result.location);
180-
return;
181-
}
182-
}
183-
res.json(result.response);
184-
},
185-
error => {
186-
next(error);
187-
}
188-
)
189-
.catch(e => {
190-
log.error(`Error generating response. ${inspect(e)}`, { error: e });
191-
next(e);
192-
});
193-
} catch (e) {
194-
log.error(`Error handling request: ${inspect(e)}`, { error: e });
195-
next(e);
150+
151+
const result = await promiseHandler(req);
152+
if (!result.response && !result.location && !result.text) {
153+
log.error('The handler did not include a "response", "location", or "text" field');
154+
throw new Error('Handler result is missing required fields.');
155+
}
156+
157+
log.logResponse({ method, url, result });
158+
159+
const status = result.status || 200;
160+
res.status(status);
161+
162+
if (result.headers) {
163+
for (const [header, value] of Object.entries(result.headers)) {
164+
res.set(header, value);
165+
}
166+
}
167+
168+
if (result.text) {
169+
res.send(result.text);
170+
return;
171+
}
172+
173+
if (result.location) {
174+
res.set('Location', result.location);
175+
if (!result.response) {
176+
res.send(`Found. Redirecting to ${result.location}`);
177+
return;
178+
}
179+
}
180+
181+
res.json(result.response);
182+
} catch (error) {
183+
log.error(`Error handling request: ${error.message}`, { error });
184+
next(error);
196185
}
197186
};
198187
}
199188

189+
200190
function maskSensitiveUrl(req) {
201191
let maskUrl = req.originalUrl.toString();
202192
const shouldMaskUrl =

src/RestQuery.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ async function RestQuery({
4646
runAfterFind = true,
4747
runBeforeFind = true,
4848
context,
49+
response
4950
}) {
5051
if (![RestQuery.Method.find, RestQuery.Method.get].includes(method)) {
5152
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad query type');
@@ -60,7 +61,8 @@ async function RestQuery({
6061
config,
6162
auth,
6263
context,
63-
method === RestQuery.Method.get
64+
method === RestQuery.Method.get,
65+
response
6466
)
6567
: Promise.resolve({ restWhere, restOptions });
6668

0 commit comments

Comments
 (0)