mirror of https://github.com/grpc/grpc-node.git
Merge pull request #190 from murgatroid99/proto_package_impl
Implement new design of protobuf loader package
This commit is contained in:
commit
5674367e36
13
gulpfile.ts
13
gulpfile.ts
|
@ -54,21 +54,22 @@ function loadGulpTasksWithPrefix(path: string, prefix: string) {
|
|||
['./packages/grpc-native/gulpfile', 'native'],
|
||||
['./packages/grpc-native-core/gulpfile', 'native.core'],
|
||||
['./packages/grpc-surface/gulpfile', 'surface'],
|
||||
['./test/gulpfile', 'internal.test']
|
||||
['./packages/grpc-protobufjs/gulpfile', 'protobuf'],
|
||||
['./test/gulpfile', 'internal.test'],
|
||||
].forEach((args) => loadGulpTasksWithPrefix(args[0], args[1]));
|
||||
|
||||
const root = __dirname;
|
||||
|
||||
gulp.task('install.all', 'Install dependencies for all subdirectory packages',
|
||||
['js.install', 'js.core.install', 'native.core.install', 'surface.install', 'health-check.install', 'internal.test.install']);
|
||||
['js.install', 'js.core.install', 'native.core.install', 'surface.install', 'health-check.install', 'protobuf.install', 'internal.test.install']);
|
||||
|
||||
gulp.task('install.all.windows', 'Install dependencies for all subdirectory packages for MS Windows',
|
||||
['js.core.install', 'native.core.install.windows', 'surface.install', 'health-check.install', 'internal.test.install']);
|
||||
['js.core.install', 'native.core.install.windows', 'surface.install', 'health-check.install', 'protobuf.install', 'internal.test.install']);
|
||||
|
||||
gulp.task('lint', 'Emit linting errors in source and test files',
|
||||
['js.core.lint', 'native.core.lint']);
|
||||
|
||||
gulp.task('build', 'Build packages', ['js.compile', 'js.core.compile', 'native.core.build']);
|
||||
gulp.task('build', 'Build packages', ['js.compile', 'js.core.compile', 'native.core.build', 'protobuf.compile']);
|
||||
|
||||
gulp.task('link.core', 'Add links to core packages without rebuilding',
|
||||
['js.link.add', 'native.link.add']);
|
||||
|
@ -91,11 +92,11 @@ gulp.task('setup.windows', 'One-time setup for a clean repository for MS Windows
|
|||
runSequence('install.all.windows', 'link', callback);
|
||||
});
|
||||
|
||||
gulp.task('clean', 'Delete generated files', ['js.core.clean', 'native.core.clean']);
|
||||
gulp.task('clean', 'Delete generated files', ['js.core.clean', 'native.core.clean', 'protobuf.clean']);
|
||||
|
||||
gulp.task('clean.all', 'Delete all files created by tasks',
|
||||
['js.core.clean.all', 'native.core.clean.all', 'health-check.clean.all',
|
||||
'internal.test.clean.all', 'js.clean.all', 'native.clean.all']);
|
||||
'internal.test.clean.all', 'js.clean.all', 'native.clean.all', 'protobuf.clean.all']);
|
||||
|
||||
gulp.task('native.test.only', 'Run tests of native code without rebuilding anything',
|
||||
['native.core.test', 'internal.test.test', 'health-check.test']);
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2017 gRPC authors.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as _gulp from 'gulp';
|
||||
import * as help from 'gulp-help';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as mocha from 'gulp-mocha';
|
||||
import * as path from 'path';
|
||||
import * as execa from 'execa';
|
||||
|
||||
// gulp-help monkeypatches tasks to have an additional description parameter
|
||||
const gulp = help(_gulp);
|
||||
|
||||
Error.stackTraceLimit = Infinity;
|
||||
|
||||
const protojsDir = __dirname;
|
||||
const tslintPath = path.resolve(protojsDir, 'node_modules/google-ts-style/tslint.json');
|
||||
const tsconfigPath = path.resolve(protojsDir, 'tsconfig.json');
|
||||
const outDir = path.resolve(protojsDir, 'build');
|
||||
const srcDir = path.resolve(protojsDir, 'src');
|
||||
const testDir = path.resolve(protojsDir, 'test');
|
||||
|
||||
const execNpmVerb = (verb: string, ...args: string[]) =>
|
||||
execa('npm', [verb, ...args], {cwd: protojsDir, stdio: 'inherit'});
|
||||
const execNpmCommand = execNpmVerb.bind(null, 'run');
|
||||
|
||||
gulp.task('install', 'Install native core dependencies', () =>
|
||||
execNpmVerb('install', '--unsafe-perm'));
|
||||
|
||||
/**
|
||||
* Runs tslint on files in src/, with linting rules defined in tslint.json.
|
||||
*/
|
||||
gulp.task('lint', 'Emits linting errors found in src/ and test/.', () =>
|
||||
execNpmCommand('check'));
|
||||
|
||||
gulp.task('clean', 'Deletes transpiled code.', ['install'],
|
||||
() => execNpmCommand('clean'));
|
||||
|
||||
gulp.task('clean.all', 'Deletes all files added by targets', ['clean']);
|
||||
|
||||
/**
|
||||
* Transpiles TypeScript files in src/ to JavaScript according to the settings
|
||||
* found in tsconfig.json.
|
||||
*/
|
||||
gulp.task('compile', 'Transpiles src/.', () => execNpmCommand('compile'));
|
|
@ -1,139 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2017 gRPC authors.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var _ = require('lodash');
|
||||
var ProtoBuf = require('protobufjs');
|
||||
|
||||
module.exports = function(grpc) {
|
||||
|
||||
let exports = {};
|
||||
|
||||
const protobuf_js_5_common = require('protobuf_js_5_common')(grpc);
|
||||
const protobuf_js_6_common = require('protobuf_js_6_common')(grpc);
|
||||
|
||||
/**
|
||||
* Default options for loading proto files into gRPC
|
||||
* @alias grpc~defaultLoadOptions
|
||||
*/
|
||||
const defaultGrpcOptions = {
|
||||
convertFieldsToCamelCase: false,
|
||||
binaryAsBase64: false,
|
||||
longsAsStrings: true,
|
||||
enumsAsStrings: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a ProtoBuf.js object as a gRPC object. The options object can provide
|
||||
* the following options:
|
||||
* - binaryAsBase64: deserialize bytes values as base64 strings instead of
|
||||
* Buffers. Defaults to false
|
||||
* - longsAsStrings: deserialize long values as strings instead of objects.
|
||||
* Defaults to true
|
||||
* - enumsAsStrings: deserialize enum values as strings instead of numbers.
|
||||
* Defaults to true
|
||||
* - protobufjsVersion: Available values are 5, 6, and 'detect'. 5 and 6
|
||||
* respectively indicate that an object from the corresponding version of
|
||||
* ProtoBuf.js is provided in the value argument. If the option is 'detect',
|
||||
* gRPC will guess what the version is based on the structure of the value.
|
||||
* Defaults to 'detect'.
|
||||
* @param {Object} value The ProtoBuf.js reflection object to load
|
||||
* @param {Object=} options Options to apply to the loaded file
|
||||
* @return {Object<string, *>} The resulting gRPC object
|
||||
*/
|
||||
exports.loadObject = function loadObject(value, options) {
|
||||
options = _.defaults(options, defaultGrpcOptions);
|
||||
options = _.defaults(options, {'protobufjsVersion': 'detect'});
|
||||
var protobufjsVersion;
|
||||
if (options.protobufjsVersion === 'detect') {
|
||||
if (protobuf_js_6_common.isProbablyProtobufJs6(value)) {
|
||||
protobufjsVersion = 6;
|
||||
} else if (protobuf_js_5_common.isProbablyProtobufJs5(value)) {
|
||||
protobufjsVersion = 5;
|
||||
} else {
|
||||
var error_message = 'Could not detect ProtoBuf.js version. Please ' +
|
||||
'specify the version number with the "protobufjs_version" option';
|
||||
throw new Error(error_message);
|
||||
}
|
||||
} else {
|
||||
protobufjsVersion = options.protobufjsVersion;
|
||||
}
|
||||
switch (protobufjsVersion) {
|
||||
case 6: return protobuf_js_6_common.loadObject(value, options);
|
||||
case 5: return protobuf_js_5_common.loadObject(value, options);
|
||||
default:
|
||||
throw new Error('Unrecognized protobufjsVersion', protobufjsVersion);
|
||||
}
|
||||
};
|
||||
|
||||
var loadObject = exports.loadObject;
|
||||
|
||||
function applyProtoRoot(filename, root) {
|
||||
if (_.isString(filename)) {
|
||||
return filename;
|
||||
}
|
||||
filename.root = path.resolve(filename.root) + '/';
|
||||
root.resolvePath = function(originPath, importPath, alreadyNormalized) {
|
||||
return ProtoBuf.util.path.resolve(filename.root,
|
||||
importPath,
|
||||
alreadyNormalized);
|
||||
};
|
||||
return filename.file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a gRPC object from a .proto file. The options object can provide the
|
||||
* following options:
|
||||
* - convertFieldsToCamelCase: Load this file with field names in camel case
|
||||
* instead of their original case
|
||||
* - binaryAsBase64: deserialize bytes values as base64 strings instead of
|
||||
* Buffers. Defaults to false
|
||||
* - longsAsStrings: deserialize long values as strings instead of objects.
|
||||
* Defaults to true
|
||||
* - enumsAsStrings: deserialize enum values as strings instead of numbers.
|
||||
* Defaults to true
|
||||
* - deprecatedArgumentOrder: Use the beta method argument order for client
|
||||
* methods, with optional arguments after the callback. Defaults to false.
|
||||
* This option is only a temporary stopgap measure to smooth an API breakage.
|
||||
* It is deprecated, and new code should not use it.
|
||||
* @param {string|{root: string, file: string}} filename The file to load
|
||||
* @param {string=} format The file format to expect. Must be either 'proto' or
|
||||
* 'json'. Defaults to 'proto'
|
||||
* @param {Object=} options Options to apply to the loaded file
|
||||
* @return {Object<string, *>} The resulting gRPC object
|
||||
*/
|
||||
exports.load = function load(filename, format, options) {
|
||||
/* Note: format is currently unused, because the API for loading a proto
|
||||
file or a JSON file is identical in Protobuf.js 6. In the future, there is
|
||||
still the possibility of adding other formats that would be loaded
|
||||
differently */
|
||||
options = _.defaults(options, defaultGrpcOptions);
|
||||
options.protobufjs_version = 6;
|
||||
var root = new ProtoBuf.Root();
|
||||
var parse_options = {keepCase: !options.convertFieldsToCamelCase};
|
||||
return loadObject(root.loadSync(applyProtoRoot(filename, root),
|
||||
parse_options),
|
||||
options);
|
||||
};
|
||||
|
||||
return exports;
|
||||
|
||||
};
|
|
@ -2,9 +2,19 @@
|
|||
"name": "@grpc/protobufjs",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "build/src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"build": "npm run compile",
|
||||
"clean": "gts clean",
|
||||
"compile": "tsc -p .",
|
||||
"format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts",
|
||||
"lint": "tslint -c node_modules/google-ts-style/tslint.json -p . -t codeFrame --type-check",
|
||||
"prepare": "npm run compile",
|
||||
"test": "gulp test",
|
||||
"check": "gts check",
|
||||
"fix": "gts fix",
|
||||
"pretest": "npm run compile",
|
||||
"posttest": "npm run check"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -15,8 +25,15 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/grpc/grpc-node/issues"
|
||||
},
|
||||
"files": [
|
||||
"build/src/*.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/node": "^9.4.6",
|
||||
"clang-format": "^1.2.2",
|
||||
"gts": "^0.5.3",
|
||||
"lodash": "^4.17.4",
|
||||
"protobufjs": "^6.8.0"
|
||||
"protobufjs": "^6.8.5",
|
||||
"typescript": "~2.7.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2017 gRPC authors.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function(grpc) {
|
||||
|
||||
let exports = {};
|
||||
|
||||
/**
|
||||
* Get a function that deserializes a specific type of protobuf.
|
||||
* @param {function()} cls The constructor of the message type to deserialize
|
||||
* @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings
|
||||
* instead of Buffers. Defaults to false
|
||||
* @param {bool=} longsAsStrings Deserialize long values as strings instead of
|
||||
* objects. Defaults to true
|
||||
* @return {function(Buffer):cls} The deserialization function
|
||||
*/
|
||||
exports.deserializeCls = function deserializeCls(cls, options) {
|
||||
/**
|
||||
* Deserialize a buffer to a message object
|
||||
* @param {Buffer} arg_buf The buffer to deserialize
|
||||
* @return {cls} The resulting object
|
||||
*/
|
||||
return function deserialize(arg_buf) {
|
||||
// Convert to a native object with binary fields as Buffers (first argument)
|
||||
// and longs as strings (second argument)
|
||||
return cls.decode(arg_buf).toRaw(options.binaryAsBase64,
|
||||
options.longsAsStrings);
|
||||
};
|
||||
};
|
||||
|
||||
var deserializeCls = exports.deserializeCls;
|
||||
|
||||
/**
|
||||
* Get a function that serializes objects to a buffer by protobuf class.
|
||||
* @param {function()} Cls The constructor of the message type to serialize
|
||||
* @return {function(Cls):Buffer} The serialization function
|
||||
*/
|
||||
exports.serializeCls = function serializeCls(Cls) {
|
||||
/**
|
||||
* Serialize an object to a Buffer
|
||||
* @param {Object} arg The object to serialize
|
||||
* @return {Buffer} The serialized object
|
||||
*/
|
||||
return function serialize(arg) {
|
||||
return new Buffer(new Cls(arg).encode().toBuffer());
|
||||
};
|
||||
};
|
||||
|
||||
var serializeCls = exports.serializeCls;
|
||||
|
||||
/**
|
||||
* Get the fully qualified (dotted) name of a ProtoBuf.Reflect value.
|
||||
* @param {ProtoBuf.Reflect.Namespace} value The value to get the name of
|
||||
* @return {string} The fully qualified name of the value
|
||||
*/
|
||||
exports.fullyQualifiedName = function fullyQualifiedName(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return '';
|
||||
}
|
||||
var name = value.name;
|
||||
var parent_name = fullyQualifiedName(value.parent);
|
||||
if (parent_name !== '') {
|
||||
name = parent_name + '.' + name;
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
var fullyQualifiedName = exports.fullyQualifiedName;
|
||||
|
||||
/**
|
||||
* Return a map from method names to method attributes for the service.
|
||||
* @param {ProtoBuf.Reflect.Service} service The service to get attributes for
|
||||
* @param {Object=} options Options to apply to these attributes
|
||||
* @return {Object} The attributes map
|
||||
*/
|
||||
exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service,
|
||||
options) {
|
||||
var prefix = '/' + fullyQualifiedName(service) + '/';
|
||||
var binaryAsBase64, longsAsStrings;
|
||||
if (options) {
|
||||
binaryAsBase64 = options.binaryAsBase64;
|
||||
longsAsStrings = options.longsAsStrings;
|
||||
}
|
||||
/* This slightly awkward construction is used to make sure we only use
|
||||
lodash@3.10.1-compatible functions. A previous version used
|
||||
_.fromPairs, which would be cleaner, but was introduced in lodash
|
||||
version 4 */
|
||||
return _.zipObject(_.map(service.children, function(method) {
|
||||
return _.camelCase(method.name);
|
||||
}), _.map(service.children, function(method) {
|
||||
return {
|
||||
originalName: method.name,
|
||||
path: prefix + method.name,
|
||||
requestStream: method.requestStream,
|
||||
responseStream: method.responseStream,
|
||||
requestType: method.resolvedRequestType,
|
||||
responseType: method.resolvedResponseType,
|
||||
requestSerialize: serializeCls(method.resolvedRequestType.build()),
|
||||
requestDeserialize: deserializeCls(method.resolvedRequestType.build(),
|
||||
options),
|
||||
responseSerialize: serializeCls(method.resolvedResponseType.build()),
|
||||
responseDeserialize: deserializeCls(method.resolvedResponseType.build(),
|
||||
options)
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
var getProtobufServiceAttrs = exports.getProtobufServiceAttrs;
|
||||
|
||||
/**
|
||||
* Load a gRPC object from an existing ProtoBuf.Reflect object.
|
||||
* @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load.
|
||||
* @param {Object=} options Options to apply to the loaded object
|
||||
* @return {Object<string, *>} The resulting gRPC object
|
||||
*/
|
||||
exports.loadObject = function loadObject(value, options) {
|
||||
var result = {};
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (value.hasOwnProperty('ns')) {
|
||||
return loadObject(value.ns, options);
|
||||
}
|
||||
if (value.className === 'Namespace') {
|
||||
_.each(value.children, function(child) {
|
||||
result[child.name] = loadObject(child, options);
|
||||
});
|
||||
return result;
|
||||
} else if (value.className === 'Service') {
|
||||
return grpc.makeGenericClientConstructor(getProtobufServiceAttrs(value, options),
|
||||
options);
|
||||
} else if (value.className === 'Message' || value.className === 'Enum') {
|
||||
return value.build();
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The primary purpose of this method is to distinguish between reflection
|
||||
* objects from different versions of ProtoBuf.js. This is just a heuristic,
|
||||
* checking for properties that are (currently) specific to this version of
|
||||
* ProtoBuf.js
|
||||
* @param {Object} obj The object to check
|
||||
* @return {boolean} Whether the object appears to be a Protobuf.js 5
|
||||
* ReflectionObject
|
||||
*/
|
||||
exports.isProbablyProtobufJs5 = function isProbablyProtobufJs5(obj) {
|
||||
return _.isArray(obj.children) && (typeof obj.build === 'function');
|
||||
};
|
||||
|
||||
return exports;
|
||||
};
|
|
@ -1,166 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2017 gRPC authors.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function(grpc) {
|
||||
|
||||
let exports = {};
|
||||
|
||||
/**
|
||||
* Get a function that deserializes a specific type of protobuf.
|
||||
* @param {function()} cls The constructor of the message type to deserialize
|
||||
* @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings
|
||||
* instead of Buffers. Defaults to false
|
||||
* @param {bool=} longsAsStrings Deserialize long values as strings instead of
|
||||
* objects. Defaults to true
|
||||
* @return {function(Buffer):cls} The deserialization function
|
||||
*/
|
||||
exports.deserializeCls = function deserializeCls(cls, options) {
|
||||
var conversion_options = {
|
||||
defaults: true,
|
||||
bytes: options.binaryAsBase64 ? String : Buffer,
|
||||
longs: options.longsAsStrings ? String : null,
|
||||
enums: options.enumsAsStrings ? String : null,
|
||||
oneofs: true
|
||||
};
|
||||
/**
|
||||
* Deserialize a buffer to a message object
|
||||
* @param {Buffer} arg_buf The buffer to deserialize
|
||||
* @return {cls} The resulting object
|
||||
*/
|
||||
return function deserialize(arg_buf) {
|
||||
return cls.toObject(cls.decode(arg_buf), conversion_options);
|
||||
};
|
||||
};
|
||||
|
||||
var deserializeCls = exports.deserializeCls;
|
||||
|
||||
/**
|
||||
* Get a function that serializes objects to a buffer by protobuf class.
|
||||
* @param {function()} Cls The constructor of the message type to serialize
|
||||
* @return {function(Cls):Buffer} The serialization function
|
||||
*/
|
||||
exports.serializeCls = function serializeCls(cls) {
|
||||
/**
|
||||
* Serialize an object to a Buffer
|
||||
* @param {Object} arg The object to serialize
|
||||
* @return {Buffer} The serialized object
|
||||
*/
|
||||
return function serialize(arg) {
|
||||
var message = cls.fromObject(arg);
|
||||
return cls.encode(message).finish();
|
||||
};
|
||||
};
|
||||
|
||||
var serializeCls = exports.serializeCls;
|
||||
|
||||
/**
|
||||
* Get the fully qualified (dotted) name of a ProtoBuf.Reflect value.
|
||||
* @param {ProtoBuf.ReflectionObject} value The value to get the name of
|
||||
* @return {string} The fully qualified name of the value
|
||||
*/
|
||||
exports.fullyQualifiedName = function fullyQualifiedName(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return '';
|
||||
}
|
||||
var name = value.name;
|
||||
var parent_fqn = fullyQualifiedName(value.parent);
|
||||
if (parent_fqn !== '') {
|
||||
name = parent_fqn + '.' + name;
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
var fullyQualifiedName = exports.fullyQualifiedName;
|
||||
|
||||
/**
|
||||
* Return a map from method names to method attributes for the service.
|
||||
* @param {ProtoBuf.Service} service The service to get attributes for
|
||||
* @param {Object=} options Options to apply to these attributes
|
||||
* @return {Object} The attributes map
|
||||
*/
|
||||
exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service,
|
||||
options) {
|
||||
var prefix = '/' + fullyQualifiedName(service) + '/';
|
||||
service.resolveAll();
|
||||
return _.zipObject(_.map(service.methods, function(method) {
|
||||
return _.camelCase(method.name);
|
||||
}), _.map(service.methods, function(method) {
|
||||
return {
|
||||
originalName: method.name,
|
||||
path: prefix + method.name,
|
||||
requestStream: !!method.requestStream,
|
||||
responseStream: !!method.responseStream,
|
||||
requestType: method.resolvedRequestType,
|
||||
responseType: method.resolvedResponseType,
|
||||
requestSerialize: serializeCls(method.resolvedRequestType),
|
||||
requestDeserialize: deserializeCls(method.resolvedRequestType, options),
|
||||
responseSerialize: serializeCls(method.resolvedResponseType),
|
||||
responseDeserialize: deserializeCls(method.resolvedResponseType, options)
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
var getProtobufServiceAttrs = exports.getProtobufServiceAttrs;
|
||||
|
||||
exports.loadObject = function loadObject(value, options) {
|
||||
var result = {};
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (value.hasOwnProperty('methods')) {
|
||||
// It's a service object
|
||||
var service_attrs = getProtobufServiceAttrs(value, options);
|
||||
return grpc..makeGenericClientConstructor(service_attrs);
|
||||
}
|
||||
|
||||
if (value.hasOwnProperty('nested')) {
|
||||
// It's a namespace or root object
|
||||
_.each(value.nested, function(nested, name) {
|
||||
result[name] = loadObject(nested, options);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Otherwise, it's not something we need to change
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* The primary purpose of this method is to distinguish between reflection
|
||||
* objects from different versions of ProtoBuf.js. This is just a heuristic,
|
||||
* checking for properties that are (currently) specific to this version of
|
||||
* ProtoBuf.js
|
||||
* @param {Object} obj The object to check
|
||||
* @return {boolean} Whether the object appears to be a Protobuf.js 6
|
||||
* ReflectionObject
|
||||
*/
|
||||
exports.isProbablyProtobufJs6 = function isProbablyProtobufJs6(obj) {
|
||||
return (typeof obj.root === 'object') && (typeof obj.resolve === 'function');
|
||||
};
|
||||
|
||||
return exports;
|
||||
};
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
import * as Protobuf from 'protobufjs';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface Serialize<T> {
|
||||
(value: T): Buffer;
|
||||
}
|
||||
|
||||
export interface Deserialize<T> {
|
||||
(bytes: Buffer): T;
|
||||
}
|
||||
|
||||
export interface MethodDefinition<RequestType, ResponseType> {
|
||||
path: string;
|
||||
requestStream: boolean;
|
||||
responseStream: boolean;
|
||||
requestSerialize: Serialize<RequestType>;
|
||||
responseSerialize: Serialize<ResponseType>;
|
||||
requestDeserialize: Deserialize<RequestType>;
|
||||
responseDeserialize: Deserialize<ResponseType>;
|
||||
}
|
||||
|
||||
export interface ServiceDefinition {
|
||||
[index: string]: MethodDefinition<object, object>;
|
||||
}
|
||||
|
||||
export interface PackageDefinition {
|
||||
[index: string]: ServiceDefinition;
|
||||
}
|
||||
|
||||
export type Options = Protobuf.IParseOptions & Protobuf.IConversionOptions & {
|
||||
include?: string[];
|
||||
};
|
||||
|
||||
function joinName(baseName: string, name: string): string {
|
||||
if (baseName === '') {
|
||||
return name;
|
||||
} else {
|
||||
return baseName + '.' + name;
|
||||
}
|
||||
}
|
||||
|
||||
function getAllServices(obj: Protobuf.NamespaceBase, parentName: string): Array<[string, Protobuf.Service]> {
|
||||
const objName = joinName(parentName, obj.name);
|
||||
if (obj.hasOwnProperty('methods')) {
|
||||
return [[objName, obj as Protobuf.Service]];
|
||||
} else {
|
||||
return obj.nestedArray.map((child) => {
|
||||
if (child.hasOwnProperty('nested')) {
|
||||
return getAllServices(child as Protobuf.NamespaceBase, objName);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}).reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
|
||||
}
|
||||
}
|
||||
|
||||
function createDeserializer(cls: Protobuf.Type, options: Options): Deserialize<object> {
|
||||
return function deserialize(argBuf: Buffer): object {
|
||||
return cls.toObject(cls.decode(argBuf), options);
|
||||
};
|
||||
}
|
||||
|
||||
function createSerializer(cls: Protobuf.Type): Serialize<object> {
|
||||
return function serialize(arg: object): Buffer {
|
||||
const message = cls.fromObject(arg);
|
||||
return cls.encode(message).finish() as Buffer;
|
||||
};
|
||||
}
|
||||
|
||||
function createMethodDefinition(method: Protobuf.Method, serviceName: string, options: Options): MethodDefinition<object, object> {
|
||||
return {
|
||||
path: '/' + serviceName + '/' + method.name,
|
||||
requestStream: !!method.requestStream,
|
||||
responseStream: !!method.responseStream,
|
||||
requestSerialize: createSerializer(method.resolvedRequestType as Protobuf.Type),
|
||||
requestDeserialize: createDeserializer(method.resolvedRequestType as Protobuf.Type, options),
|
||||
responseSerialize: createSerializer(method.resolvedResponseType as Protobuf.Type),
|
||||
responseDeserialize: createDeserializer(method.resolvedResponseType as Protobuf.Type, options)
|
||||
};
|
||||
}
|
||||
|
||||
function createServiceDefinition(service: Protobuf.Service, name: string, options: Options): ServiceDefinition {
|
||||
const def: ServiceDefinition = {};
|
||||
for (const method of service.methodsArray) {
|
||||
def[method.name] = createMethodDefinition(method, name, options);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
function createPackageDefinition(root: Protobuf.Root, options: Options): PackageDefinition {
|
||||
const def: PackageDefinition = {};
|
||||
for (const [name, service] of getAllServices(root, '')) {
|
||||
def[name] = createServiceDefinition(service, name, options);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a .proto file with the specified options.
|
||||
* @param filename The file path to load. Can be an absolute path or relative to
|
||||
* an include path.
|
||||
* @param options.keepCase Preserve field names. The default is to change them
|
||||
* to camel case.
|
||||
* @param options.longs The type that should be used to represent `long` values.
|
||||
* Valid options are `Number` and `String`. Defaults to a `Long` object type
|
||||
* from a library.
|
||||
* @param options.enums The type that should be used to represent `enum` values.
|
||||
* The only valid option is `String`. Defaults to the numeric value.
|
||||
* @param options.bytes The type that should be used to represent `bytes`
|
||||
* values. Valid options are `Array` and `String`. The default is to use
|
||||
* `Buffer`.
|
||||
* @param options.defaults Set default values on output objects. Defaults to
|
||||
* `false`.
|
||||
* @param options.arrays Set empty arrays for missing array values even if
|
||||
* `defaults` is `false`. Defaults to `false`.
|
||||
* @param options.objects Set empty objects for missing object values even if
|
||||
* `defaults` is `false`. Defaults to `false`.
|
||||
* @param options.oneofs Set virtual oneof properties to the present field's
|
||||
* name
|
||||
* @param options.include Paths to search for imported `.proto` files.
|
||||
*/
|
||||
export function load(filename: string, options: Options): Promise<PackageDefinition> {
|
||||
const root: Protobuf.Root = new Protobuf.Root();
|
||||
if (!!options.include) {
|
||||
if (!(options.include instanceof Array)) {
|
||||
return Promise.reject(new Error('The include option must be an array'));
|
||||
}
|
||||
root.resolvePath = (origin: string, target: string) => {
|
||||
for (const directory of options.include as string[]) {
|
||||
const fullPath: string = path.join(directory, target);
|
||||
try {
|
||||
fs.accessSync(fullPath, fs.constants.R_OK);
|
||||
return fullPath;
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
return root.load(filename, options).then((loadedRoot) => {
|
||||
loadedRoot.resolveAll();
|
||||
return createPackageDefinition(root, options);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "./node_modules/gts/tsconfig-google.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build"
|
||||
},
|
||||
"include": [
|
||||
"src/*.ts",
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue