blob: 96a8475c6053d4ec7faef0a73143fbef54674f1a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/**
* @fileoverview Generator of typeless service interface from JavaScript object.
* @private
*/
module.exports = ReflectInterface;
var ArgInspector = require('../lib/arg-inspector');
var isPublicMethod = require('../lib/service-reflection').isPublicMethod;
var vdlUtil = require('./util');
var format = require('format');
/**
* Create an interface for a service by inspecting the service object.
* @private
* @param {Service} service The service.
* @constructor
*/
function ReflectInterface(service) {
if (!(this instanceof ReflectInterface)) {
return new ReflectInterface(service);
}
var ifc = this;
ifc.methods = [];
// NOTE: service.hasOwnProperty(key) is intentionally omitted so that
// methods defined on the prototype chain are mapped into the interface
// correctly. This supports services defined using constructors:
//
// function Service() {
//
// }
//
// Service.prototype.method = function() {
//
// }
//
// TODO(jasoncampbell): At some point we should try to avoid inherited
// properties so we don't unintentionally publish a service's internal
// implementation where inheritance has been used (event emitters etc.).
//
// SEE: http://git.io/mi6jDg
// SEE: veyron/release-issues#657
for (var key in service) { // jshint ignore:line
if (!isPublicMethod(key, service)) {
continue;
}
var method = service[key];
var methodSignature = {
name: vdlUtil.capitalize(key),
streaming: false
};
var argInspector = new ArgInspector(method);
// Check whether the number of args reported by javascript (method.length)
// and the number of args retrieved from fn.toString() are the same.
// This usually differs if the method is a native method.
if (argInspector.names.length !== method.length) {
throw new Error('Function "' + key + '" can not be inspected. ' +
'This is usually because it is a native method or bind is used.');
}
var message;
if (!argInspector.hasContext()) {
message = format('Service method "%s" is missing the required ' +
'`context` object as the first argument in its definition. ' +
'Args were: %s',
key, argInspector.names);
throw new Error(message);
}
if (!argInspector.hasCall()) {
message = format('Service method "%s" is missing the required ' +
'`serverCall` object as the second argument in its definition. ' +
'Args were: %s',
key, argInspector.names);
var e = new Error(message);
throw e;
}
methodSignature.inArgs = argInspector.filteredNames.map(function(name) {
return { name: name };
});
methodSignature.streaming = argInspector.contains('$stream');
// Add this method's signature to its service interface.
ifc.methods.push(methodSignature);
}
// Sort all the method signatures by method name.
ifc.methods.sort(function(methodSig, methodSig2) {
if (methodSig.name === methodSig2.name) {
return 0;
}
return methodSig.name < methodSig2.name ? -1 : 1;
});
}