'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _typeof2 = require('babel-runtime/helpers/typeof'); var _typeof3 = _interopRequireDefault(_typeof2); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _stringify = require('babel-runtime/core-js/json/stringify'); var _stringify2 = _interopRequireDefault(_stringify); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _integer = require('../../integer'); var _graphTypes = require('../../graph-types'); var _error = require('../../error'); var _spatialTypes = require('../../spatial-types'); var _temporalTypes = require('../../temporal-types'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var CREDENTIALS_EXPIRED_CODE = 'Neo.ClientError.Security.CredentialsExpired'; /** * Copyright (c) 2002-2018 "Neo4j," * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var HttpResponseConverter = function () { function HttpResponseConverter() { (0, _classCallCheck3.default)(this, HttpResponseConverter); } (0, _createClass3.default)(HttpResponseConverter, [{ key: 'encodeStatementParameters', value: function encodeStatementParameters(parameters) { return encodeQueryParameters(parameters); } /** * Attempts to extract error from transactional cypher endpoint response and convert it to {@link Neo4jError}. * @param {object} response the response. * @return {Neo4jError|null} new driver friendly error, if exists. */ }, { key: 'extractError', value: function extractError(response) { var errors = response.errors; if (errors) { var error = errors[0]; if (error) { // endpoint returns 'Neo.ClientError.Security.Forbidden' code and 'password_change' that points to another endpoint // this is different from code returned via Bolt and less descriptive // make code same as in Bolt, if password change is required var code = response.password_change ? CREDENTIALS_EXPIRED_CODE : error.code; var message = error.message; return new _error.Neo4jError(message, code); } } return null; } /** * Extracts transaction id from the db/data/transaction endpoint response. * @param {object} response the response. * @return {number} the transaction id. */ }, { key: 'extractTransactionId', value: function extractTransactionId(response) { var commitUrl = response.commit; if (commitUrl) { // extract id 42 from commit url like 'http://localhost:7474/db/data/transaction/42/commit' var url = commitUrl.replace('/commit', ''); var transactionIdString = url.substring(url.lastIndexOf('/') + 1); var transactionId = parseInt(transactionIdString, 10); if (transactionId || transactionId === 0) { return transactionId; } } throw new _error.Neo4jError('Unable to extract transaction id from the response JSON: ' + (0, _stringify2.default)(response)); } /** * Extracts record metadata (array of column names) from transactional cypher endpoint response. * @param {object} response the response. * @return {object} new metadata object. */ }, { key: 'extractRecordMetadata', value: function extractRecordMetadata(response) { var result = extractResult(response); var fields = result ? result.columns : []; return { fields: fields }; } /** * Extracts raw records (each raw record is just an array of value) from transactional cypher endpoint response. * @param {object} response the response. * @return {object[][]} raw records from the response. */ }, { key: 'extractRawRecords', value: function extractRawRecords(response) { var result = extractResult(response); if (result) { var data = result.data; if (data) { return data.map(function (element) { return extractRawRecord(element); }); } } return []; } /** * Extracts metadata for a completed statement. * @param {object} response the response. * @return {object} metadata as object. */ }, { key: 'extractStatementMetadata', value: function extractStatementMetadata(response) { var result = extractResult(response); if (result) { var stats = result.stats; if (stats) { var convertedStats = (0, _keys2.default)(stats).reduce(function (newStats, key) { if (key === 'contains_updates') { // skip because such key does not exist in bolt return newStats; } // fix key name for future parsing by StatementStatistics class var newKey = (key === 'relationship_deleted' ? 'relationships_deleted' : key).replace('_', '-'); newStats[newKey] = stats[key]; return newStats; }, {}); return { stats: convertedStats }; } } return {}; } }]); return HttpResponseConverter; }(); exports.default = HttpResponseConverter; function encodeQueryParameters(parameters) { if (parameters && (typeof parameters === 'undefined' ? 'undefined' : (0, _typeof3.default)(parameters)) === 'object') { return (0, _keys2.default)(parameters).reduce(function (result, key) { result[key] = encodeQueryParameter(parameters[key]); return result; }, {}); } return parameters; } function encodeQueryParameter(value) { if (value instanceof _graphTypes.Node) { throw new _error.Neo4jError('It is not allowed to pass nodes in query parameters', _error.PROTOCOL_ERROR); } else if (value instanceof _graphTypes.Relationship) { throw new _error.Neo4jError('It is not allowed to pass relationships in query parameters', _error.PROTOCOL_ERROR); } else if (value instanceof _graphTypes.Path) { throw new _error.Neo4jError('It is not allowed to pass paths in query parameters', _error.PROTOCOL_ERROR); } else if ((0, _spatialTypes.isPoint)(value)) { throw newUnsupportedParameterError('points'); } else if ((0, _temporalTypes.isDate)(value)) { throw newUnsupportedParameterError('dates'); } else if ((0, _temporalTypes.isDateTime)(value)) { throw newUnsupportedParameterError('date-time'); } else if ((0, _temporalTypes.isDuration)(value)) { throw newUnsupportedParameterError('durations'); } else if ((0, _temporalTypes.isLocalDateTime)(value)) { throw newUnsupportedParameterError('local date-time'); } else if ((0, _temporalTypes.isLocalTime)(value)) { throw newUnsupportedParameterError('local time'); } else if ((0, _temporalTypes.isTime)(value)) { throw newUnsupportedParameterError('time'); } else if ((0, _integer.isInt)(value)) { return value.toNumber(); } else if (Array.isArray(value)) { return value.map(function (element) { return encodeQueryParameter(element); }); } else if ((typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) === 'object') { return encodeQueryParameters(value); } else { return value; } } function newUnsupportedParameterError(name) { return new _error.Neo4jError('It is not allowed to pass ' + name + ' in query parameters when using HTTP endpoint. ' + ('Please consider using Cypher functions to create ' + name + ' so that query parameters are plain objects.'), _error.PROTOCOL_ERROR); } function extractResult(response) { var results = response.results; if (results) { var result = results[0]; if (result) { return result; } } return null; } function extractRawRecord(data) { var row = data.row; var nodesById = indexNodesById(data); var relationshipsById = indexRelationshipsById(data); if (row) { return row.map(function (ignore, index) { return extractRawRecordElement(index, data, nodesById, relationshipsById); }); } return []; } function indexNodesById(data) { var graph = data.graph; if (graph) { var nodes = graph.nodes; if (nodes) { return nodes.reduce(function (result, node) { var identity = convertNumber(node.id); var labels = node.labels; var properties = convertPrimitiveValue(node.properties); result[node.id] = new _graphTypes.Node(identity, labels, properties); return result; }, {}); } } return {}; } function indexRelationshipsById(data) { var graph = data.graph; if (graph) { var relationships = graph.relationships; if (relationships) { return relationships.reduce(function (result, relationship) { var identity = convertNumber(relationship.id); var startNode = convertNumber(relationship.startNode); var endNode = convertNumber(relationship.endNode); var type = relationship.type; var properties = convertPrimitiveValue(relationship.properties); result[relationship.id] = new _graphTypes.Relationship(identity, startNode, endNode, type, properties); return result; }, {}); } } return {}; } function extractRawRecordElement(index, data, nodesById, relationshipsById) { var element = data.row ? data.row[index] : null; var elementMetadata = data.meta ? data.meta[index] : null; if (elementMetadata) { // element is either a graph, spatial or temporal type return convertComplexValue(element, elementMetadata, nodesById, relationshipsById); } else { // element is a primitive, like number, string, array or object return convertPrimitiveValue(element); } } function convertComplexValue(element, elementMetadata, nodesById, relationshipsById) { if (isNodeMetadata(elementMetadata)) { return nodesById[elementMetadata.id]; } else if (isRelationshipMetadata(elementMetadata)) { return relationshipsById[elementMetadata.id]; } else if (isPathMetadata(elementMetadata)) { return convertPath(elementMetadata, nodesById, relationshipsById); } else if (isPointMetadata(elementMetadata)) { return convertPoint(element); } else { return element; } } function convertPath(metadata, nodesById, relationshipsById) { var startNode = null; var relationship = null; var pathSegments = []; for (var i = 0; i < metadata.length; i++) { var element = metadata[i]; var elementId = element.id; var elementType = element.type; var nodeExpected = startNode === null && relationship === null || startNode !== null && relationship !== null; if (nodeExpected && elementType !== 'node') { throw new _error.Neo4jError('Unable to parse path, node expected but got: ' + (0, _stringify2.default)(element) + ' in ' + (0, _stringify2.default)(metadata)); } if (!nodeExpected && elementType === 'node') { throw new _error.Neo4jError('Unable to parse path, relationship expected but got: ' + (0, _stringify2.default)(element) + ' in ' + (0, _stringify2.default)(metadata)); } if (nodeExpected) { var node = nodesById[elementId]; if (startNode === null) { startNode = node; } else if (startNode !== null && relationship !== null) { var pathSegment = new _graphTypes.PathSegment(startNode, relationship, node); pathSegments.push(pathSegment); startNode = node; relationship = null; } else { throw new _error.Neo4jError('Unable to parse path, illegal node configuration: ' + (0, _stringify2.default)(metadata)); } } else { if (relationship === null) { relationship = relationshipsById[elementId]; } else { throw new _error.Neo4jError('Unable to parse path, illegal relationship configuration: ' + (0, _stringify2.default)(metadata)); } } } var lastPathSegment = pathSegments[pathSegments.length - 1]; if (lastPathSegment && lastPathSegment.end !== startNode || relationship !== null) { throw new _error.Neo4jError('Unable to parse path: ' + (0, _stringify2.default)(metadata)); } return createPath(pathSegments); } function createPath(pathSegments) { var pathStartNode = pathSegments[0].start; var pathEndNode = pathSegments[pathSegments.length - 1].end; return new _graphTypes.Path(pathStartNode, pathEndNode, pathSegments); } function convertPoint(element) { var type = element.type; if (type !== 'Point') { throw new _error.Neo4jError('Unexpected Point type received: ' + (0, _stringify2.default)(element)); } var coordinates = element.coordinates; if (!Array.isArray(coordinates) && (coordinates.length !== 2 || coordinates.length !== 3)) { throw new _error.Neo4jError('Unexpected Point coordinates received: ' + (0, _stringify2.default)(element)); } var srid = convertCrsToId(element); return new (Function.prototype.bind.apply(_spatialTypes.Point, [null].concat([srid], (0, _toConsumableArray3.default)(coordinates))))(); } function convertCrsToId(element) { var crs = element.crs; if (!crs || !crs.name) { throw new _error.Neo4jError('Unexpected Point crs received: ' + (0, _stringify2.default)(element)); } var name = crs.name.toLowerCase(); if (name === 'wgs-84') { return 4326; } else if (name === 'wgs-84-3d') { return 4979; } else if (name === 'cartesian') { return 7203; } else if (name === 'cartesian-3d') { return 9157; } else { throw new _error.Neo4jError('Unexpected Point crs received: ' + (0, _stringify2.default)(element)); } } function convertPrimitiveValue(element) { if (element == null || element === undefined) { return null; } else if (typeof element === 'number') { return convertNumber(element); } else if (Array.isArray(element)) { return element.map(function (element) { return convertPrimitiveValue(element); }); } else if ((typeof element === 'undefined' ? 'undefined' : (0, _typeof3.default)(element)) === 'object') { return (0, _keys2.default)(element).reduce(function (result, key) { result[key] = convertPrimitiveValue(element[key]); return result; }, {}); } else { return element; } } function convertNumber(value) { return typeof value === 'number' ? value : Number(value); } function isNodeMetadata(metadata) { return isMetadataForType('node', metadata); } function isRelationshipMetadata(metadata) { return isMetadataForType('relationship', metadata); } function isPointMetadata(metadata) { return isMetadataForType('point', metadata); } function isMetadataForType(name, metadata) { return !Array.isArray(metadata) && (typeof metadata === 'undefined' ? 'undefined' : (0, _typeof3.default)(metadata)) === 'object' && metadata.type === name; } function isPathMetadata(metadata) { return Array.isArray(metadata); }