Skip to content

Commit

Permalink
Add create method to types (elastic#621)
Browse files Browse the repository at this point in the history
* feat: add serialize/deserialize capabilities

* fix: send types to serialize provider

* feat: add create method to types and pass types in cast and interpret

* fix: re-add type validation

* fix: no need to import datatable in pointseries
  • Loading branch information
lukasolson authored May 29, 2018
1 parent 4cea78b commit 7fcaaa6
Show file tree
Hide file tree
Showing 15 changed files with 94 additions and 58 deletions.
2 changes: 2 additions & 0 deletions common/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { table } from './table';
import { tail } from './tail';
import { timefilter } from './timefilter';
import { timefilterControl } from './timefilterControl';
import { to } from './to';
import { switchFn } from './switch';
import { caseFn } from './case';

Expand Down Expand Up @@ -96,6 +97,7 @@ export const commonFunctions = [
tail,
timefilter,
timefilterControl,
to,
switchFn,
caseFn,
];
42 changes: 18 additions & 24 deletions common/functions/to.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
/*
WARNING: This is imported into the public and server function registries separately because
it needs access to types and the registry of types is, obviously, different between the environments
*/

import { castProvider } from '../interpreter/cast';

export function toProvider(types) {
return () => ({
name: 'to',
aliases: [],
help: 'Explicitly cast from one type to another.',
context: {},
args: {
_: {
types: ['string'],
help: 'A known type',
aliases: ['type'],
multi: true,
},
},
fn: (context, args) => {
if (!args._) throw new Error('Must specify a casting type');
return castProvider(types.toJS())(context, args._);
export const to = () => ({
name: 'to',
aliases: [],
help: 'Explicitly cast from one type to another.',
context: {},
args: {
_: {
types: ['string'],
help: 'A known type',
aliases: ['type'],
multi: true,
},
});
}
},
fn: (context, args, { types }) => {
if (!args._) throw new Error('Must specify a casting type');

return castProvider(types)(context, args._);
},
});
4 changes: 2 additions & 2 deletions common/interpreter/cast.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export function castProvider(types) {
for (let i = 0; i < toTypeNames.length; i++) {
// First check if the current type can cast to this type
if (fromTypeDef && fromTypeDef.castsTo(toTypeNames[i])) {
return fromTypeDef.to(node, toTypeNames[i]);
return fromTypeDef.to(node, toTypeNames[i], types);
}

// If that isn't possible, check if this type can cast from the current type
const toTypeDef = types[toTypeNames[i]];
if (toTypeDef && toTypeDef.castsFrom(fromTypeName)) {
return toTypeDef.from(node, fromTypeName);
return toTypeDef.from(node, types);
}
}

Expand Down
5 changes: 3 additions & 2 deletions common/interpreter/interpret.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ const createError = (err, { name, context, args }) => ({
});

export function interpretProvider(config) {
const cast = castProvider(config.types);
const { functions, onFunctionNotFound, handlers } = config;
const { functions, onFunctionNotFound, types } = config;
const handlers = { ...config.handlers, types };
const cast = castProvider(types);

return interpret;

Expand Down
7 changes: 5 additions & 2 deletions common/interpreter/socket_interpret.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { has } from 'lodash';
import uuid from 'uuid/v4';
import { serializeProvider } from '../lib/serialize';
import { interpretProvider } from './interpret';

/*
Expand Down Expand Up @@ -41,22 +42,24 @@ export function socketInterpreterProvider({
const id = uuid();

return new Promise((resolve, reject) => {
const { serialize, deserialize } = serializeProvider(types);

const listener = resp => {
if (resp.error) {
// cast error strings back into error instances
const err = resp.error instanceof Error ? resp.error : new Error(resp.error);
if (resp.stack) err.stack = resp.stack;
reject(err);
} else {
resolve(resp.value);
resolve(deserialize(resp.value));
}
};

socket.once(`resp:${id}`, listener);

// Go run the remaining AST and context somewhere else, meaning either the browser or the server, depending on
// where this file was loaded
socket.emit('run', { ast, context, id });
socket.emit('run', { ast, context: serialize(context), id });
});
});
},
Expand Down
18 changes: 18 additions & 0 deletions common/lib/serialize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { get, identity } from 'lodash';
import { getType } from '../lib/get_type';

export function serializeProvider(types) {
return {
serialize: provider('serialize'),
deserialize: provider('deserialize'),
};

function provider(key) {
return context => {
const type = getType(context);
const typeDef = types[type];
const fn = get(typeDef, key) || identity;
return fn(context);
};
}
}
15 changes: 11 additions & 4 deletions common/lib/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,34 @@ export function Type(config) {
// Optional type validation, useful for checking function output
this.validate = config.validate || function validate() {};

// Optional
this.create = config.create;

// Optional serialization (used when passing context around client/server)
this.serialize = config.serialize;
this.deserialize = config.deserialize;

const getToFn = type => get(config, ['to', type]) || get(config, ['to', '*']);
const getFromFn = type => get(config, ['from', type]) || get(config, ['from', '*']);

this.castsTo = type => typeof getToFn(type) === 'function';
this.castsFrom = type => typeof getFromFn(type) === 'function';

this.to = (node, toTypeName) => {
this.to = (node, toTypeName, types) => {
const typeName = getType(node);
if (typeName !== this.name) {
throw new Error(`Can not cast object of type '${typeName}' using '${this.name}'`);
} else if (!this.castsTo(toTypeName)) {
throw new Error(`Can not cast '${typeName}' to '${toTypeName}'`);
}
return getToFn(toTypeName)(node);
return getToFn(toTypeName)(node, types);
};

this.from = node => {
this.from = (node, types) => {
const typeName = getType(node);
if (!this.castsFrom(typeName)) {
throw new Error(`Can not cast '${this.name}' from ${typeName}`);
}
return getFromFn(typeName)(node);
return getFromFn(typeName)(node, types);
};
}
20 changes: 19 additions & 1 deletion common/types/datatable.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { map } from 'lodash';
import { map, zipObject } from 'lodash';

export const datatable = () => ({
name: 'datatable',
Expand All @@ -12,6 +12,24 @@ export const datatable = () => ({
throw new Error('datatable must have a rows array, even if it is empty');
}
},
serialize: datatable => {
const { columns, rows } = datatable;
return {
...datatable,
rows: rows.map(row => {
return columns.map(column => row[column.name]);
}),
};
},
deserialize: datatable => {
const { columns, rows } = datatable;
return {
...datatable,
rows: rows.map(row => {
return zipObject(map(columns, 'name'), row);
}),
};
},
from: {
null: () => {
return {
Expand Down
9 changes: 3 additions & 6 deletions common/types/pointseries.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { datatable } from './datatable';

export const pointseries = () => ({
name: 'pointseries',
from: {
Expand All @@ -12,13 +10,12 @@ export const pointseries = () => ({
},
},
to: {
render: pointseries => {
render: (pointseries, types) => {
const datatable = types.datatable.from(pointseries, types);
return {
type: 'render',
as: 'table',
value: {
datatable: datatable().from.pointseries(pointseries),
},
value: { datatable },
};
},
},
Expand Down
3 changes: 1 addition & 2 deletions public/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { asset } from './asset';
import { filters } from './filters';
import { geoip } from './geoip';
import { location } from './location';
import { toFn } from './to';
import { urlparam } from './urlparam';

export const clientFunctions = [geoip, location, filters, asset, toFn, urlparam];
export const clientFunctions = [geoip, location, filters, asset, urlparam];
4 changes: 0 additions & 4 deletions public/functions/to.js

This file was deleted.

7 changes: 5 additions & 2 deletions public/lib/interpreter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { socketInterpreterProvider } from '../../common/interpreter/socket_interpret';
import { serializeProvider } from '../../common/lib/serialize';
import { socket } from '../socket';
import { typesRegistry } from '../../common/lib/types_registry';
import { createHandlers } from './create_handlers';
Expand All @@ -24,7 +25,9 @@ export function interpretAst(ast, context) {
}

socket.on('run', ({ ast, context, id }) => {
interpretAst(ast, context).then(value => {
socket.emit(`resp:${id}`, { value });
const types = typesRegistry.toJS();
const { serialize, deserialize } = serializeProvider(types);
interpretAst(ast, deserialize(context)).then(value => {
socket.emit(`resp:${id}`, { value: serialize(value) });
});
});
3 changes: 1 addition & 2 deletions server/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ import { escount } from './escount';
import { esdocs } from './esdocs';
import { pointseries } from './pointseries';
import { timelion } from './timelion';
import { toFn } from './to';

export const serverFunctions = [esdocs, escount, demodata, demoprices, pointseries, timelion, toFn];
export const serverFunctions = [esdocs, escount, demodata, demoprices, pointseries, timelion];
4 changes: 0 additions & 4 deletions server/functions/to.js

This file was deleted.

9 changes: 6 additions & 3 deletions server/routes/socket.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import socket from 'socket.io';
import { createHandlers } from '../lib/create_handlers';
import { socketInterpreterProvider } from '../../common/interpreter/socket_interpret';
import { serializeProvider } from '../../common/lib/serialize';
import { functionsRegistry } from '../../common/lib/functions_registry';
import { typesRegistry } from '../../common/lib/types_registry';
import { getAuthHeader } from './get_auth/get_auth_header';
Expand Down Expand Up @@ -34,17 +35,19 @@ export function socketApi(server) {
Promise.all([getClientFunctions, authHeader]).then(([clientFunctions, authHeader]) => {
if (server.plugins.security) request.headers.authorization = authHeader;

const types = typesRegistry.toJS();
const interpret = socketInterpreterProvider({
types: typesRegistry.toJS(),
types,
functions: functionsRegistry.toJS(),
handlers: createHandlers(request, server),
referableFunctions: clientFunctions,
socket: socket,
});

return interpret(ast, context)
const { serialize, deserialize } = serializeProvider(types);
return interpret(ast, deserialize(context))
.then(value => {
socket.emit(`resp:${id}`, { value });
socket.emit(`resp:${id}`, { value: serialize(value) });
})
.catch(e => {
socket.emit(`resp:${id}`, {
Expand Down

0 comments on commit 7fcaaa6

Please sign in to comment.