diff --git a/gapic/schema/wrappers.py b/gapic/schema/wrappers.py index 24422244a9..2fbc5a2b20 100644 --- a/gapic/schema/wrappers.py +++ b/gapic/schema/wrappers.py @@ -743,6 +743,7 @@ def try_parse_http_rule(cls, http_rule) -> Optional['HttpRule']: uri = getattr(http_rule, method) if not uri: return None + uri = utils.convert_uri_fieldnames(uri) body = http_rule.body or None return cls(method, uri, body) diff --git a/gapic/utils/__init__.py b/gapic/utils/__init__.py index 447b0df837..5000d78b49 100644 --- a/gapic/utils/__init__.py +++ b/gapic/utils/__init__.py @@ -28,11 +28,13 @@ from gapic.utils.options import Options from gapic.utils.reserved_names import RESERVED_NAMES from gapic.utils.rst import rst +from gapic.utils.uri_conv import convert_uri_fieldnames from gapic.utils.uri_sample import sample_from_path_fields __all__ = ( 'cached_property', + 'convert_uri_fieldnames', 'doc', 'empty', 'is_msg_field_pb', diff --git a/gapic/utils/uri_conv.py b/gapic/utils/uri_conv.py new file mode 100644 index 0000000000..988a8c2997 --- /dev/null +++ b/gapic/utils/uri_conv.py @@ -0,0 +1,49 @@ +# Copyright 2021 Google LLC +# +# 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 +# +# https://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. + +from gapic.utils.reserved_names import RESERVED_NAMES +from google.api_core import path_template # type: ignore + + +def convert_uri_fieldnames(uri: str) -> str: + """Modify field names in uri_templates to avoid reserved names. + + Args: + uri: a uri template, optionally containing field references in {} braces. + + Returns: + the uri with any field names modified if they conflict with + reserved names. + """ + + def _fix_name_segment(name_seg: str) -> str: + return name_seg + "_" if name_seg in RESERVED_NAMES else name_seg + + def _fix_field_path(field_path: str) -> str: + return ".".join( + (_fix_name_segment(name_seg) for name_seg in field_path.split("."))) + + last = 0 + pieces = [] + for match in path_template._VARIABLE_RE.finditer(uri): + start_pos, end_pos = match.span("name") + if start_pos == end_pos: + continue + pieces.append(uri[last:start_pos]) + fixed_field_path = _fix_field_path(uri[start_pos:end_pos]) + pieces.append(fixed_field_path) + last = end_pos + pieces.append(uri[last:]) + + return "".join(pieces) diff --git a/noxfile.py b/noxfile.py index 8b446ff44b..c4e3f34f43 100644 --- a/noxfile.py +++ b/noxfile.py @@ -34,7 +34,7 @@ def unit(session): """Run the unit test suite.""" session.install( - "coverage", "pytest-cov", "pytest", "pytest-xdist", "pyfakefs", + "coverage", "pytest-cov", "pytest", "pytest-xdist", "pyfakefs", "grpcio-status", ) session.install("-e", ".") diff --git a/tests/unit/schema/wrappers/test_method.py b/tests/unit/schema/wrappers/test_method.py index d377375036..72766d453e 100644 --- a/tests/unit/schema/wrappers/test_method.py +++ b/tests/unit/schema/wrappers/test_method.py @@ -452,6 +452,19 @@ def test_method_http_options_additional_bindings(): }] +def test_method_http_options_reserved_name_in_url(): + http_rule = http_pb2.HttpRule( + post='/v1/license/{license=lic/*}', + body='*' + ) + method = make_method('DoSomething', http_rule=http_rule) + assert [dataclasses.asdict(http) for http in method.http_options] == [{ + 'method': 'post', + 'uri': '/v1/license/{license_=lic/*}', + 'body': '*' + }] + + def test_method_http_options_generate_sample(): http_rule = http_pb2.HttpRule( get='/v1/{resource.id=projects/*/regions/*/id/**}/stuff', diff --git a/tests/unit/utils/test_uri_conv.py b/tests/unit/utils/test_uri_conv.py new file mode 100644 index 0000000000..5c870b430a --- /dev/null +++ b/tests/unit/utils/test_uri_conv.py @@ -0,0 +1,35 @@ +# Copyright 2021 Google LLC +# +# 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 +# +# https://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. + +from unittest import mock + +import pypandoc + +from gapic import utils + + +def test_convert_uri_fieldname(): + uri = "abc/*/license/{license}/{xyz.reversed=reversed/*}" + expected_uri = "abc/*/license/{license_}/{xyz.reversed_=reversed/*}" + assert utils.convert_uri_fieldnames(uri) == expected_uri + + +def test_convert_uri_fieldname_no_fields(): + uri = "abc/license" + assert utils.convert_uri_fieldnames(uri) == uri + + +def test_convert_uri_fieldname_no_reserved_names(): + uri = "abc/*/books/{book}/{xyz.chapter=page/*}" + assert utils.convert_uri_fieldnames(uri) == uri