Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

Commit

Permalink
Service Framework 3.0 Diff #2 - TypeRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
rameshvarun committed Aug 28, 2015
1 parent 9e1b792 commit e3c43a6
Show file tree
Hide file tree
Showing 5 changed files with 388 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/nuclide/external-interfaces/1.0/jasmine.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type JasmineMatcher = {
toBeCloseTo(expected: number, precision: number): boolean;
toBeDefined(): boolean;
toBeFalsy(): boolean;
toBeTruthy(): boolean;
toBeGreaterThan(expected: number): boolean;
toBeLessThan(expected: number): boolean;
toBeNull(): boolean;
Expand Down
2 changes: 2 additions & 0 deletions pkg/nuclide/server/.flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
[ignore]

[libs]
./node_modules/nuclide-external-interfaces/1.0/jasmine.js
./node_modules/nuclide-service-parser/lib/types.js

[options]

Expand Down
2 changes: 2 additions & 0 deletions pkg/nuclide/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"nuclide-path-search": "0.0.0",
"nuclide-remote-search": "0.0.0",
"nuclide-remote-uri": "0.0.0",
"nuclide-service-parser": "0.0.0",
"nuclide-service-transformer": "0.0.0",
"nuclide-source-control-helpers": "0.0.0",
"nuclide-version": "0.0.0",
Expand All @@ -43,6 +44,7 @@
"yargs": "3.1.0"
},
"devDependencies": {
"nuclide-external-interfaces": "0.0.0",
"nuclide-jasmine": "0.0.0",
"rimraf": "2.2.8"
},
Expand Down
226 changes: 226 additions & 0 deletions pkg/nuclide/service-parser/lib/TypeRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
'use babel';
/* @flow */

/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/

import assert from 'assert';
import vm from 'vm';

import type {Type, NullableType, ArrayType, ObjectType} from './types';

/*
* This type represents a Transfomer function, which takes in a value, and either serializes
* or deserializes it. Transformer's are added to a registry and indexed by the name of
* the type they handle (eg: 'Date'). The second argument is the actual type object that represent
* the value. Parameterized types like Array, or Object can use this to recursively call other
* transformers.
*/
export type Transfomer = (value: any, type: Type) => Promise<any>;

