Index: server/static/rpcexplorer/rpc-descriptor-util.html |
diff --git a/server/static/rpcexplorer/rpc-descriptor-util.html b/server/static/rpcexplorer/rpc-descriptor-util.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7fca7b25c78f3d790e24d8f212d72c9ee2116661 |
--- /dev/null |
+++ b/server/static/rpcexplorer/rpc-descriptor-util.html |
@@ -0,0 +1,205 @@ |
+<!-- |
+ Copyright 2016 The Chromium Authors. All rights reserved. |
+ Use of this source code is governed by a BSD-style license that can be |
+ found in the LICENSE file. |
+ --> |
+ |
+<!-- |
+ rpcExplorer.descUtil exposes helper methods to work with protobuf |
+ descriptor messages. |
+ Primarily it implements name and comments resolution in a FileDescriptorSet. |
+--> |
+<script> |
+ 'use strict'; |
+ |
+ var rpcExplorer = (function(rpcExplorer) { |
+ |
+ rpcExplorer.descUtil = { |
+ |
+ /** |
+ * for all descriptors in the file annotate resolves |
+ * SourceLocationInfo.Location message and assigns it to the |
+ * source_code_info property of the descriptor. |
+ * Prerequisite reading: |
+ * https://github.com/luci/luci-go/blob/ea240d0/common/proto/google/descriptor/descriptor.proto#L713 |
+ * @param {FileDescriptorProto} file |
+ */ |
+ annotate: function(file) { |
+ if (!file.source_code_info) { |
+ return; |
+ } |
+ |
+ // First, build a map { path -> location message }. |
+ function key(path) { |
+ var key = ''; |
+ for (var i = 0; i < path.length; i++) { |
+ key += path[i] + '.'; |
+ } |
+ return key; |
+ } |
+ var locationMap = {}; |
+ for (var i = 0; i < file.source_code_info.location.length; i++) { |
+ var loc = file.source_code_info.location[i]; |
+ if (loc.path) { |
+ locationMap[key(loc.path)] = loc; |
+ } |
+ } |
+ |
+ // Now join all descriptors in file with the map. |
+ var path = []; |
+ |
+ function annotateList(list, field, fn) { |
+ if (!list) { |
+ return; |
+ } |
+ path.push(field, 0); |
+ for (var i = 0; i < list.length; i++) { |
+ path[path.length - 1] = i; |
+ list[i].source_code_info = locationMap[key(path)]; |
+ if (fn) { |
+ fn(list[i]); |
+ } |
+ } |
+ path.pop(); |
+ path.pop(); |
+ } |
+ |
+ // The magic numbers below are message field numbers defined in |
+ // descriptor.proto. |
+ |
+ function annotateMessage(msg) { |
+ annotateList(msg.field, 2); |
+ annotateList(msg.nested_type, 3, annotateMessage); |
+ annotateList(msg.enum_type, 4, annotateEnum); |
+ } |
+ |
+ function annotateEnum(e) { |
+ annotateList(e.value, 2); |
+ } |
+ |
+ function annotateService(svc) { |
+ annotateList(svc.method, 2); |
+ } |
+ |
+ annotateList(file.message_type, 4, annotateMessage); |
+ annotateList(file.enum_type, 5, annotateEnum); |
+ annotateList(file.service, 6, annotateService); |
+ }, |
+ |
+ /** |
+ * Annotates a FileDescriptorSet. |
+ */ |
+ annotateSet: function(fileSet) { |
+ for (var i = 0; i < fileSet.file.length; i++) { |
+ this.annotate(fileSet.file[i]); |
+ } |
+ }, |
+ |
+ splitFullName: function(fullName) { |
+ var lastDot = fullName.lastIndexOf('.'); |
+ if (lastDot === -1) { |
+ return { |
+ pkg: '', |
+ name: fullName |
+ }; |
+ } |
+ |
+ return { |
+ pkg: fullName.substr(0, lastDot), |
+ name: fullName.substr(lastDot + 1) |
+ }; |
+ }, |
+ |
+ /** |
+ * Resolves services, methods, messages, fields, enums and enum values. |
+ */ |
+ resolve: function(desc, name) { |
+ if (!desc || !name) { |
+ return null; |
+ } |
+ name = this.splitFullName(name); |
+ |
+ var self = this; |
+ |
+ // searches in each list. |
+ function checkLists(lists) { |
+ if (!lists) { |
+ return null; |
+ } |
+ for (var type in lists) { |
+ var desc = self.findByName(lists[type], name.name); |
+ if (desc) { |
+ return {type: type, desc: desc }; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ // Check top-level descriptors. |
+ for (var i = 0; i < desc.file.length; i++) { |
+ var file = desc.file[i]; |
+ if (file['package'] != name.pkg) { |
+ continue |
+ } |
+ |
+ var result = checkLists({ |
+ service: file.service, |
+ messageType: file.message_type, |
+ enumType: file.enum_type |
+ }); |
+ if (result) { |
+ return result; |
+ } |
+ } |
+ |
+ // Possibly the entity we are resolving is not top-level and |
+ // name.name references an object inside an object referenced by |
+ // name.pkg. Try to resolve name.pkg. |
+ var parent = this.resolve(desc, name.pkg); |
+ if (!parent) { |
+ return null; |
+ } |
+ switch (parent.type) { |
+ case 'service': |
+ return checkLists({ method: parent.desc.method }); |
+ |
+ case 'messageType': |
+ return checkLists({ |
+ field: parent.desc.field, |
+ messageType: parent.desc.nested_type, |
+ enumType: parent.desc.enum_type |
+ }); |
+ |
+ case 'enumType': |
+ return checkLists({ |
+ enumValue: parent.desc.value, |
+ }); |
+ |
+ default: |
+ return null; |
+ } |
+ }, |
+ |
+ findByName: function(array, name) { |
+ if (!array) { |
+ return null; |
+ } |
+ for (var i = 0; i < array.length; i++) { |
+ if (array[i].name === name) { |
+ return array[i]; |
+ } |
+ } |
+ return null; |
+ }, |
+ |
+ trimPrefixDot: function(name) { |
+ if (typeof name === 'string' && name.charAt(0) === '.') { |
+ name = name.substr(1); |
+ } |
+ return name; |
+ } |
+ }; |
+ |
+ return rpcExplorer; |
+ }(rpcExplorer || {})); |
+</script> |