Skip to content

Commit 4f36ef0

Browse files
committed
resolve naming collisions
1 parent 80d0dd9 commit 4f36ef0

File tree

2 files changed

+87
-4
lines changed

2 files changed

+87
-4
lines changed

packages/gapic-generator/gapic/schema/wrappers.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,10 +685,36 @@ def resource_path(self) -> Optional[str]:
685685
If there are multiple paths, returns the first one."""
686686
return next(iter(self.options.Extensions[resource_pb2.resource].pattern), None)
687687

688+
def _apply_domain_heuristic(self, raw_type: str) -> str:
689+
"""Determines if a resource is foreign and adds a prefix to prevent
690+
[no-redef] AST collisions."""
691+
if not raw_type:
692+
return ""
693+
694+
if "/" not in raw_type:
695+
return raw_type
696+
697+
# Extract the root domain and final resource name, bypassing any nested paths.
698+
# (e.g., "ces.googleapis.com/Project/Location/Tool" -> "ces.googleapis.com" and "Tool")
699+
resource_parts = raw_type.split('/')
700+
domain, short_name = resource_parts[0], resource_parts[-1]
701+
domain_prefix = domain.split('.', 1)[0]
702+
703+
try:
704+
native_package = self.meta.address.package
705+
# 2. If the domain prefix isn't natively in the package namespace, it's foreign
706+
if domain_prefix and native_package and domain_prefix not in native_package:
707+
return f"{domain_prefix}_{short_name}"
708+
except (AttributeError, TypeError):
709+
# 3. Safe fallback if meta, address, or package are missing/None on this wrapper
710+
pass
711+
712+
return short_name
713+
688714
@property
689715
def resource_type(self) -> Optional[str]:
690716
resource = self.options.Extensions[resource_pb2.resource]
691-
return resource.type[resource.type.find("/") + 1 :] if resource else None
717+
return self._apply_domain_heuristic(resource.type) if resource else None
692718

693719
@property
694720
def resource_type_full_path(self) -> Optional[str]:

packages/gapic-generator/tests/unit/schema/wrappers/test_message.py

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def test_resource_path():
188188
resource.pattern.append("kingdoms/{kingdom}/phyla/{phylum}/classes/{klass}")
189189
resource.pattern.append("kingdoms/{kingdom}/divisions/{division}/classes/{klass}")
190190
resource.type = "taxonomy.biology.com/Class"
191-
message = make_message("Squid", options=options)
191+
message = make_message("Squid", options=options, package="taxonomy.biology.v1")
192192

193193
assert message.resource_path == "kingdoms/{kingdom}/phyla/{phylum}/classes/{klass}"
194194
assert message.resource_path_args == ["kingdom", "phylum", "klass"]
@@ -201,7 +201,7 @@ def test_resource_path_with_wildcard():
201201
resource.pattern.append("kingdoms/{kingdom}/phyla/{phylum}/classes/{klass=**}")
202202
resource.pattern.append("kingdoms/{kingdom}/divisions/{division}/classes/{klass}")
203203
resource.type = "taxonomy.biology.com/Class"
204-
message = make_message("Squid", options=options)
204+
message = make_message("Squid", options=options, package="taxonomy.biology.v1")
205205

206206
assert (
207207
message.resource_path == "kingdoms/{kingdom}/phyla/{phylum}/classes/{klass=**}"
@@ -230,7 +230,7 @@ def test_resource_path_pure_wildcard():
230230
resource = options.Extensions[resource_pb2.resource]
231231
resource.pattern.append("*")
232232
resource.type = "taxonomy.biology.com/Class"
233-
message = make_message("Squid", options=options)
233+
message = make_message("Squid", options=options, package="taxonomy.biology.v1")
234234

235235
# Pure wildcard resource names do not really help construct resources
236236
# but they are a part of the spec so we need to support them, which means at
@@ -473,3 +473,60 @@ def test_extended_operation_request_response_fields():
473473

474474
actual = poll_request.extended_operation_response_fields
475475
assert actual == expected
476+
477+
478+
@pytest.mark.parametrize(
479+
"raw_type, package_tuple, expected",
480+
[
481+
# 1. Empty or malformed inputs
482+
("", ("google", "cloud", "dialogflow", "v2"), ""),
483+
("Tool", ("google", "cloud", "dialogflow", "v2"), "Tool"),
484+
485+
# 2. Native Resources (Prefix 'dialogflow' IS in the package tuple)
486+
("dialogflow.googleapis.com/Tool", ("google", "cloud", "dialogflow", "v2"), "Tool"),
487+
("dialogflow.googleapis.com/Project/Location/Tool", ("google", "cloud", "dialogflow", "v2"), "Tool"),
488+
489+
# 3. Foreign Resources (Prefix 'ces' is NOT in the package tuple)
490+
("ces.googleapis.com/Tool", ("google", "cloud", "dialogflow", "v2"), "ces_Tool"),
491+
("ces.googleapis.com/Project/Location/Tool", ("google", "cloud", "dialogflow", "v2"), "ces_Tool"),
492+
]
493+
)
494+
def test_apply_domain_heuristic(raw_type, package_tuple, expected):
495+
meta = metadata.Metadata(
496+
address=metadata.Address(
497+
name="TestMessage",
498+
package=package_tuple,
499+
module="test",
500+
)
501+
)
502+
message = make_message("TestMessage", meta=meta)
503+
504+
actual = message._apply_domain_heuristic(raw_type)
505+
assert actual == expected
506+
507+
508+
def test_apply_domain_heuristic_none_package():
509+
"""Test the EAFP failsafe if package is explicitly None (TypeError)."""
510+
meta = metadata.Metadata(
511+
address=metadata.Address(
512+
name="TestMessage",
513+
package=None,
514+
module="test",
515+
)
516+
)
517+
message = make_message("TestMessage", meta=meta)
518+
519+
actual = message._apply_domain_heuristic("ces.googleapis.com/Tool")
520+
assert actual == "Tool"
521+
522+
523+
def test_apply_domain_heuristic_missing_meta():
524+
"""Test the EAFP failsafe if meta or address are missing (AttributeError)."""
525+
# To test the AttributeError fallback cleanly without mocks, we can just
526+
# pass a bare-bones Python class to the unbound method. This perfectly
527+
# proves the try/except block safely handles totally missing attributes.
528+
class EmptyMessage:
529+
pass
530+
531+
actual = wrappers.MessageType._apply_domain_heuristic(EmptyMessage(), "ces.googleapis.com/Tool")
532+
assert actual == "Tool"

0 commit comments

Comments
 (0)