Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/third_party/endpoints/directory_list_generator.py b/third_party/endpoints/directory_list_generator.py
new file mode 100644
index 0000000..40f26b6
--- /dev/null
+++ b/third_party/endpoints/directory_list_generator.py
@@ -0,0 +1,162 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# 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.
+
+"""A library for converting service configs to discovery directory lists."""
+
+from __future__ import absolute_import
+
+import collections
+import json
+import re
+from six.moves import urllib
+
+from . import util
+
+
+class DirectoryListGenerator(object):
+  """Generates a discovery directory list from a ProtoRPC service.
+
+  Example:
+
+    class HelloRequest(messages.Message):
+      my_name = messages.StringField(1, required=True)
+
+    class HelloResponse(messages.Message):
+      hello = messages.StringField(1, required=True)
+
+    class HelloService(remote.Service):
+
+      @remote.method(HelloRequest, HelloResponse)
+      def hello(self, request):
+        return HelloResponse(hello='Hello there, %s!' %
+                             request.my_name)
+
+    api_config = DirectoryListGenerator().pretty_print_config_to_json(
+        HelloService)
+
+  The resulting document will be a JSON directory list describing the APIs
+  implemented by HelloService.
+  """
+
+  def __init__(self, request=None):
+    # The ApiRequest that called this generator
+    self.__request = request
+
+  def __item_descriptor(self, config):
+    """Builds an item descriptor for a service configuration.
+
+    Args:
+      config: A dictionary containing the service configuration to describe.
+
+    Returns:
+      A dictionary that describes the service configuration.
+    """
+    descriptor = {
+        'kind': 'discovery#directoryItem',
+        'icons': {
+            'x16': 'https://www.gstatic.com/images/branding/product/1x/'
+                   'googleg_16dp.png',
+            'x32': 'https://www.gstatic.com/images/branding/product/1x/'
+                   'googleg_32dp.png',
+        },
+        'preferred': True,
+    }
+
+    description = config.get('description')
+    root_url = config.get('root')
+    name = config.get('name')
+    version = config.get('api_version')
+    relative_path = '/apis/{0}/{1}/rest'.format(name, version)
+
+    if description:
+      descriptor['description'] = description
+
+    descriptor['name'] = name
+    descriptor['version'] = version
+    descriptor['discoveryLink'] = '.{0}'.format(relative_path)
+
+    root_url_port = urllib.parse.urlparse(root_url).port
+
+    original_path = self.__request.reconstruct_full_url(
+        port_override=root_url_port)
+    descriptor['discoveryRestUrl'] = '{0}/{1}/{2}/rest'.format(
+        original_path, name, version)
+
+    if name and version:
+      descriptor['id'] = '{0}:{1}'.format(name, version)
+
+    return descriptor
+
+  def __directory_list_descriptor(self, configs):
+    """Builds a directory list for an API.
+
+    Args:
+      configs: List of dicts containing the service configurations to list.
+
+    Returns:
+      A dictionary that can be deserialized into JSON in discovery list format.
+
+    Raises:
+      ApiConfigurationError: If there's something wrong with the API
+        configuration, such as a multiclass API decorated with different API
+        descriptors (see the docstring for api()), or a repeated method
+        signature.
+    """
+    descriptor = {
+        'kind': 'discovery#directoryList',
+        'discoveryVersion': 'v1',
+    }
+
+    items = []
+    for config in configs:
+      item_descriptor = self.__item_descriptor(config)
+      if item_descriptor:
+        items.append(item_descriptor)
+
+    if items:
+      descriptor['items'] = items
+
+    return descriptor
+
+  def get_directory_list_doc(self, configs):
+    """JSON dict description of a protorpc.remote.Service in list format.
+
+    Args:
+      configs: Either a single dict or a list of dicts containing the service
+        configurations to list.
+
+    Returns:
+      dict, The directory list document as a JSON dict.
+    """
+
+    if not isinstance(configs, (tuple, list)):
+      configs = [configs]
+
+    util.check_list_type(configs, dict, 'configs', allow_none=False)
+
+    return self.__directory_list_descriptor(configs)
+
+  def pretty_print_config_to_json(self, configs):
+    """JSON string description of a protorpc.remote.Service in a discovery doc.
+
+    Args:
+      configs: Either a single dict or a list of dicts containing the service
+        configurations to list.
+
+    Returns:
+      string, The directory list document as a JSON string.
+    """
+    descriptor = self.get_directory_list_doc(configs)
+    return json.dumps(descriptor, sort_keys=True, indent=2,
+                      separators=(',', ': '))