UNPKG

gremlin

Version:

JavaScript Gremlin Language Variant

567 lines (500 loc) 14.1 kB
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ /** * @author Jorge Bay Gondra */ 'use strict'; const t = require('../../process/traversal'); const ts = require('../../process/traversal-strategy'); const Bytecode = require('../../process/bytecode'); const g = require('../graph'); const utils = require('../../utils'); const valueKey = '@value'; const typeKey = '@type'; /** * @abstract */ class TypeSerializer { serialize() { throw new Error('serialize() method not implemented for ' + this.constructor.name); } deserialize() { throw new Error('deserialize() method not implemented for ' + this.constructor.name); } canBeUsedFor() { throw new Error('canBeUsedFor() method not implemented for ' + this.constructor.name); } } class NumberSerializer extends TypeSerializer { serialize(item) { if (isNaN(item)) { return { [typeKey]: 'g:Double', [valueKey]: 'NaN', }; } else if (item === Number.POSITIVE_INFINITY) { return { [typeKey]: 'g:Double', [valueKey]: 'Infinity', }; } else if (item === Number.NEGATIVE_INFINITY) { return { [typeKey]: 'g:Double', [valueKey]: '-Infinity', }; } return item; } deserialize(obj) { const val = obj[valueKey]; if (val === 'NaN') { return NaN; } else if (val === 'Infinity') { return Number.POSITIVE_INFINITY; } else if (val === '-Infinity') { return Number.NEGATIVE_INFINITY; } return parseFloat(val); } canBeUsedFor(value) { return typeof value === 'number'; } } class DateSerializer extends TypeSerializer { // only deserialize g:Date objects deserialize(obj) { return new Date(obj[valueKey]); } } class OffsetDateTimeSerializer extends TypeSerializer { // Date objects are serialized as OffsetDateTime by default serialize(item) { return { [typeKey]: 'gx:OffsetDateTime', [valueKey]: item.toISOString(), }; } deserialize(obj) { return new Date(obj[valueKey]); } canBeUsedFor(value) { return value instanceof Date; } } class ClassSerializer extends TypeSerializer { serialize(item) { return { [typeKey]: 'g:Class', [valueKey]: new item().fqcn, }; } canBeUsedFor(value) { return ( typeof value === 'function' && !!value.prototype && !!value.prototype.constructor.name && new value() instanceof ts.TraversalStrategy ); } } class LongSerializer extends TypeSerializer { serialize(item) { return { [typeKey]: 'g:Int64', [valueKey]: item.value, }; } canBeUsedFor(value) { return value instanceof utils.Long; } } class BytecodeSerializer extends TypeSerializer { serialize(item) { let bytecode = item; if (item instanceof t.Traversal) { bytecode = item.getBytecode(); } const result = {}; result[typeKey] = 'g:Bytecode'; const resultValue = (result[valueKey] = {}); const sources = this._serializeInstructions(bytecode.sourceInstructions); if (sources) { resultValue['source'] = sources; } const steps = this._serializeInstructions(bytecode.stepInstructions); if (steps) { resultValue['step'] = steps; } return result; } _serializeInstructions(instructions) { if (instructions.length === 0) { return null; } const result = new Array(instructions.length); result[0] = instructions[0]; for (let i = 0; i < instructions.length; i++) { result[i] = instructions[i].map((item) => this.writer.adaptObject(item)); } return result; } canBeUsedFor(value) { return value instanceof Bytecode || value instanceof t.Traversal; } } class PSerializer extends TypeSerializer { /** @param {P} item */ serialize(item) { const result = {}; result[typeKey] = 'g:P'; const resultValue = (result[valueKey] = { predicate: item.operator, }); if (item.other === undefined || item.other === null) { resultValue['value'] = this.writer.adaptObject(item.value); } else { resultValue['value'] = [this.writer.adaptObject(item.value), this.writer.adaptObject(item.other)]; } return result; } canBeUsedFor(value) { return value instanceof t.P; } } class TextPSerializer extends TypeSerializer { /** @param {TextP} item */ serialize(item) { const result = {}; result[typeKey] = 'g:TextP'; const resultValue = (result[valueKey] = { predicate: item.operator, }); if (item.other === undefined || item.other === null) { resultValue['value'] = this.writer.adaptObject(item.value); } else { resultValue['value'] = [this.writer.adaptObject(item.value), this.writer.adaptObject(item.other)]; } return result; } canBeUsedFor(value) { return value instanceof t.TextP; } } class LambdaSerializer extends TypeSerializer { /** @param {Function} item */ serialize(item) { const lambdaDef = item(); // check if the language is specified otherwise assume gremlin-groovy. const returnIsString = typeof lambdaDef === 'string'; const script = returnIsString ? lambdaDef : lambdaDef[0]; const lang = returnIsString ? 'gremlin-groovy' : lambdaDef[1]; // detect argument count const argCount = lang === 'gremlin-groovy' && script.includes('->') ? script.substring(0, script.indexOf('->')).includes(',') ? 2 : 1 : -1; return { [typeKey]: 'g:Lambda', [valueKey]: { arguments: argCount, language: lang, script: script, }, }; } canBeUsedFor(value) { return typeof value === 'function'; } } class EnumSerializer extends TypeSerializer { /** @param {EnumValue} item */ serialize(item) { return { [typeKey]: 'g:' + item.typeName, [valueKey]: item.elementName, }; } canBeUsedFor(value) { return value && value.typeName && value instanceof t.EnumValue; } } class TraverserSerializer extends TypeSerializer { /** @param {Traverser} item */ serialize(item) { return { [typeKey]: 'g:Traverser', [valueKey]: { value: this.writer.adaptObject(item.object), bulk: this.writer.adaptObject(item.bulk), }, }; } deserialize(obj) { const value = obj[valueKey]; return new t.Traverser(this.reader.read(value['value']), this.reader.read(value['bulk'])); } canBeUsedFor(value) { return value instanceof t.Traverser; } } class TraversalStrategySerializer extends TypeSerializer { /** @param {TraversalStrategy} item */ serialize(item) { const conf = {}; for (const k in item.configuration) { if (item.configuration.hasOwnProperty(k)) { conf[k] = this.writer.adaptObject(item.configuration[k]); } } return { [typeKey]: 'g:' + item.constructor.name, [valueKey]: { ['fqcn']: item.fqcn, ['conf']: conf }, }; } canBeUsedFor(value) { return value instanceof ts.TraversalStrategy; } } /** * Helper function to deserialize properties into arrays of VertexProperty or Property according to graphSONV3. */ function deserializeProperties(value, reader) { let properties = []; if ('properties' in value) { const props = reader.read(value['properties']); if (props !== null && props !== undefined) { properties = Object.entries(props).flatMap(([key, values]) => { if (Array.isArray(values) && values.length > 0 && values[0] instanceof g.VertexProperty) { return values; // Flatten VertexProperty arrays } return values instanceof g.Property ? values : new g.Property(key, values); // create Property object when needed }); } } return properties; } class VertexSerializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; return new g.Vertex(this.reader.read(value['id']), value['label'], deserializeProperties(value, this.reader)); } /** @param {Vertex} item */ serialize(item) { return { [typeKey]: 'g:Vertex', [valueKey]: { id: this.writer.adaptObject(item.id), label: item.label, }, }; } canBeUsedFor(value) { return value instanceof g.Vertex; } } class VertexPropertySerializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; return new g.VertexProperty( this.reader.read(value['id']), value['label'], this.reader.read(value['value']), deserializeProperties(value, this.reader), ); } } class PropertySerializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; return new g.Property(value['key'], this.reader.read(value['value'])); } } class EdgeSerializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; return new g.Edge( this.reader.read(value['id']), new g.Vertex(this.reader.read(value['outV']), this.reader.read(value['outVLabel'])), value['label'], new g.Vertex(this.reader.read(value['inV']), this.reader.read(value['inVLabel'])), deserializeProperties(value, this.reader), ); } /** @param {Edge} item */ serialize(item) { return { [typeKey]: 'g:Edge', [valueKey]: { id: this.writer.adaptObject(item.id), label: item.label, outV: this.writer.adaptObject(item.outV.id), outVLabel: item.outV.label, inV: this.writer.adaptObject(item.inV.id), inVLabel: item.inV.label, }, }; } canBeUsedFor(value) { return value instanceof g.Edge; } } class PathSerializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; const objects = value['objects'].map((o) => this.reader.read(o)); return new g.Path(this.reader.read(value['labels']), objects); } } class Path3Serializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; return new g.Path(this.reader.read(value['labels']), this.reader.read(value['objects'])); } } class TSerializer extends TypeSerializer { deserialize(obj) { return t.t[obj[valueKey]]; } } class DirectionSerializer extends TypeSerializer { deserialize(obj) { return t.direction[obj[valueKey].toLowerCase()]; } } class ArraySerializer extends TypeSerializer { constructor(typeKey) { super(); this.typeKey = typeKey; } deserialize(obj) { const value = obj[valueKey]; if (!Array.isArray(value)) { throw new Error('Expected Array, obtained: ' + value); } return value.map((x) => this.reader.read(x)); } /** @param {Array} item */ serialize(item) { return { [typeKey]: this.typeKey, [valueKey]: item.map((x) => this.writer.adaptObject(x)), }; } canBeUsedFor(value) { return Array.isArray(value); } } class BulkSetSerializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; if (!Array.isArray(value)) { throw new Error('Expected Array, obtained: ' + value); } // coerce the BulkSet to List. if the bulk exceeds the int space then we can't coerce to List anyway, // so this query will be trouble. we'd need a legit BulkSet implementation here in js. this current // implementation is here to replicate the previous functionality that existed on the server side in // previous versions. let result = []; for (let ix = 0, iy = value.length; ix < iy; ix += 2) { const pair = value.slice(ix, ix + 2); result = result.concat(Array(this.reader.read(pair[1])).fill(this.reader.read(pair[0]))); } return result; } } class MapSerializer extends TypeSerializer { deserialize(obj) { const value = obj[valueKey]; if (!Array.isArray(value)) { throw new Error('Expected Array, obtained: ' + value); } const result = new Map(); for (let i = 0; i < value.length; i += 2) { result.set(this.reader.read(value[i]), this.reader.read(value[i + 1])); } return result; } /** @param {Map} map */ serialize(map) { const arr = []; map.forEach((v, k) => { arr.push(this.writer.adaptObject(k)); arr.push(this.writer.adaptObject(v)); }); return { [typeKey]: 'g:Map', [valueKey]: arr, }; } canBeUsedFor(value) { return value instanceof Map; } } class ListSerializer extends ArraySerializer { constructor() { super('g:List'); } } class SetSerializer extends ArraySerializer { constructor() { super('g:Set'); } deserialize(obj) { return new Set(super.deserialize(obj)); } serialize(item) { return { [typeKey]: this.typeKey, [valueKey]: [...item].map((x) => this.writer.adaptObject(x)), }; } canBeUsedFor(value) { return value instanceof Set; } } module.exports = { BulkSetSerializer, BytecodeSerializer, ClassSerializer, DateSerializer, OffsetDateTimeSerializer, DirectionSerializer, EdgeSerializer, EnumSerializer, LambdaSerializer, ListSerializer, LongSerializer, MapSerializer, NumberSerializer, Path3Serializer, PathSerializer, PropertySerializer, PSerializer, TextPSerializer, SetSerializer, TSerializer, TraverserSerializer, TraversalStrategySerializer, typeKey, valueKey, VertexPropertySerializer, VertexSerializer, };