Skip to content

Commit 0ce603e

Browse files
authored
Merge pull request #388 from lutovich/1.6-param-type-validation
Check parameter types in spatial and temporal types
2 parents 41e459a + b1733e5 commit 0ce603e

File tree

7 files changed

+207
-35
lines changed

7 files changed

+207
-35
lines changed

src/v1/internal/util.js

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* limitations under the License.
1818
*/
1919

20+
import {isInt} from '../integer';
21+
2022
const ENCRYPTION_ON = "ENCRYPTION_ON";
2123
const ENCRYPTION_OFF = "ENCRYPTION_OFF";
2224

@@ -71,6 +73,20 @@ function assertString(obj, objName) {
7173
return obj;
7274
}
7375

76+
function assertNumber(obj, objName) {
77+
if (typeof obj !== 'number') {
78+
throw new TypeError(objName + ' expected to be a number but was: ' + JSON.stringify(obj));
79+
}
80+
return obj;
81+
}
82+
83+
function assertNumberOrInteger(obj, objName) {
84+
if (typeof obj !== 'number' && !isInt(obj)) {
85+
throw new TypeError(objName + ' expected to be either a number or an Integer object but was: ' + JSON.stringify(obj));
86+
}
87+
return obj;
88+
}
89+
7490
function assertCypherStatement(obj) {
7591
assertString(obj, 'Cypher statement');
7692
if (obj.trim().length === 0) {
@@ -94,6 +110,8 @@ export {
94110
isEmptyObjectOrNull,
95111
isString,
96112
assertString,
113+
assertNumber,
114+
assertNumberOrInteger,
97115
validateStatementAndParameters,
98116
ENCRYPTION_ON,
99117
ENCRYPTION_OFF

src/v1/spatial-types.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* See the License for the specific language governing permissions and
1717
* limitations under the License.
1818
*/
19+
import {assertNumber, assertNumberOrInteger} from './internal/util';
1920

2021
const POINT_IDENTIFIER_PROPERTY = '__isPoint__';
2122

@@ -33,10 +34,10 @@ export class Point {
3334
* @param {number} [z=undefined] the <code>y</code> coordinate of the point or <code>undefined</code> if point has 2 dimensions.
3435
*/
3536
constructor(srid, x, y, z) {
36-
this.srid = srid;
37-
this.x = x;
38-
this.y = y;
39-
this.z = z;
37+
this.srid = assertNumberOrInteger(srid, 'SRID');
38+
this.x = assertNumber(x, 'X coordinate');
39+
this.y = assertNumber(y, 'Y coordinate');
40+
this.z = (z === null || z === undefined) ? z : assertNumber(z, 'Z coordinate');
4041
Object.freeze(this);
4142
}
4243

src/v1/temporal-types.js

+33-28
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919

2020
import * as util from './internal/temporal-util';
21+
import {assertNumberOrInteger, assertString} from './internal/util';
2122
import {newError} from './error';
2223

2324
const IDENTIFIER_PROPERTY_ATTRIBUTES = {
@@ -47,8 +48,10 @@ export class Duration {
4748
* @param {Integer|number} nanoseconds the number of nanoseconds for the new duration.
4849
*/
4950
constructor(months, days, seconds, nanoseconds) {
50-
this.months = months;
51-
this.days = days;
51+
this.months = assertNumberOrInteger(months, 'Months');
52+
this.days = assertNumberOrInteger(days, 'Days');
53+
assertNumberOrInteger(seconds, 'Seconds');
54+
assertNumberOrInteger(nanoseconds, 'Nanoseconds');
5255
this.seconds = util.normalizeSecondsForDuration(seconds, nanoseconds);
5356
this.nanoseconds = util.normalizeNanosecondsForDuration(nanoseconds);
5457
Object.freeze(this);
@@ -84,10 +87,10 @@ export class LocalTime {
8487
* @param {Integer|number} nanosecond the nanosecond for the new local time.
8588
*/
8689
constructor(hour, minute, second, nanosecond) {
87-
this.hour = hour;
88-
this.minute = minute;
89-
this.second = second;
90-
this.nanosecond = nanosecond;
90+
this.hour = assertNumberOrInteger(hour, 'Hour');
91+
this.minute = assertNumberOrInteger(minute, 'Minute');
92+
this.second = assertNumberOrInteger(second, 'Second');
93+
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
9194
Object.freeze(this);
9295
}
9396

@@ -122,11 +125,11 @@ export class Time {
122125
* @param {Integer|number} timeZoneOffsetSeconds the time zone offset in seconds.
123126
*/
124127
constructor(hour, minute, second, nanosecond, timeZoneOffsetSeconds) {
125-
this.hour = hour;
126-
this.minute = minute;
127-
this.second = second;
128-
this.nanosecond = nanosecond;
129-
this.timeZoneOffsetSeconds = timeZoneOffsetSeconds;
128+
this.hour = assertNumberOrInteger(hour, 'Hour');
129+
this.minute = assertNumberOrInteger(minute, 'Minute');
130+
this.second = assertNumberOrInteger(second, 'Second');
131+
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
132+
this.timeZoneOffsetSeconds = assertNumberOrInteger(timeZoneOffsetSeconds, 'Time zone offset in seconds');
130133
Object.freeze(this);
131134
}
132135

@@ -159,9 +162,9 @@ export class Date {
159162
* @param {Integer|number} day the day for the new local date.
160163
*/
161164
constructor(year, month, day) {
162-
this.year = year;
163-
this.month = month;
164-
this.day = day;
165+
this.year = assertNumberOrInteger(year, 'Year');
166+
this.month = assertNumberOrInteger(month, 'Month');
167+
this.day = assertNumberOrInteger(day, 'Day');
165168
Object.freeze(this);
166169
}
167170

@@ -198,13 +201,13 @@ export class LocalDateTime {
198201
* @param {Integer|number} nanosecond the nanosecond for the new local time.
199202
*/
200203
constructor(year, month, day, hour, minute, second, nanosecond) {
201-
this.year = year;
202-
this.month = month;
203-
this.day = day;
204-
this.hour = hour;
205-
this.minute = minute;
206-
this.second = second;
207-
this.nanosecond = nanosecond;
204+
this.year = assertNumberOrInteger(year, 'Year');
205+
this.month = assertNumberOrInteger(month, 'Month');
206+
this.day = assertNumberOrInteger(day, 'Day');
207+
this.hour = assertNumberOrInteger(hour, 'Hour');
208+
this.minute = assertNumberOrInteger(minute, 'Minute');
209+
this.second = assertNumberOrInteger(second, 'Second');
210+
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
208211
Object.freeze(this);
209212
}
210213

@@ -243,13 +246,13 @@ export class DateTime {
243246
* @param {string|null} timeZoneId the time zone id for the new date-time. Either this argument or <code>timeZoneOffsetSeconds</code> should be defined.
244247
*/
245248
constructor(year, month, day, hour, minute, second, nanosecond, timeZoneOffsetSeconds, timeZoneId) {
246-
this.year = year;
247-
this.month = month;
248-
this.day = day;
249-
this.hour = hour;
250-
this.minute = minute;
251-
this.second = second;
252-
this.nanosecond = nanosecond;
249+
this.year = assertNumberOrInteger(year, 'Year');
250+
this.month = assertNumberOrInteger(month, 'Month');
251+
this.day = assertNumberOrInteger(day, 'Day');
252+
this.hour = assertNumberOrInteger(hour, 'Hour');
253+
this.minute = assertNumberOrInteger(minute, 'Minute');
254+
this.second = assertNumberOrInteger(second, 'Second');
255+
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
253256

254257
const [offset, id] = verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId);
255258
this.timeZoneOffsetSeconds = offset;
@@ -289,8 +292,10 @@ function verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId) {
289292
const idDefined = timeZoneId && timeZoneId !== '';
290293

291294
if (offsetDefined && !idDefined) {
295+
assertNumberOrInteger(timeZoneOffsetSeconds, 'Time zone offset in seconds');
292296
return [timeZoneOffsetSeconds, null];
293297
} else if (!offsetDefined && idDefined) {
298+
assertString(timeZoneId, 'Time zone ID');
294299
return [null, timeZoneId];
295300
} else if (offsetDefined && idDefined) {
296301
throw newError(`Unable to create DateTime with both time zone offset and id. Please specify either of them. Given offset: ${timeZoneOffsetSeconds} and id: ${timeZoneId}`);

test/internal/http/http-driver.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ describe('http driver', () => {
346346
return;
347347
}
348348

349-
testUnsupportedQueryParameterWithHttpDriver(new neo4j.types.LocalDateTime(2000, 10, 12, 10, 10, 10), done);
349+
testUnsupportedQueryParameterWithHttpDriver(new neo4j.types.LocalDateTime(2000, 10, 12, 10, 10, 10, 10), done);
350350
});
351351

352352
it('should fail to pass local time as a query parameter', done => {

test/internal/util.test.js

+58
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919

2020
import * as util from '../../src/v1/internal/util';
21+
import {int} from '../../src/v1';
2122

2223
describe('util', () => {
2324

@@ -93,6 +94,47 @@ describe('util', () => {
9394
verifyInvalidQueryParameters(() => null);
9495
});
9596

97+
it('should check numbers and integers', () => {
98+
verifyValidNumber(0);
99+
verifyValidNumber(1);
100+
verifyValidNumber(42);
101+
verifyValidNumber(-42);
102+
verifyValidNumber(12.001);
103+
verifyValidNumber(-493.423);
104+
105+
verifyInvalidNumber(int(0));
106+
verifyInvalidNumber(int(1));
107+
verifyInvalidNumber(int(147123));
108+
verifyInvalidNumber('');
109+
verifyInvalidNumber('42');
110+
verifyInvalidNumber([]);
111+
verifyInvalidNumber([42]);
112+
verifyInvalidNumber({});
113+
verifyInvalidNumber({value: 42});
114+
});
115+
116+
it('should check numbers and integers', () => {
117+
verifyValidNumberOrInteger(0);
118+
verifyValidNumberOrInteger(1);
119+
verifyValidNumberOrInteger(42);
120+
verifyValidNumberOrInteger(-42);
121+
verifyValidNumberOrInteger(12.001);
122+
verifyValidNumberOrInteger(-493.423);
123+
124+
verifyValidNumberOrInteger(int(0));
125+
verifyValidNumberOrInteger(int(42));
126+
verifyValidNumberOrInteger(int(1241));
127+
verifyValidNumberOrInteger(int(441));
128+
verifyValidNumberOrInteger(int(-100500));
129+
130+
verifyInvalidNumberOrInteger('');
131+
verifyInvalidNumberOrInteger('42');
132+
verifyInvalidNumberOrInteger([]);
133+
verifyInvalidNumberOrInteger([42]);
134+
verifyInvalidNumberOrInteger({});
135+
verifyInvalidNumberOrInteger({value: 42});
136+
});
137+
96138
function verifyValidString(str) {
97139
expect(util.assertString(str, 'Test string')).toBe(str);
98140
}
@@ -101,6 +143,22 @@ describe('util', () => {
101143
expect(() => util.assertString(str, 'Test string')).toThrowError(TypeError);
102144
}
103145

146+
function verifyValidNumber(obj) {
147+
expect(util.assertNumber(obj, 'Test number')).toBe(obj);
148+
}
149+
150+
function verifyInvalidNumber(obj) {
151+
expect(() => util.assertNumber(obj, 'Test number')).toThrowError(TypeError);
152+
}
153+
154+
function verifyValidNumberOrInteger(obj) {
155+
expect(util.assertNumberOrInteger(obj, 'Test object')).toEqual(obj);
156+
}
157+
158+
function verifyInvalidNumberOrInteger(obj) {
159+
expect(() => util.assertNumberOrInteger(obj, 'Test object')).toThrowError(TypeError);
160+
}
161+
104162
function verifyInvalidCypherStatement(str) {
105163
expect(() => util.validateStatementAndParameters(str, {})).toThrowError(TypeError);
106164
}

test/v1/spatial-types.test.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* limitations under the License.
1818
*/
1919

20-
import neo4j from '../../src/v1';
20+
import neo4j, {int} from '../../src/v1';
2121
import sharedNeo4j from '../internal/shared-neo4j';
2222
import {ServerVersion, VERSION_3_4_0} from '../../src/v1/internal/server-version';
2323
import {isPoint, Point} from '../../src/v1/spatial-types';
@@ -210,6 +210,35 @@ describe('spatial-types', () => {
210210
expect(point6.toString()).toEqual('Point{srid=7203, x=23.9378123, y=67.3891}');
211211
});
212212

213+
it('should validate types of constructor arguments for Point', () => {
214+
expect(() => new Point('1', 2, 3)).toThrowError(TypeError);
215+
expect(() => new Point(1, '2', 3)).toThrowError(TypeError);
216+
expect(() => new Point(1, 2, '3')).toThrowError(TypeError);
217+
expect(() => new Point(1, 2, '3')).toThrowError(TypeError);
218+
219+
expect(() => new Point('1', 2, 3, null)).toThrowError(TypeError);
220+
expect(() => new Point(1, '2', 3, null)).toThrowError(TypeError);
221+
expect(() => new Point(1, 2, '3', null)).toThrowError(TypeError);
222+
expect(() => new Point(1, 2, '3', null)).toThrowError(TypeError);
223+
224+
expect(() => new Point('1', 2, 3, 4)).toThrowError(TypeError);
225+
expect(() => new Point(1, '2', 3, 4)).toThrowError(TypeError);
226+
expect(() => new Point(1, 2, '3', 4)).toThrowError(TypeError);
227+
expect(() => new Point(1, 2, '3', 4)).toThrowError(TypeError);
228+
229+
expect(() => new Point(1, int(2), 3, 4)).toThrowError(TypeError);
230+
expect(() => new Point(1, 2, int(3), 4)).toThrowError(TypeError);
231+
expect(() => new Point(1, 2, 3, int(4))).toThrowError(TypeError);
232+
233+
expect(new Point(1, 2, 3, 4)).toBeDefined();
234+
235+
expect(new Point(int(1), 2, 3, undefined)).toBeDefined();
236+
expect(new Point(1, 2, 3, undefined)).toBeDefined();
237+
238+
expect(new Point(1, 2, 3, null)).toBeDefined();
239+
expect(new Point(int(1), 2, 3, null)).toBeDefined();
240+
});
241+
213242
function testReceivingOfPoints(done, query, pointChecker) {
214243
if (neo4jDoesNotSupportPoints(done)) {
215244
return;

test/v1/temporal-types.test.js

+62-1
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ describe('temporal-types', () => {
513513
});
514514

515515
it('should expose local date-time components in date-time with zone ID', () => {
516-
const zonedDateTime = dateTimeWithZoneId(2356, 7, 29, 23, 32, 11, 9346458, 3600, randomZoneId());
516+
const zonedDateTime = dateTimeWithZoneId(2356, 7, 29, 23, 32, 11, 9346458, randomZoneId());
517517

518518
expect(zonedDateTime.year).toEqual(neo4j.int(2356));
519519
expect(zonedDateTime.month).toEqual(neo4j.int(7));
@@ -597,6 +597,67 @@ describe('temporal-types', () => {
597597
expect(duration6.nanoseconds).toEqual(neo4j.int(876543001));
598598
});
599599

600+
it('should validate types of constructor arguments for Duration', () => {
601+
expect(() => new neo4j.types.Duration('1', 2, 3, 4)).toThrowError(TypeError);
602+
expect(() => new neo4j.types.Duration(1, '2', 3, 4)).toThrowError(TypeError);
603+
expect(() => new neo4j.types.Duration(1, 2, [3], 4)).toThrowError(TypeError);
604+
expect(() => new neo4j.types.Duration(1, 2, 3, {value: 4})).toThrowError(TypeError);
605+
expect(() => new neo4j.types.Duration({months: 1, days: 2, seconds: 3, nanoseconds: 4})).toThrowError(TypeError);
606+
});
607+
608+
it('should validate types of constructor arguments for LocalTime', () => {
609+
expect(() => new neo4j.types.LocalTime('1', 2, 3, 4)).toThrowError(TypeError);
610+
expect(() => new neo4j.types.LocalTime(1, '2', 3, 4)).toThrowError(TypeError);
611+
expect(() => new neo4j.types.LocalTime(1, 2, [3], 4)).toThrowError(TypeError);
612+
expect(() => new neo4j.types.LocalTime(1, 2, 3, {value: 4})).toThrowError(TypeError);
613+
expect(() => new neo4j.types.LocalTime({hour: 1, minute: 2, seconds: 3, nanosecond: 4})).toThrowError(TypeError);
614+
});
615+
616+
it('should validate types of constructor arguments for Time', () => {
617+
expect(() => new neo4j.types.Time('1', 2, 3, 4, 5)).toThrowError(TypeError);
618+
expect(() => new neo4j.types.Time(1, '2', 3, 4, 5)).toThrowError(TypeError);
619+
expect(() => new neo4j.types.Time(1, 2, [3], 4, 5)).toThrowError(TypeError);
620+
expect(() => new neo4j.types.Time(1, 2, 3, {value: 4}, 5)).toThrowError(TypeError);
621+
expect(() => new neo4j.types.Time(1, 2, 3, 4, () => 5)).toThrowError(TypeError);
622+
expect(() => new neo4j.types.Time({hour: 1, minute: 2, seconds: 3, nanosecond: 4, timeZoneOffsetSeconds: 5})).toThrowError(TypeError);
623+
});
624+
625+
it('should validate types of constructor arguments for Date', () => {
626+
expect(() => new neo4j.types.Date('1', 2, 3)).toThrowError(TypeError);
627+
expect(() => new neo4j.types.Date(1, [2], 3)).toThrowError(TypeError);
628+
expect(() => new neo4j.types.Date(1, 2, () => 3)).toThrowError(TypeError);
629+
expect(() => new neo4j.types.Date({year: 1, month: 2, day: 3})).toThrowError(TypeError);
630+
});
631+
632+
it('should validate types of constructor arguments for LocalDateTime', () => {
633+
expect(() => new neo4j.types.LocalDateTime('1', 2, 3, 4, 5, 6, 7)).toThrowError(TypeError);
634+
expect(() => new neo4j.types.LocalDateTime(1, '2', 3, 4, 5, 6, 7)).toThrowError(TypeError);
635+
expect(() => new neo4j.types.LocalDateTime(1, 2, [3], 4, 5, 6, 7)).toThrowError(TypeError);
636+
expect(() => new neo4j.types.LocalDateTime(1, 2, 3, [4], 5, 6, 7)).toThrowError(TypeError);
637+
expect(() => new neo4j.types.LocalDateTime(1, 2, 3, 4, () => 5, 6, 7)).toThrowError(TypeError);
638+
expect(() => new neo4j.types.LocalDateTime(1, 2, 3, 4, 5, () => 6, 7)).toThrowError(TypeError);
639+
expect(() => new neo4j.types.LocalDateTime(1, 2, 3, 4, 5, 6, {value: 7})).toThrowError(TypeError);
640+
expect(() => new neo4j.types.LocalDateTime({year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6, nanosecond: 7})).toThrowError(TypeError);
641+
});
642+
643+
it('should validate types of constructor arguments for DateTime', () => {
644+
expect(() => new neo4j.types.DateTime('1', 2, 3, 4, 5, 6, 7, 8)).toThrowError(TypeError);
645+
expect(() => new neo4j.types.DateTime(1, '2', 3, 4, 5, 6, 7, 8)).toThrowError(TypeError);
646+
expect(() => new neo4j.types.DateTime(1, 2, [3], 4, 5, 6, 7, 8)).toThrowError(TypeError);
647+
expect(() => new neo4j.types.DateTime(1, 2, 3, [4], 5, 6, 7, 8)).toThrowError(TypeError);
648+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, () => 5, 6, 7, 8)).toThrowError(TypeError);
649+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, () => 6, 7, 8)).toThrowError(TypeError);
650+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, {value: 7}, 8)).toThrowError(TypeError);
651+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, 7, {value: 8})).toThrowError(TypeError);
652+
653+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, 7, null, {timeZoneId: 'UK'})).toThrowError(TypeError);
654+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, 7, null, ['UK'])).toThrowError(TypeError);
655+
656+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, 7)).toThrow();
657+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, 7, null, null)).toThrow();
658+
expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, 7, 8, 'UK')).toThrow();
659+
});
660+
600661
function testSendAndReceiveRandomTemporalValues(valueGenerator, done) {
601662
const asyncFunction = (index, callback) => {
602663
const next = () => callback();

0 commit comments

Comments
 (0)