Skip to content

Commit d4f3636

Browse files
committed
Support points in HTTP driver
This commit makes experimental HTTP driver able to receive spatial 2D and 3D points and return them as `Point` objects in records. Note that no metadata is returned by the REST endpoint for nested points (in lists or maps) so they will not be converted to `Point` objects.
1 parent b1ed174 commit d4f3636

File tree

2 files changed

+100
-10
lines changed

2 files changed

+100
-10
lines changed

src/v1/internal/http/http-response-converter.js

+64-10
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
import {isInt} from '../../integer';
2121
import {Node, Path, PathSegment, Relationship} from '../../graph-types';
22-
import {Neo4jError} from '../../error';
22+
import {Neo4jError, PROTOCOL_ERROR} from '../../error';
23+
import {isPoint, Point} from '../../spatial-types';
2324

2425
const CREDENTIALS_EXPIRED_CODE = 'Neo.ClientError.Security.CredentialsExpired';
2526

@@ -137,11 +138,13 @@ function encodeQueryParameters(parameters) {
137138

138139
function encodeQueryParameter(value) {
139140
if (value instanceof Node) {
140-
throw new Neo4jError('It is not allowed to pass nodes in query parameters');
141+
throw new Neo4jError('It is not allowed to pass nodes in query parameters', PROTOCOL_ERROR);
141142
} else if (value instanceof Relationship) {
142-
throw new Neo4jError('It is not allowed to pass relationships in query parameters');
143+
throw new Neo4jError('It is not allowed to pass relationships in query parameters', PROTOCOL_ERROR);
143144
} else if (value instanceof Path) {
144-
throw new Neo4jError('It is not allowed to pass paths in query parameters');
145+
throw new Neo4jError('It is not allowed to pass paths in query parameters', PROTOCOL_ERROR);
146+
} else if (isPoint(value)) {
147+
throw newUnsupportedParameterError('points');
145148
} else if (isInt(value)) {
146149
return value.toNumber();
147150
} else if (Array.isArray(value)) {
@@ -153,6 +156,11 @@ function encodeQueryParameter(value) {
153156
}
154157
}
155158

159+
function newUnsupportedParameterError(name) {
160+
return new Neo4jError(`It is not allowed to pass ${name} in query parameters when using HTTP endpoint. ` +
161+
`Please consider using Cypher functions to create ${name} so that query parameters are plain objects.`, PROTOCOL_ERROR);
162+
}
163+
156164
function extractResult(response) {
157165
const results = response.results;
158166
if (results) {
@@ -222,23 +230,25 @@ function extractRawRecordElement(index, data, nodesById, relationshipsById) {
222230
const elementMetadata = data.meta ? data.meta[index] : null;
223231

224232
if (elementMetadata) {
225-
// element is either a Node, Relationship or Path
226-
return convertComplexValue(elementMetadata, nodesById, relationshipsById);
233+
// element is either a graph, spatial or temporal type
234+
return convertComplexValue(element, elementMetadata, nodesById, relationshipsById);
227235
} else {
228236
// element is a primitive, like number, string, array or object
229237
return convertPrimitiveValue(element);
230238
}
231239
}
232240

233-
function convertComplexValue(elementMetadata, nodesById, relationshipsById) {
241+
function convertComplexValue(element, elementMetadata, nodesById, relationshipsById) {
234242
if (isNodeMetadata(elementMetadata)) {
235243
return nodesById[elementMetadata.id];
236244
} else if (isRelationshipMetadata(elementMetadata)) {
237245
return relationshipsById[elementMetadata.id];
238246
} else if (isPathMetadata(elementMetadata)) {
239247
return convertPath(elementMetadata, nodesById, relationshipsById);
248+
} else if (isPointMetadata(elementMetadata)) {
249+
return convertPoint(element);
240250
} else {
241-
return null;
251+
return element;
242252
}
243253
}
244254

@@ -295,6 +305,42 @@ function createPath(pathSegments) {
295305
return new Path(pathStartNode, pathEndNode, pathSegments);
296306
}
297307

308+
function convertPoint(element) {
309+
const type = element.type;
310+
if (type !== 'Point') {
311+
throw new Neo4jError(`Unexpected Point type received: ${JSON.stringify(element)}`);
312+
}
313+
314+
const coordinates = element.coordinates;
315+
if (!Array.isArray(coordinates) && (coordinates.length !== 2 || coordinates.length !== 3)) {
316+
throw new Neo4jError(`Unexpected Point coordinates received: ${JSON.stringify(element)}`);
317+
}
318+
319+
const srid = convertCrsToId(element);
320+
321+
return new Point(srid, ...coordinates);
322+
}
323+
324+
function convertCrsToId(element) {
325+
const crs = element.crs;
326+
if (!crs || !crs.name) {
327+
throw new Neo4jError(`Unexpected Point crs received: ${JSON.stringify(element)}`);
328+
}
329+
const name = crs.name.toLowerCase();
330+
331+
if (name === 'wgs-84') {
332+
return 4326;
333+
} else if (name === 'wgs-84-3d') {
334+
return 4979;
335+
} else if (name === 'cartesian') {
336+
return 7203;
337+
} else if (name === 'cartesian-3d') {
338+
return 9157;
339+
} else {
340+
throw new Neo4jError(`Unexpected Point crs received: ${JSON.stringify(element)}`);
341+
}
342+
}
343+
298344
function convertPrimitiveValue(element) {
299345
if (element == null || element === undefined) {
300346
return null;
@@ -317,11 +363,19 @@ function convertNumber(value) {
317363
}
318364

319365
function isNodeMetadata(metadata) {
320-
return !Array.isArray(metadata) && typeof metadata === 'object' && metadata.type === 'node';
366+
return isMetadataForType('node', metadata);
321367
}
322368

323369
function isRelationshipMetadata(metadata) {
324-
return !Array.isArray(metadata) && typeof metadata === 'object' && metadata.type === 'relationship';
370+
return isMetadataForType('relationship', metadata);
371+
}
372+
373+
function isPointMetadata(metadata) {
374+
return isMetadataForType('point', metadata);
375+
}
376+
377+
function isMetadataForType(name, metadata) {
378+
return !Array.isArray(metadata) && typeof metadata === 'object' && metadata.type === name;
325379
}
326380

327381
function isPathMetadata(metadata) {

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

+36
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,29 @@ describe('http driver', () => {
273273
});
274274
}, 20000);
275275

276+
it('should fail to pass node as a query parameter', done => {
277+
testUnsupportedQueryParameterWithHttpDriver(new neo4j.types.Node(neo4j.int(1), ['Person'], {name: 'Bob'}), done);
278+
});
279+
280+
it('should fail to pass relationship as a query parameter', done => {
281+
testUnsupportedQueryParameterWithHttpDriver(new neo4j.types.Relationship(neo4j.int(1), neo4j.int(2), neo4j.int(3), 'KNOWS', {since: 42}), done);
282+
});
283+
284+
it('should fail to pass path as a query parameter', done => {
285+
const node1 = new neo4j.types.Node(neo4j.int(1), ['Person'], {name: 'Alice'});
286+
const node2 = new neo4j.types.Node(neo4j.int(2), ['Person'], {name: 'Bob'});
287+
testUnsupportedQueryParameterWithHttpDriver(new neo4j.types.Path(node1, node2, []), done);
288+
});
289+
290+
it('should receive points', done => {
291+
testReceivingOfResults([
292+
'RETURN point({x: 42.341, y: 125.0})',
293+
'RETURN point({x: 13.2, y: 22.2, z: 33.3})',
294+
'RETURN point({x: 92.3, y: 71.2, z: 2.12345, crs: "wgs-84-3d"})',
295+
'RETURN point({longitude: 56.7, latitude: 12.78})',
296+
], done);
297+
});
298+
276299
function testSendAndReceiveWithReturnQuery(values, done) {
277300
const query = 'RETURN $value';
278301

@@ -337,6 +360,19 @@ describe('http driver', () => {
337360
});
338361
}
339362

363+
function testUnsupportedQueryParameterWithHttpDriver(value, done) {
364+
const session = httpDriver.session();
365+
session.run('RETURN $value', {value: value}).then(() => {
366+
done.fail('Should not be possible to send ' + value);
367+
}).catch(error => {
368+
expect(error.name).toEqual('Neo4jError');
369+
expect(error.code).toEqual(neo4j.error.PROTOCOL_ERROR);
370+
session.close(() => {
371+
done();
372+
});
373+
});
374+
}
375+
340376
function databaseSupportsTransactionTerminationInLocks() {
341377
return serverVersion.compareTo(VERSION_3_1_0) >= 0;
342378
}

0 commit comments

Comments
 (0)