From c99e91ec652bac79bc4b504bbd33f31e1b771869 Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Wed, 12 Nov 2025 20:06:45 +0000 Subject: [PATCH 01/11] Fixing Deduplication in test --- .../registry/tools/RdapQueryCommand.java | 97 +++++++++++++++++++ .../google/registry/tools/RegistryTool.java | 1 + .../registry/tools/RegistryToolComponent.java | 2 + 3 files changed, 100 insertions(+) create mode 100644 core/src/main/java/google/registry/tools/RdapQueryCommand.java diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java new file mode 100644 index 00000000000..9d20b49d80e --- /dev/null +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -0,0 +1,97 @@ +// Copyright 2024 The Nomulus Authors. 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. + +package google.registry.tools; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.flogger.GoogleLogger; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import google.registry.config.RegistryConfig.Config; +import google.registry.request.Action.GkeService; +import jakarta.inject.Inject; // <-- CORRECTED IMPORT +import java.io.IOException; +import java.util.List; + +/** Command to manually perform an authenticated RDAP query. */ +@Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") +public final class RdapQueryCommand implements CommandWithConnection { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private static final ImmutableSet VALID_TYPES = + ImmutableSet.of("domain", "registrar", "contact", "nameserver"); + + @Parameter(description = "The object type and query term.", required = true) + private List mainParameters; + + private ServiceConnection defaultConnection; + + @Inject + @Config("useCanary") + boolean useCanary; + + @Override + public void setConnection(ServiceConnection connection) { + this.defaultConnection = connection; + } + + @Override + public void run() { + checkArgument( + mainParameters != null && mainParameters.size() == 2, + "Usage: nomulus rdap_query \n" + + " must be one of " + + VALID_TYPES); + + String type = Ascii.toLowerCase(mainParameters.get(0)); + checkArgument( + VALID_TYPES.contains(type), + "Invalid object type '%s'. Must be one of %s", + type, + VALID_TYPES); + + String name = mainParameters.get(1); + String path = String.format("/rdap/%s/%s", type, name); + + logger.atInfo().log("Starting RDAP query for path: %s", path); + + try { + if (defaultConnection == null) { + throw new IllegalStateException("ServiceConnection was not set by RegistryCli."); + } + ServiceConnection pubapiConnection = + defaultConnection.withService(GkeService.PUBAPI, useCanary); + + String rdapResponse = pubapiConnection.sendGetRequest(path, ImmutableMap.of()); + JsonElement rdapJson = JsonParser.parseString(rdapResponse); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + System.out.println(gson.toJson(rdapJson)); + + logger.atInfo().log("Successfully completed RDAP query for path: %s", path); + } catch (IOException e) { + logger.atSevere().withCause(e).log("Request failed for path: %s", path); + System.err.println("Request failed for " + path + ": " + e.getMessage()); + } + } +} diff --git a/core/src/main/java/google/registry/tools/RegistryTool.java b/core/src/main/java/google/registry/tools/RegistryTool.java index c08c5cc490c..869e9a927ca 100644 --- a/core/src/main/java/google/registry/tools/RegistryTool.java +++ b/core/src/main/java/google/registry/tools/RegistryTool.java @@ -98,6 +98,7 @@ public final class RegistryTool { .put("login", LoginCommand.class) .put("logout", LogoutCommand.class) .put("pending_escrow", PendingEscrowCommand.class) + .put("rdap_query", RdapQueryCommand.class) .put("recreate_billing_recurrences", RecreateBillingRecurrencesCommand.class) .put("registrar_poc", RegistrarPocCommand.class) .put("renew_domain", RenewDomainCommand.class) diff --git a/core/src/main/java/google/registry/tools/RegistryToolComponent.java b/core/src/main/java/google/registry/tools/RegistryToolComponent.java index 4846e77b384..386ec2a5749 100644 --- a/core/src/main/java/google/registry/tools/RegistryToolComponent.java +++ b/core/src/main/java/google/registry/tools/RegistryToolComponent.java @@ -135,6 +135,8 @@ interface RegistryToolComponent { void inject(PendingEscrowCommand command); + void inject(RdapQueryCommand command); + void inject(RenewDomainCommand command); void inject(SaveSqlCredentialCommand command); From 92edd4578ce2dadd520ffeb5ded8ad99b00a6b91 Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Thu, 13 Nov 2025 10:51:17 +0000 Subject: [PATCH 02/11] feat: Implement rdap_query command --- .../registry/tools/RdapQueryCommand.java | 205 ++++++++++++++++-- 1 file changed, 182 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index 9d20b49d80e..e4a4772e148 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -1,3 +1,100 @@ +// // Copyright 2024 The Nomulus Authors. 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. +// +// package google.registry.tools; +// +// import static com.google.common.base.Preconditions.checkArgument; +// +// import com.beust.jcommander.Parameter; +// import com.beust.jcommander.Parameters; +// import com.google.common.base.Ascii; +// import com.google.common.collect.ImmutableMap; +// import com.google.common.collect.ImmutableSet; +// import com.google.common.flogger.GoogleLogger; +// import com.google.gson.Gson; +// import com.google.gson.GsonBuilder; +// import com.google.gson.JsonElement; +// import com.google.gson.JsonParser; +// import google.registry.config.RegistryConfig.Config; +// import google.registry.request.Action.GkeService; +// import jakarta.inject.Inject; // <-- CORRECTED IMPORT +// import java.io.IOException; +// import java.util.List; +// +// /** Command to manually perform an authenticated RDAP query. */ +// @Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") +// public final class RdapQueryCommand implements CommandWithConnection { +// +// private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); +// +// private static final ImmutableSet VALID_TYPES = +// ImmutableSet.of("domain", "registrar", "contact", "nameserver"); +// +// @Parameter(description = "The object type and query term.", required = true) +// private List mainParameters; +// +// private ServiceConnection defaultConnection; +// +// @Inject +// @Config("useCanary") +// boolean useCanary; +// +// @Override +// public void setConnection(ServiceConnection connection) { +// this.defaultConnection = connection; +// } +// +// @Override +// public void run() { +// checkArgument( +// mainParameters != null && mainParameters.size() == 2, +// "Usage: nomulus rdap_query \n" +// + " must be one of " +// + VALID_TYPES); +// +// String type = Ascii.toLowerCase(mainParameters.get(0)); +// checkArgument( +// VALID_TYPES.contains(type), +// "Invalid object type '%s'. Must be one of %s", +// type, +// VALID_TYPES); +// +// String name = mainParameters.get(1); +// String path = String.format("/rdap/%s/%s", type, name); +// +// logger.atInfo().log("Starting RDAP query for path: %s", path); +// +// try { +// if (defaultConnection == null) { +// throw new IllegalStateException("ServiceConnection was not set by RegistryCli."); +// } +// ServiceConnection pubapiConnection = +// defaultConnection.withService(GkeService.PUBAPI, useCanary); +// +// String rdapResponse = pubapiConnection.sendGetRequest(path, ImmutableMap.of()); +// JsonElement rdapJson = JsonParser.parseString(rdapResponse); +// +// Gson gson = new GsonBuilder().setPrettyPrinting().create(); +// System.out.println(gson.toJson(rdapJson)); +// +// logger.atInfo().log("Successfully completed RDAP query for path: %s", path); +// } catch (IOException e) { +// logger.atSevere().withCause(e).log("Request failed for path: %s", path); +// System.err.println("Request failed for " + path + ": " + e.getMessage()); +// } +// } +// } // Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,13 +109,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.tools; - -import static com.google.common.base.Preconditions.checkArgument; +// Copyright 2024 The Nomulus Authors. 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. +// In google.registry.tools.RdapQueryCommand.java +// ... import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Ascii; +import com.google.common.base.Splitter; // Keep Splitter import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.flogger.GoogleLogger; @@ -28,9 +138,11 @@ import com.google.gson.JsonParser; import google.registry.config.RegistryConfig.Config; import google.registry.request.Action.GkeService; -import jakarta.inject.Inject; // <-- CORRECTED IMPORT +import jakarta.inject.Inject; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Map; /** Command to manually perform an authenticated RDAP query. */ @Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") @@ -38,11 +150,25 @@ public final class RdapQueryCommand implements CommandWithConnection { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private static final ImmutableSet VALID_TYPES = - ImmutableSet.of("domain", "registrar", "contact", "nameserver"); + private static final ImmutableSet LOOKUP_TYPES = + ImmutableSet.of("domain", "nameserver", "entity"); + private static final ImmutableSet SEARCH_TYPES = + ImmutableSet.of("domains", "nameservers", "entities"); + + @Parameter( + description = + "The RDAP path segments. For lookups: ' ' (e.g., 'domain gustav.dev'). " + + "For searches: '' followed by '--params' (e.g., 'domains').", + required = true) + private List mainParameters = new ArrayList<>(); - @Parameter(description = "The object type and query term.", required = true) - private List mainParameters; + @Parameter( + names = "--params", + description = + "Optional search parameters in key=value format, comma-separated. " + + "E.g., '--params name=gusta*.dev,nsLdhName=ns1.example.com'. " + + "Only used with search types.") + private List params = new ArrayList<>(); // JCommander will populate this List private ServiceConnection defaultConnection; @@ -55,25 +181,58 @@ public void setConnection(ServiceConnection connection) { this.defaultConnection = connection; } + // Removed KeyValueValidator class, validation will be done in run() + @Override public void run() { - checkArgument( - mainParameters != null && mainParameters.size() == 2, - "Usage: nomulus rdap_query \n" - + " must be one of " - + VALID_TYPES); + checkArgument(!mainParameters.isEmpty(), "Missing RDAP path argument."); + + String path; + ImmutableMap queryParams = ImmutableMap.of(); - String type = Ascii.toLowerCase(mainParameters.get(0)); - checkArgument( - VALID_TYPES.contains(type), - "Invalid object type '%s'. Must be one of %s", - type, - VALID_TYPES); + String firstParam = Ascii.toLowerCase(mainParameters.get(0)); - String name = mainParameters.get(1); - String path = String.format("/rdap/%s/%s", type, name); + if (SEARCH_TYPES.contains(firstParam)) { + // Handle search: /rdap/{search_type}?{params} + checkArgument( + mainParameters.size() == 1, + "Search types like '%s' do not accept additional positional arguments.", + firstParam); + path = "/rdap/" + firstParam; + if (!params.isEmpty()) { + queryParams = + params.stream() + .map( + p -> { + List parts = Splitter.on('=').limit(2).splitToList(p); + checkArgument(parts.size() == 2, "Invalid parameter format: %s", p); + return parts; + }) + .collect(toImmutableMap(parts -> parts.get(0), parts -> parts.get(1))); + } else { + logger.atWarning().log( + "Performing an RDAP search for '%s' without any query parameters.", firstParam); + } + } else if (LOOKUP_TYPES.contains(firstParam)) { + // Handle lookup: /rdap/{type}/{name} + checkArgument( + mainParameters.size() == 2, + "Lookup type '%s' requires exactly one query term.", + firstParam); + checkArgument(params.isEmpty(), "Lookup queries do not accept --params."); + String type = firstParam; + String name = mainParameters.get(1); + path = String.format("/rdap/%s/%s", type, name); + } else { + throw new IllegalArgumentException( + "Usage: nomulus rdap_query OR nomulus rdap_query --params [,...]\n" + + " Lookup types: " + + LOOKUP_TYPES + + "\n Search types: " + + SEARCH_TYPES); + } - logger.atInfo().log("Starting RDAP query for path: %s", path); + logger.atInfo().log("Starting RDAP query for path: %s with params: %s", path, queryParams); try { if (defaultConnection == null) { @@ -82,7 +241,7 @@ public void run() { ServiceConnection pubapiConnection = defaultConnection.withService(GkeService.PUBAPI, useCanary); - String rdapResponse = pubapiConnection.sendGetRequest(path, ImmutableMap.of()); + String rdapResponse = pubapiConnection.sendGetRequest(path, queryParams); JsonElement rdapJson = JsonParser.parseString(rdapResponse); Gson gson = new GsonBuilder().setPrettyPrinting().create(); From 87e025fc44905a7320fd2e3a88cfa3db9f1d171c Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Thu, 13 Nov 2025 11:03:09 +0000 Subject: [PATCH 03/11] feat: Implement rdap_query command --- .../registry/tools/RdapQueryCommand.java | 98 +------------------ 1 file changed, 1 insertion(+), 97 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index e4a4772e148..3b649b718e5 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -1,100 +1,3 @@ -// // Copyright 2024 The Nomulus Authors. 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. -// -// package google.registry.tools; -// -// import static com.google.common.base.Preconditions.checkArgument; -// -// import com.beust.jcommander.Parameter; -// import com.beust.jcommander.Parameters; -// import com.google.common.base.Ascii; -// import com.google.common.collect.ImmutableMap; -// import com.google.common.collect.ImmutableSet; -// import com.google.common.flogger.GoogleLogger; -// import com.google.gson.Gson; -// import com.google.gson.GsonBuilder; -// import com.google.gson.JsonElement; -// import com.google.gson.JsonParser; -// import google.registry.config.RegistryConfig.Config; -// import google.registry.request.Action.GkeService; -// import jakarta.inject.Inject; // <-- CORRECTED IMPORT -// import java.io.IOException; -// import java.util.List; -// -// /** Command to manually perform an authenticated RDAP query. */ -// @Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") -// public final class RdapQueryCommand implements CommandWithConnection { -// -// private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); -// -// private static final ImmutableSet VALID_TYPES = -// ImmutableSet.of("domain", "registrar", "contact", "nameserver"); -// -// @Parameter(description = "The object type and query term.", required = true) -// private List mainParameters; -// -// private ServiceConnection defaultConnection; -// -// @Inject -// @Config("useCanary") -// boolean useCanary; -// -// @Override -// public void setConnection(ServiceConnection connection) { -// this.defaultConnection = connection; -// } -// -// @Override -// public void run() { -// checkArgument( -// mainParameters != null && mainParameters.size() == 2, -// "Usage: nomulus rdap_query \n" -// + " must be one of " -// + VALID_TYPES); -// -// String type = Ascii.toLowerCase(mainParameters.get(0)); -// checkArgument( -// VALID_TYPES.contains(type), -// "Invalid object type '%s'. Must be one of %s", -// type, -// VALID_TYPES); -// -// String name = mainParameters.get(1); -// String path = String.format("/rdap/%s/%s", type, name); -// -// logger.atInfo().log("Starting RDAP query for path: %s", path); -// -// try { -// if (defaultConnection == null) { -// throw new IllegalStateException("ServiceConnection was not set by RegistryCli."); -// } -// ServiceConnection pubapiConnection = -// defaultConnection.withService(GkeService.PUBAPI, useCanary); -// -// String rdapResponse = pubapiConnection.sendGetRequest(path, ImmutableMap.of()); -// JsonElement rdapJson = JsonParser.parseString(rdapResponse); -// -// Gson gson = new GsonBuilder().setPrettyPrinting().create(); -// System.out.println(gson.toJson(rdapJson)); -// -// logger.atInfo().log("Successfully completed RDAP query for path: %s", path); -// } catch (IOException e) { -// logger.atSevere().withCause(e).log("Request failed for path: %s", path); -// System.err.println("Request failed for " + path + ": " + e.getMessage()); -// } -// } -// } // Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -125,6 +28,7 @@ // In google.registry.tools.RdapQueryCommand.java // ... +package google.registry.tools; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Ascii; From a8b5ef88855587bc447ab3b645d9ecf8c29ece8c Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Thu, 13 Nov 2025 11:18:56 +0000 Subject: [PATCH 04/11] feat: Implement rdap_query command --- core/src/main/java/google/registry/tools/RdapQueryCommand.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index 3b649b718e5..3035df97233 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -29,6 +29,9 @@ // In google.registry.tools.RdapQueryCommand.java // ... package google.registry.tools; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Ascii; From 59648d75398819a5a7a9079e85d8fe08be8e8bde Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Thu, 13 Nov 2025 18:42:51 +0000 Subject: [PATCH 05/11] feat: Implement rdap_query command --- core/src/main/java/google/registry/tools/RdapQueryCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index 3035df97233..249ccb2506b 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -60,7 +60,7 @@ public final class RdapQueryCommand implements CommandWithConnection { private static final ImmutableSet LOOKUP_TYPES = ImmutableSet.of("domain", "nameserver", "entity"); private static final ImmutableSet SEARCH_TYPES = - ImmutableSet.of("domains", "nameservers", "entities"); + ImmutableSet.of("domains", "nameservers", "entities", "autnum"); @Parameter( description = From 8ed4e0d59c0b6757f57f4b7f4e4c826b454febe7 Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Thu, 13 Nov 2025 19:35:07 +0000 Subject: [PATCH 06/11] feat: Implement rdap_query command --- .../src/main/java/google/registry/tools/RdapQueryCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index 249ccb2506b..cd465f6b0d3 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -58,9 +58,9 @@ public final class RdapQueryCommand implements CommandWithConnection { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); private static final ImmutableSet LOOKUP_TYPES = - ImmutableSet.of("domain", "nameserver", "entity"); + ImmutableSet.of("domain", "nameserver", "entity", "autnum"); private static final ImmutableSet SEARCH_TYPES = - ImmutableSet.of("domains", "nameservers", "entities", "autnum"); + ImmutableSet.of("domains", "nameservers", "entities"); @Parameter( description = From 0e2c69e6e10986a03752f091d06a4bb6e0494104 Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Thu, 13 Nov 2025 20:21:40 +0000 Subject: [PATCH 07/11] feat: Implement rdap_query command --- .../registry/tools/RdapQueryCommand.java | 105 ++++-------------- 1 file changed, 24 insertions(+), 81 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index cd465f6b0d3..a9472104351 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -12,32 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Copyright 2024 The Nomulus Authors. 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. - -// In google.registry.tools.RdapQueryCommand.java -// ... package google.registry.tools; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableMap.toImmutableMap; + import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; -import com.google.common.base.Ascii; -import com.google.common.base.Splitter; // Keep Splitter +import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.flogger.GoogleLogger; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -51,31 +34,22 @@ import java.util.List; import java.util.Map; -/** Command to manually perform an authenticated RDAP query. */ +/** Command to manually perform an authenticated RDAP query for any path. */ @Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") public final class RdapQueryCommand implements CommandWithConnection { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private static final ImmutableSet LOOKUP_TYPES = - ImmutableSet.of("domain", "nameserver", "entity", "autnum"); - private static final ImmutableSet SEARCH_TYPES = - ImmutableSet.of("domains", "nameservers", "entities"); - @Parameter( - description = - "The RDAP path segments. For lookups: ' ' (e.g., 'domain gustav.dev'). " - + "For searches: '' followed by '--params' (e.g., 'domains').", + description = "The ordered RDAP path segments that form the path (e.g., 'domain foo.dev').", required = true) private List mainParameters = new ArrayList<>(); @Parameter( names = "--params", - description = - "Optional search parameters in key=value format, comma-separated. " - + "E.g., '--params name=gusta*.dev,nsLdhName=ns1.example.com'. " - + "Only used with search types.") - private List params = new ArrayList<>(); // JCommander will populate this List + description = "Optional search parameters in key=value format (e.g., 'name=example*.com').", + variableArity = true) + private List params = new ArrayList<>(); private ServiceConnection defaultConnection; @@ -88,56 +62,24 @@ public void setConnection(ServiceConnection connection) { this.defaultConnection = connection; } - // Removed KeyValueValidator class, validation will be done in run() - @Override public void run() { - checkArgument(!mainParameters.isEmpty(), "Missing RDAP path argument."); - - String path; - ImmutableMap queryParams = ImmutableMap.of(); - - String firstParam = Ascii.toLowerCase(mainParameters.get(0)); - - if (SEARCH_TYPES.contains(firstParam)) { - // Handle search: /rdap/{search_type}?{params} - checkArgument( - mainParameters.size() == 1, - "Search types like '%s' do not accept additional positional arguments.", - firstParam); - path = "/rdap/" + firstParam; - if (!params.isEmpty()) { - queryParams = - params.stream() - .map( - p -> { - List parts = Splitter.on('=').limit(2).splitToList(p); - checkArgument(parts.size() == 2, "Invalid parameter format: %s", p); - return parts; - }) - .collect(toImmutableMap(parts -> parts.get(0), parts -> parts.get(1))); - } else { - logger.atWarning().log( - "Performing an RDAP search for '%s' without any query parameters.", firstParam); - } - } else if (LOOKUP_TYPES.contains(firstParam)) { - // Handle lookup: /rdap/{type}/{name} - checkArgument( - mainParameters.size() == 2, - "Lookup type '%s' requires exactly one query term.", - firstParam); - checkArgument(params.isEmpty(), "Lookup queries do not accept --params."); - String type = firstParam; - String name = mainParameters.get(1); - path = String.format("/rdap/%s/%s", type, name); - } else { - throw new IllegalArgumentException( - "Usage: nomulus rdap_query OR nomulus rdap_query --params [,...]\n" - + " Lookup types: " - + LOOKUP_TYPES - + "\n Search types: " - + SEARCH_TYPES); - } + checkArgument(!mainParameters.isEmpty(), "Missing RDAP path segments."); + + // Universally construct the path from all main parameters. + // E.g., ["domain", "foo.dev"] becomes "/rdap/domain/foo.dev" + String path = "/rdap/" + String.join("/", mainParameters); + + // Universally parse all optional --params into a map. + ImmutableMap queryParams = + params.stream() + .map( + p -> { + List parts = Splitter.on('=').limit(2).splitToList(p); + checkArgument(parts.size() == 2, "Invalid parameter format: %s", p); + return parts; + }) + .collect(toImmutableMap(parts -> parts.get(0), parts -> parts.get(1))); logger.atInfo().log("Starting RDAP query for path: %s with params: %s", path, queryParams); @@ -145,6 +87,7 @@ public void run() { if (defaultConnection == null) { throw new IllegalStateException("ServiceConnection was not set by RegistryCli."); } + // This is the core fix: always create a new connection targeting the PUBAPI service. ServiceConnection pubapiConnection = defaultConnection.withService(GkeService.PUBAPI, useCanary); From 97e3247e77a1ee4a36e71c41b29d49b2c2e5fe89 Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Fri, 14 Nov 2025 07:43:49 +0000 Subject: [PATCH 08/11] feat: Implement rdap_query command --- .../registry/tools/RdapQueryCommand.java | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index a9472104351..bee3d49e14d 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -20,11 +20,13 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Splitter; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.flogger.GoogleLogger; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; import google.registry.config.RegistryConfig.Config; import google.registry.request.Action.GkeService; @@ -32,7 +34,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** Command to manually perform an authenticated RDAP query for any path. */ @Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") @@ -66,11 +67,8 @@ public void setConnection(ServiceConnection connection) { public void run() { checkArgument(!mainParameters.isEmpty(), "Missing RDAP path segments."); - // Universally construct the path from all main parameters. - // E.g., ["domain", "foo.dev"] becomes "/rdap/domain/foo.dev" String path = "/rdap/" + String.join("/", mainParameters); - // Universally parse all optional --params into a map. ImmutableMap queryParams = params.stream() .map( @@ -87,7 +85,6 @@ public void run() { if (defaultConnection == null) { throw new IllegalStateException("ServiceConnection was not set by RegistryCli."); } - // This is the core fix: always create a new connection targeting the PUBAPI service. ServiceConnection pubapiConnection = defaultConnection.withService(GkeService.PUBAPI, useCanary); @@ -100,7 +97,67 @@ public void run() { logger.atInfo().log("Successfully completed RDAP query for path: %s", path); } catch (IOException e) { logger.atSevere().withCause(e).log("Request failed for path: %s", path); - System.err.println("Request failed for " + path + ": " + e.getMessage()); + String errorMessage = e.getMessage(); + String userFriendlyError = "Request failed for " + path + ": " + errorMessage; + + try { + int jsonStartIndex = errorMessage != null ? errorMessage.indexOf('{') : -1; + if (jsonStartIndex != -1) { + String jsonString = errorMessage.substring(jsonStartIndex); + JsonElement errorJsonElement = JsonParser.parseString(jsonString); + + if (errorJsonElement.isJsonObject()) { + JsonObject errorObj = errorJsonElement.getAsJsonObject(); + String title = errorObj.has("title") ? errorObj.get("title").getAsString() : "Error"; + int errorCode = errorObj.has("errorCode") ? errorObj.get("errorCode").getAsInt() : -1; + String description = ""; + if (errorObj.has("description")) { + JsonElement descElement = errorObj.get("description"); + if (descElement.isJsonArray()) { + StringBuilder sb = new StringBuilder(); + for (JsonElement element : descElement.getAsJsonArray()) { + if (sb.length() > 0) { + sb.append("\n "); + } + sb.append(element.getAsString()); + } + description = sb.toString(); + } else if (descElement.isJsonPrimitive()) { + description = descElement.getAsString(); + } + } + + StringBuilder improvedError = new StringBuilder(); + improvedError.append("RDAP Request Failed (Code ").append(errorCode).append("): "); + improvedError.append(title); + if (!Strings.isNullOrEmpty(description)) { + improvedError.append("\n Description: ").append(description); + } + userFriendlyError = improvedError.toString(); + } + } else { + if (errorMessage != null) { + if (errorMessage.contains("501 Not Implemented")) { + userFriendlyError = + "RDAP Request Failed (Code 501): Not Implemented\n" + + " Description: The query for '" + + path + + "' was understood, but is not implemented by this registry.\n" + + " This is expected for 'ip' and 'autnum' queries, as this is a domain name registry."; + } else if (errorMessage.contains("404 Not Found")) { + userFriendlyError = + "RDAP Request Failed (Code 404): Not Found\n" + + " Description: The resource at path '" + + path + + "' does not exist."; + } + } + } + } catch (Exception jsonEx) { + logger.atWarning().withCause(jsonEx).log("Failed to parse error response as JSON."); + userFriendlyError = "Request failed for " + path + ": " + errorMessage; + } + System.err.println(userFriendlyError); } } } From 3454699a3e904e58e36cd1ef3cbf95314f219612 Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Fri, 14 Nov 2025 08:06:36 +0000 Subject: [PATCH 09/11] feat: Implement rdap_query command --- .../registry/tools/RdapQueryCommand.java | 101 +++++++++++------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index bee3d49e14d..4eb66e834d0 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -96,67 +96,88 @@ public void run() { logger.atInfo().log("Successfully completed RDAP query for path: %s", path); } catch (IOException e) { + // Always log the full exception for backend records. logger.atSevere().withCause(e).log("Request failed for path: %s", path); + String errorMessage = e.getMessage(); - String userFriendlyError = "Request failed for " + path + ": " + errorMessage; - - try { - int jsonStartIndex = errorMessage != null ? errorMessage.indexOf('{') : -1; - if (jsonStartIndex != -1) { - String jsonString = errorMessage.substring(jsonStartIndex); - JsonElement errorJsonElement = JsonParser.parseString(jsonString); - - if (errorJsonElement.isJsonObject()) { - JsonObject errorObj = errorJsonElement.getAsJsonObject(); - String title = errorObj.has("title") ? errorObj.get("title").getAsString() : "Error"; - int errorCode = errorObj.has("errorCode") ? errorObj.get("errorCode").getAsInt() : -1; - String description = ""; - if (errorObj.has("description")) { - JsonElement descElement = errorObj.get("description"); - if (descElement.isJsonArray()) { - StringBuilder sb = new StringBuilder(); - for (JsonElement element : descElement.getAsJsonArray()) { - if (sb.length() > 0) { - sb.append("\n "); + String userFriendlyError = "RDAP Request failed for " + path + "."; + + if (errorMessage != null) { + try { + int jsonStartIndex = errorMessage.indexOf('{'); + if (jsonStartIndex != -1) { + String jsonString = errorMessage.substring(jsonStartIndex); + JsonElement errorJsonElement = JsonParser.parseString(jsonString); + + if (errorJsonElement.isJsonObject()) { + JsonObject errorObj = errorJsonElement.getAsJsonObject(); + String title = errorObj.has("title") ? errorObj.get("title").getAsString() : "Error"; + int errorCode = errorObj.has("errorCode") ? errorObj.get("errorCode").getAsInt() : -1; + String description = ""; + if (errorObj.has("description")) { + JsonElement descElement = errorObj.get("description"); + if (descElement.isJsonArray()) { + StringBuilder sb = new StringBuilder(); + for (JsonElement element : descElement.getAsJsonArray()) { + if (sb.length() > 0) sb.append("\n "); + sb.append(element.getAsString()); } - sb.append(element.getAsString()); + description = sb.toString(); + } else if (descElement.isJsonPrimitive()) { + description = descElement.getAsString(); } - description = sb.toString(); - } else if (descElement.isJsonPrimitive()) { - description = descElement.getAsString(); } - } - StringBuilder improvedError = new StringBuilder(); - improvedError.append("RDAP Request Failed (Code ").append(errorCode).append("): "); - improvedError.append(title); - if (!Strings.isNullOrEmpty(description)) { - improvedError.append("\n Description: ").append(description); + StringBuilder improvedError = new StringBuilder(); + improvedError.append("RDAP Request Failed (Code ").append(errorCode).append("): "); + improvedError.append(title); + if (!Strings.isNullOrEmpty(description)) { + improvedError.append("\n Description: ").append(description); + } + userFriendlyError = improvedError.toString(); } - userFriendlyError = improvedError.toString(); - } - } else { - if (errorMessage != null) { - if (errorMessage.contains("501 Not Implemented")) { + } else { + // Fallback if no JSON, try to extract HTTP status from the message + if (errorMessage.contains(": 501 Not Implemented")) { userFriendlyError = "RDAP Request Failed (Code 501): Not Implemented\n" + " Description: The query for '" + path + "' was understood, but is not implemented by this registry.\n" + " This is expected for 'ip' and 'autnum' queries, as this is a domain name registry."; - } else if (errorMessage.contains("404 Not Found")) { + } else if (errorMessage.contains(": 404 Not Found")) { userFriendlyError = "RDAP Request Failed (Code 404): Not Found\n" + " Description: The resource at path '" + path - + "' does not exist."; + + "' does not exist or no results matched the query."; + } else if (errorMessage.contains(": 422 Unprocessable Entity")) { + userFriendlyError = + "RDAP Request Failed (Code 422): Unprocessable Entity\n" + + " Description: The server understood the request, but cannot process the included entities."; + } else if (errorMessage.contains(": 500 Internal Server Error")) { + userFriendlyError = + "RDAP Request Failed (Code 500): Internal Server Error\n" + + " Description: An unexpected error occurred on the server. Check server logs for details."; + } else if (errorMessage.contains(": 503 Service Unavailable")) { + userFriendlyError = + "RDAP Request Failed (Code 503): Service Unavailable\n" + + " Description: The RDAP service is temporarily unavailable. Please try again later."; + } else { + userFriendlyError = + "Request failed for " + + path + + ": " + + errorMessage.substring(0, Math.min(errorMessage.length(), 150)); } } + } catch (Exception jsonEx) { + // If JSON parsing fails, fall back to a clean message. + logger.atWarning().withCause(jsonEx).log("Failed to parse error response as JSON, showing raw error."); + userFriendlyError = "Request failed for " + path + ": " + errorMessage; } - } catch (Exception jsonEx) { - logger.atWarning().withCause(jsonEx).log("Failed to parse error response as JSON."); - userFriendlyError = "Request failed for " + path + ": " + errorMessage; } + // ONLY print the userFriendlyError to System.err System.err.println(userFriendlyError); } } From ba641bb7a2b66d9294e3c6a703ea6af9071abdab Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Fri, 14 Nov 2025 08:26:26 +0000 Subject: [PATCH 10/11] feat: Implement rdap_query command --- .../registry/tools/RdapQueryCommand.java | 129 ++++++++++++++---- 1 file changed, 101 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index 4eb66e834d0..3ef20f70a35 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -19,12 +19,15 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.google.common.base.Ascii; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.flogger.GoogleLogger; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -34,6 +37,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** Command to manually perform an authenticated RDAP query for any path. */ @Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") @@ -41,6 +45,11 @@ public final class RdapQueryCommand implements CommandWithConnection { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private static final ImmutableSet LOOKUP_TYPES = + ImmutableSet.of("domain", "nameserver", "entity", "autnum", "ip", "help"); + private static final ImmutableSet SEARCH_TYPES = + ImmutableSet.of("domains", "nameservers", "entities"); + @Parameter( description = "The ordered RDAP path segments that form the path (e.g., 'domain foo.dev').", required = true) @@ -67,17 +76,51 @@ public void setConnection(ServiceConnection connection) { public void run() { checkArgument(!mainParameters.isEmpty(), "Missing RDAP path segments."); - String path = "/rdap/" + String.join("/", mainParameters); + String path; + ImmutableMap queryParams = ImmutableMap.of(); - ImmutableMap queryParams = - params.stream() - .map( - p -> { - List parts = Splitter.on('=').limit(2).splitToList(p); - checkArgument(parts.size() == 2, "Invalid parameter format: %s", p); - return parts; - }) - .collect(toImmutableMap(parts -> parts.get(0), parts -> parts.get(1))); + String firstParam = Ascii.toLowerCase(mainParameters.get(0)); + + if (SEARCH_TYPES.contains(firstParam)) { + checkArgument( + mainParameters.size() == 1, + "Search types like '%s' do not accept additional positional arguments.", + firstParam); + path = "/rdap/" + firstParam; + if (!params.isEmpty()) { + queryParams = + params.stream() + .map( + p -> { + List parts = Splitter.on('=').limit(2).splitToList(p); + checkArgument(parts.size() == 2, "Invalid parameter format: %s", p); + return parts; + }) + .collect(toImmutableMap(parts -> parts.get(0), parts -> parts.get(1))); + } else { + logger.atWarning().log( + "Performing an RDAP search for '%s' without any query parameters.", firstParam); + } + } else if (LOOKUP_TYPES.contains(firstParam)) { + checkArgument(params.isEmpty(), "Lookup queries do not accept --params."); + String type = firstParam; + String name = ""; + if (mainParameters.size() > 1) { + name = mainParameters.get(1); + checkArgument( + mainParameters.size() == 2, "Lookup type '%s' requires exactly one query term.", type); + } else if (!type.equals("help")) { + throw new IllegalArgumentException(String.format("Lookup type '%s' requires a query term.", type)); + } + path = String.format("/rdap/%s%s", type, name.isEmpty() ? "" : "/" + name); + } else { + throw new IllegalArgumentException( + "Usage: nomulus rdap_query OR nomulus rdap_query --params [,...]\n" + + " Lookup types: " + + LOOKUP_TYPES + + "\n Search types: " + + SEARCH_TYPES); + } logger.atInfo().log("Starting RDAP query for path: %s with params: %s", path, queryParams); @@ -91,8 +134,8 @@ public void run() { String rdapResponse = pubapiConnection.sendGetRequest(path, queryParams); JsonElement rdapJson = JsonParser.parseString(rdapResponse); - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - System.out.println(gson.toJson(rdapJson)); + // This now calls the custom formatter instead of Gson. + System.out.println(formatJsonElement(rdapJson, "")); logger.atInfo().log("Successfully completed RDAP query for path: %s", path); } catch (IOException e) { @@ -100,7 +143,7 @@ public void run() { logger.atSevere().withCause(e).log("Request failed for path: %s", path); String errorMessage = e.getMessage(); - String userFriendlyError = "RDAP Request failed for " + path + "."; + String userFriendlyError; if (errorMessage != null) { try { @@ -135,22 +178,18 @@ public void run() { improvedError.append("\n Description: ").append(description); } userFriendlyError = improvedError.toString(); + } else { + userFriendlyError = "Request failed for " + path + ": " + errorMessage; } } else { - // Fallback if no JSON, try to extract HTTP status from the message if (errorMessage.contains(": 501 Not Implemented")) { userFriendlyError = "RDAP Request Failed (Code 501): Not Implemented\n" - + " Description: The query for '" - + path - + "' was understood, but is not implemented by this registry.\n" - + " This is expected for 'ip' and 'autnum' queries, as this is a domain name registry."; + + " Description: The query for '" + path + "' was understood, but is not implemented by this registry."; } else if (errorMessage.contains(": 404 Not Found")) { userFriendlyError = "RDAP Request Failed (Code 404): Not Found\n" - + " Description: The resource at path '" - + path - + "' does not exist or no results matched the query."; + + " Description: The resource at path '" + path + "' does not exist or no results matched the query."; } else if (errorMessage.contains(": 422 Unprocessable Entity")) { userFriendlyError = "RDAP Request Failed (Code 422): Unprocessable Entity\n" @@ -164,21 +203,55 @@ public void run() { "RDAP Request Failed (Code 503): Service Unavailable\n" + " Description: The RDAP service is temporarily unavailable. Please try again later."; } else { - userFriendlyError = - "Request failed for " - + path - + ": " - + errorMessage.substring(0, Math.min(errorMessage.length(), 150)); + userFriendlyError = "Request failed for " + path + ": " + errorMessage.substring(0, Math.min(errorMessage.length(), 150)); } } } catch (Exception jsonEx) { - // If JSON parsing fails, fall back to a clean message. logger.atWarning().withCause(jsonEx).log("Failed to parse error response as JSON, showing raw error."); userFriendlyError = "Request failed for " + path + ": " + errorMessage; } + } else { + userFriendlyError = "Request failed for " + path + ": " + "No error message available."; } - // ONLY print the userFriendlyError to System.err System.err.println(userFriendlyError); } } + + /** Recursively formats a JsonElement into a human-readable, indented, key-value string. */ + private String formatJsonElement(JsonElement element, String indent) { + StringBuilder sb = new StringBuilder(); + if (element == null || element.isJsonNull()) { + // Omit nulls for cleaner output + } else if (element.isJsonObject()) { + JsonObject obj = element.getAsJsonObject(); + obj.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) // Sort keys for consistent output + .forEach( + entry -> { + if (!entry.getValue().isJsonNull()) { + sb.append(indent).append(entry.getKey()).append(":"); + JsonElement child = entry.getValue(); + if (child.isJsonPrimitive()) { + sb.append(" ").append(child.getAsString()).append("\n"); + } else { + sb.append("\n").append(formatJsonElement(child, indent + " ")); + } + } + }); + } else if (element.isJsonArray()) { + JsonArray array = element.getAsJsonArray(); + for (JsonElement item : array) { + if (item.isJsonPrimitive()) { + sb.append(indent).append("- ").append(item.getAsString()).append("\n"); + } else if (item.isJsonObject()) { + sb.append(indent).append("-\n").append(formatJsonElement(item, indent + " ")); + } else if (item.isJsonArray()) { + sb.append(indent).append("-\n").append(formatJsonElement(item, indent + " ")); + } + } + } else if (element.isJsonPrimitive()) { + sb.append(indent).append(element.getAsString()).append("\n"); // Handle top-level or standalone primitives + } + return sb.toString(); + } } From 4cf0a999b7d50e853a58d98b10126801c52c9bb4 Mon Sep 17 00:00:00 2001 From: Harshita Sharma Date: Fri, 14 Nov 2025 08:38:11 +0000 Subject: [PATCH 11/11] feat: Implement rdap_query command --- .../registry/tools/RdapQueryCommand.java | 102 +++++------------- 1 file changed, 28 insertions(+), 74 deletions(-) diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java index 3ef20f70a35..a62e0d8e724 100644 --- a/core/src/main/java/google/registry/tools/RdapQueryCommand.java +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -19,14 +19,10 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; -import com.google.common.base.Ascii; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.flogger.GoogleLogger; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -45,11 +41,6 @@ public final class RdapQueryCommand implements CommandWithConnection { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private static final ImmutableSet LOOKUP_TYPES = - ImmutableSet.of("domain", "nameserver", "entity", "autnum", "ip", "help"); - private static final ImmutableSet SEARCH_TYPES = - ImmutableSet.of("domains", "nameservers", "entities"); - @Parameter( description = "The ordered RDAP path segments that form the path (e.g., 'domain foo.dev').", required = true) @@ -76,51 +67,17 @@ public void setConnection(ServiceConnection connection) { public void run() { checkArgument(!mainParameters.isEmpty(), "Missing RDAP path segments."); - String path; - ImmutableMap queryParams = ImmutableMap.of(); - - String firstParam = Ascii.toLowerCase(mainParameters.get(0)); + String path = "/rdap/" + String.join("/", mainParameters); - if (SEARCH_TYPES.contains(firstParam)) { - checkArgument( - mainParameters.size() == 1, - "Search types like '%s' do not accept additional positional arguments.", - firstParam); - path = "/rdap/" + firstParam; - if (!params.isEmpty()) { - queryParams = - params.stream() - .map( - p -> { - List parts = Splitter.on('=').limit(2).splitToList(p); - checkArgument(parts.size() == 2, "Invalid parameter format: %s", p); - return parts; - }) - .collect(toImmutableMap(parts -> parts.get(0), parts -> parts.get(1))); - } else { - logger.atWarning().log( - "Performing an RDAP search for '%s' without any query parameters.", firstParam); - } - } else if (LOOKUP_TYPES.contains(firstParam)) { - checkArgument(params.isEmpty(), "Lookup queries do not accept --params."); - String type = firstParam; - String name = ""; - if (mainParameters.size() > 1) { - name = mainParameters.get(1); - checkArgument( - mainParameters.size() == 2, "Lookup type '%s' requires exactly one query term.", type); - } else if (!type.equals("help")) { - throw new IllegalArgumentException(String.format("Lookup type '%s' requires a query term.", type)); - } - path = String.format("/rdap/%s%s", type, name.isEmpty() ? "" : "/" + name); - } else { - throw new IllegalArgumentException( - "Usage: nomulus rdap_query OR nomulus rdap_query --params [,...]\n" - + " Lookup types: " - + LOOKUP_TYPES - + "\n Search types: " - + SEARCH_TYPES); - } + ImmutableMap queryParams = + params.stream() + .map( + p -> { + List parts = Splitter.on('=').limit(2).splitToList(p); + checkArgument(parts.size() == 2, "Invalid parameter format: %s", p); + return parts; + }) + .collect(toImmutableMap(parts -> parts.get(0), parts -> parts.get(1))); logger.atInfo().log("Starting RDAP query for path: %s with params: %s", path, queryParams); @@ -134,13 +91,12 @@ public void run() { String rdapResponse = pubapiConnection.sendGetRequest(path, queryParams); JsonElement rdapJson = JsonParser.parseString(rdapResponse); - // This now calls the custom formatter instead of Gson. System.out.println(formatJsonElement(rdapJson, "")); - logger.atInfo().log("Successfully completed RDAP query for path: %s", path); } catch (IOException e) { - // Always log the full exception for backend records. - logger.atSevere().withCause(e).log("Request failed for path: %s", path); + // Log the full exception for backend records, but without withCause(e) to + // prevent automatic stack trace printing to the console. + logger.atSevere().log("Request failed for path: %s: %s", path, e.getMessage()); String errorMessage = e.getMessage(); String userFriendlyError; @@ -182,28 +138,25 @@ public void run() { userFriendlyError = "Request failed for " + path + ": " + errorMessage; } } else { + // Fallback if no JSON, try to extract HTTP status from the message if (errorMessage.contains(": 501 Not Implemented")) { userFriendlyError = "RDAP Request Failed (Code 501): Not Implemented\n" - + " Description: The query for '" + path + "' was understood, but is not implemented by this registry."; + + " Description: The query for '" + + path + + "' was understood, but is not implemented by this registry."; } else if (errorMessage.contains(": 404 Not Found")) { userFriendlyError = "RDAP Request Failed (Code 404): Not Found\n" - + " Description: The resource at path '" + path + "' does not exist or no results matched the query."; - } else if (errorMessage.contains(": 422 Unprocessable Entity")) { - userFriendlyError = - "RDAP Request Failed (Code 422): Unprocessable Entity\n" - + " Description: The server understood the request, but cannot process the included entities."; - } else if (errorMessage.contains(": 500 Internal Server Error")) { - userFriendlyError = - "RDAP Request Failed (Code 500): Internal Server Error\n" - + " Description: An unexpected error occurred on the server. Check server logs for details."; - } else if (errorMessage.contains(": 503 Service Unavailable")) { - userFriendlyError = - "RDAP Request Failed (Code 503): Service Unavailable\n" - + " Description: The RDAP service is temporarily unavailable. Please try again later."; + + " Description: The resource at path '" + + path + + "' does not exist or no results matched the query."; } else { - userFriendlyError = "Request failed for " + path + ": " + errorMessage.substring(0, Math.min(errorMessage.length(), 150)); + userFriendlyError = + "Request failed for " + + path + + ": " + + errorMessage.substring(0, Math.min(errorMessage.length(), 150)); } } } catch (Exception jsonEx) { @@ -211,8 +164,9 @@ public void run() { userFriendlyError = "Request failed for " + path + ": " + errorMessage; } } else { - userFriendlyError = "Request failed for " + path + ": " + "No error message available."; + userFriendlyError = "Request failed for " + path + ": No error message available."; } + // ONLY print the userFriendlyError to System.err System.err.println(userFriendlyError); } } @@ -250,7 +204,7 @@ private String formatJsonElement(JsonElement element, String indent) { } } } else if (element.isJsonPrimitive()) { - sb.append(indent).append(element.getAsString()).append("\n"); // Handle top-level or standalone primitives + sb.append(indent).append(element.getAsString()).append("\n"); } return sb.toString(); }