/*
* The TypeRegistry is a centralized place to register functions that serialize and deserialize
* types. This allows for types defined in one service to include types from another service in
* another file. It also allows the ability to add new primitives, ranging from Buffer to NuclideUri
* that are not handled at the transport layer. The key concept is that marshalling functions can
* be recursive, calling other marshalling functions, ending at the primitives.
*/
export default class TypeRegistry {
/** Store marhsallers and and unmarshallers, index by the name of the type. */
_marshallers: Map<string, Transfomer>;
_unmarshallers: Map<string, Transfomer>;

constructor() {
this._marshallers = new Map();
this._unmarshallers = new Map();

this._registerPrimitives();
this._registerSpecialTypes();
this._registerContainers();

// Register NullableType and NamedType
this.registerType('nullable', async (value: any, type: NullableType) => {
if (value === null || value === undefined) {
return null;
}
return await this.marshal(value, type.type);
}, async (value: any, type: NullableType) => {
if (value === null || value === undefined) {
return null;
}
return await this.unmarshal(value, type.type);
});

this.registerType('named', async (value: any, type: NamedType) => {
return await this.marshal(value, { kind: type.name });
}, async (value: any, type: NullableType) => {
return await this.unmarshal(value, { kind: type.name });
});
}

/**
* Register a type by providing both a marshaller and an unmarshaller. The marshaller
* will be called to transform the type before sending it out onto the network, while the
* unmarshaller will be called on values incoming from the network.
* @param typeName - The string name of the type that the provided marshaller / unmarshaller convert.
* @param marshaller - Serialize the type.
* @param unmarshaller - Deserialize the type.
*/
registerType(typeName: string, marshaller: Transformer, unmarshaller: Transformer): void {
if (this._marshallers.has(typeName) || this._unmarshallers.has(typeName)) {
throw new Error(`A type by the name ${typeName} has already been registered.`);
}
this._marshallers.set(typeName, marshaller);
this._unmarshallers.set(typeName, unmarshaller);
}

/**
* Helper function for registering the marashaller/unmarshaller for a type alias.
* @param name - The name of the alias type.
* @param type - The type the the alias represents.
*/
registerAlias(name: string, type: Type): void {
this.registerType(name, value => this.marshal(value, type),
value => this.unmarshal(value, type));
}

/**
* Marshal an object using the appropriate marshal function.
* @param value - The value to be marshalled.
* @param type - The type object (used to find the appropriate function).
*/
marshal(value: any, type: Type): Promise<any> {
return this._marshallers.get(type.kind)(value, type);
}

/**
* Unmarshal and object using the appropriate unmarshal function.
* @param value - The value to be marshalled.
* @param type - The type object (used to find the appropriate function).
*/
unmarshal(value: any, type: Type): Promise<any> {
return this._unmarshallers.get(type.kind)(value, type);
}

_registerPrimitives(): void {
// Since string, number, and boolean are JSON primitives,
// they require no marshalling. Instead, simply create wrapped transformers
// that assert the type of their argument.
var stringTransformer = async arg => {
// Unbox argument.
arg = (arg instanceof String) ? arg.valueOf() : arg;
assert(typeof arg === 'string', 'Expected a string argument');
return arg;
};
var numberTransformer = async arg => {
// Unbox argument.
if (arg instanceof Number) {
arg = arg.valueOf();
}
assert(typeof arg === 'number', 'Expected a number argument');
return arg;
};
var booleanTransformer = async arg => {
// Unbox argument
if (arg instanceof Boolean) {
arg = arg.valueOf();
}
assert(typeof arg === 'boolean', 'Expected a boolean argument');
return arg;
};

// Register these transformers
this.registerType('string', stringTransformer, stringTransformer);
this.registerType('number', numberTransformer, numberTransformer);
this.registerType('boolean', booleanTransformer, booleanTransformer);
}

_registerSpecialTypes(): void {
// Serialize / Deserialize Javascript Date objects
this.registerType('Date', async date => {
assert(date instanceof Date, 'Expected date argument.');
return date.toJSON();
}, async dateStr => {
// Unbox argument.
dateStr = (dateStr instanceof String) ? dateStr.valueOf() : dateStr;

assert(typeof dateStr === 'string', 'Expeceted a string argument.');
return new Date(dateStr);
});

// Serialize / Deserialize RegExp objects
this.registerType('RegExp', async regexp => {
assert(regexp instanceof RegExp, 'Expected a RegExp object as an argument');
return regexp.toString();
}, async regStr => {
// Unbox argument.
regStr = (regStr instanceof String) ? regStr.valueOf() : regStr;

assert(typeof regStr === 'string', 'Expected a string argument.');
return vm.runInThisContext(regStr);
});

// Serialize / Deserialize Buffer objects through Base64 strings
this.registerType('Buffer', async buffer => {
assert(buffer instanceof Buffer, 'Expected a buffer argument.');
return buffer.toString('base64');
}, async base64string => {
// Unbox argument.
base64string = (base64string instanceof String) ? base64string.valueOf() : base64string;

assert(typeof base64string === 'string', `Expected a base64 string. Not ${typeof base64string}`);
return new Buffer(base64string, 'base64');
});
}

_registerContainers(): void {
// Serialize / Deserialize Arrays
this.registerType('array', async (value: any, type: ArrayType) => {
assert(value instanceof Array, 'Expected an object of type Array.');
return await* value.map(elem => this.marshal(elem, type.type));
}, async (value: any, type: ArrayType) => {
assert(value instanceof Array, 'Expected an object of type Array.');
return await* value.map(elem => this.unmarshal(elem, type.type));
});

// Serialize and Deserialize Objects
this.registerType('object', async (obj: any, type: ObjectType) => {
assert(typeof obj === 'object', 'Expected an argument of type object.');
var newObj = {}; // Create a new object so we don't mutate the original one.
await* type.fields.map(async prop => {
// Check if the source object has this key.
if (obj.hasOwnProperty(prop.name)) {
newObj[prop.name] = await this.marshal(obj[prop.name], prop.type);
} else {
// If the property is optional, it's okay for it to be missing.
if (!prop.optional) {
throw new Error(`Source object is missing property ${prop.name}.`);
}
}
});
return newObj;
}, async (obj: any, type: ObjectType) => {
assert(typeof obj === 'object', 'Expected an argument of type object.');
var newObj = {}; // Create a new object so we don't mutate the original one.
await* type.fields.map(async prop => {
// Check if the source object has this key.
if (obj.hasOwnProperty(prop.name)) {
newObj[prop.name] = await this.unmarshal(obj[prop.name], prop.type);
} else {
// If the property is optional, it's okay for it to be missing.
if (!prop.optional) {
throw new Error(`Source object is missing property ${prop.name}.`);
}
}
});
return newObj;
});

// TODO: Serialize Map and Set.
}
}
Loading

0 comments on commit e3c43a6

Please sign in to comment.