2222import com .google .common .base .Splitter ;
2323import com .google .common .base .Strings ;
2424import com .google .common .collect .ImmutableMap ;
25- import com .google .common .flogger .GoogleLogger ;
2625import com .google .gson .JsonArray ;
2726import com .google .gson .JsonElement ;
2827import com .google .gson .JsonObject ;
3433import java .util .ArrayList ;
3534import java .util .List ;
3635import java .util .Map ;
36+ import java .util .Optional ;
3737
3838/** Command to manually perform an RDAP query for any path. */
39- @ Parameters (separators = " =" , commandDescription = "Manually perform an authenticated RDAP query" )
39+ @ Parameters (separators = " =" , commandDescription = "Manually perform an RDAP query" )
4040public final class RdapQueryCommand implements CommandWithConnection {
4141
42- private static final GoogleLogger logger = GoogleLogger .forEnclosingClass ();
43-
4442 @ Parameter (
4543 description = "The ordered RDAP path segments that form the path (e.g., 'domain foo.dev')." ,
4644 required = true )
@@ -65,21 +63,10 @@ public void setConnection(ServiceConnection connection) {
6563
6664 @ Override
6765 public void run () {
68- checkArgument (!mainParameters .isEmpty (), "Missing RDAP path segments." );
6966
7067 String path = "/rdap/" + String .join ("/" , mainParameters );
7168
72- ImmutableMap <String , String > queryParams =
73- params .stream ()
74- .map (
75- p -> {
76- List <String > parts = Splitter .on ('=' ).limit (2 ).splitToList (p );
77- checkArgument (parts .size () == 2 , "Invalid parameter format: %s" , p );
78- return parts ;
79- })
80- .collect (toImmutableMap (parts -> parts .get (0 ), parts -> parts .get (1 )));
81-
82- logger .atInfo ().log ("Starting RDAP query for path: %s with params: %s" , path , queryParams );
69+ ImmutableMap <String , String > queryParams = parseParams (params );
8370
8471 try {
8572 if (defaultConnection == null ) {
@@ -94,92 +81,135 @@ public void run() {
9481 System .out .println (formatJsonElement (rdapJson , "" ));
9582
9683 } catch (IOException e ) {
97- // Log the full exception for backend records, but without withCause(e) to
98- // prevent automatic stack trace printing to the console.
99- logger .atSevere ().log ("Request failed for path: %s: %s" , path , e .getMessage ());
100-
101- String errorMessage = e .getMessage ();
102- String userFriendlyError ;
103-
104- if (errorMessage != null ) {
105- try {
106- int jsonStartIndex = errorMessage .indexOf ('{' );
107- if (jsonStartIndex != -1 ) {
108- String jsonString = errorMessage .substring (jsonStartIndex );
109- JsonElement errorJsonElement = JsonParser .parseString (jsonString );
110-
111- if (errorJsonElement .isJsonObject ()) {
112- JsonObject errorObj = errorJsonElement .getAsJsonObject ();
113- String title = errorObj .has ("title" ) ? errorObj .get ("title" ).getAsString () : "Error" ;
114- int errorCode = errorObj .has ("errorCode" ) ? errorObj .get ("errorCode" ).getAsInt () : -1 ;
115- String description = "" ;
116- if (errorObj .has ("description" )) {
117- JsonElement descElement = errorObj .get ("description" );
118- if (descElement .isJsonArray ()) {
119- StringBuilder sb = new StringBuilder ();
120- for (JsonElement element : descElement .getAsJsonArray ()) {
121- if (sb .length () > 0 ) sb .append ("\n " );
122- sb .append (element .getAsString ());
123- }
124- description = sb .toString ();
125- } else if (descElement .isJsonPrimitive ()) {
126- description = descElement .getAsString ();
127- }
128- }
129-
130- StringBuilder improvedError = new StringBuilder ();
131- improvedError .append ("RDAP Request Failed (Code " ).append (errorCode ).append ("): " );
132- improvedError .append (title );
133- if (!Strings .isNullOrEmpty (description )) {
134- improvedError .append ("\n Description: " ).append (description );
135- }
136- userFriendlyError = improvedError .toString ();
137- } else {
138- userFriendlyError = "Request failed for " + path + ": " + errorMessage ;
139- }
140- } else {
141- // Fallback if no JSON, try to extract HTTP status from the message
142- if (errorMessage .contains (": 501 Not Implemented" )) {
143- userFriendlyError =
144- "RDAP Request Failed (Code 501): Not Implemented\n "
145- + " Description: The query for '"
146- + path
147- + "' was understood, but is not implemented by this registry." ;
148- } else if (errorMessage .contains (": 404 Not Found" )) {
149- userFriendlyError =
150- "RDAP Request Failed (Code 404): Not Found\n "
151- + " Description: The resource at path '"
152- + path
153- + "' does not exist or no results matched the query." ;
154- } else {
155- userFriendlyError =
156- "Request failed for "
157- + path
158- + ": "
159- + errorMessage .substring (0 , Math .min (errorMessage .length (), 150 ));
160- }
161- }
162- } catch (Exception jsonEx ) {
163- logger .atWarning ().withCause (jsonEx ).log ("Failed to parse error response as JSON, showing raw error." );
164- userFriendlyError = "Request failed for " + path + ": " + errorMessage ;
84+ handleIOException (path , e );
85+ }
86+ }
87+
88+ /** Parses the --params list into an ImmutableMap of query parameters. */
89+ private ImmutableMap <String , String > parseParams (List <String > paramsList ) {
90+ return paramsList .stream ()
91+ .map (
92+ p -> {
93+ List <String > parts = Splitter .on ('=' ).limit (2 ).splitToList (p );
94+ checkArgument (parts .size () == 2 , "Invalid parameter format: %s" , p );
95+ return parts ;
96+ })
97+ .collect (toImmutableMap (parts -> parts .get (0 ), parts -> parts .get (1 )));
98+ }
99+
100+ /** Handles and formats IOException, printing a user-friendly message to System.err. */
101+ private void handleIOException (String path , IOException e ) {
102+
103+ String errorMessage = e .getMessage ();
104+ String userFriendlyError = formatUserFriendlyError (path , errorMessage );
105+ System .err .println (userFriendlyError );
106+ }
107+
108+ /** Formats a user-friendly error message from the IOException details. */
109+ private String formatUserFriendlyError (String path , String errorMessage ) {
110+ if (errorMessage == null ) {
111+ return "Request failed for " + path + ": No error message available." ;
112+ }
113+
114+ Optional <JsonObject > errorJson = parseErrorJson (errorMessage );
115+
116+ if (errorJson .isPresent ()) {
117+ return formatJsonError (errorJson .get ());
118+ } else {
119+ return formatFallbackError (path , errorMessage );
120+ }
121+ }
122+
123+ /** Attempts to parse a JSON object from the IOException message. */
124+ private Optional <JsonObject > parseErrorJson (String errorMessage ) {
125+ try {
126+ int jsonStartIndex = errorMessage .indexOf ('{' );
127+ if (jsonStartIndex != -1 ) {
128+ String jsonString = errorMessage .substring (jsonStartIndex );
129+ JsonElement errorJsonElement = JsonParser .parseString (jsonString );
130+ if (errorJsonElement .isJsonObject ()) {
131+ return Optional .of (errorJsonElement .getAsJsonObject ());
165132 }
166- } else {
167- userFriendlyError = "Request failed for " + path + ": No error message available." ;
168133 }
169- // ONLY print the userFriendlyError to System.err
170- System .err .println (userFriendlyError );
134+ } catch (Exception jsonEx ) {
135+ }
136+ return Optional .empty ();
137+ }
138+
139+ /** Formats a user-friendly error string from a parsed JSON error object. */
140+ private String formatJsonError (JsonObject errorObj ) {
141+ String title = errorObj .has ("title" ) ? errorObj .get ("title" ).getAsString () : "Error" ;
142+ int errorCode = errorObj .has ("errorCode" ) ? errorObj .get ("errorCode" ).getAsInt () : -1 ;
143+ String description = parseJsonDescription (errorObj );
144+
145+ StringBuilder improvedError =
146+ new StringBuilder ()
147+ .append ("RDAP Request Failed (Code " )
148+ .append (errorCode )
149+ .append ("): " )
150+ .append (title );
151+ if (!Strings .isNullOrEmpty (description )) {
152+ improvedError .append ("\n Description: " ).append (description );
153+ }
154+ return improvedError .toString ();
155+ }
156+
157+ /** Extracts and formats the 'description' field from a JSON error object. */
158+ private String parseJsonDescription (JsonObject errorObj ) {
159+ if (!errorObj .has ("description" )) {
160+ return "" ;
161+ }
162+ JsonElement descElement = errorObj .get ("description" );
163+ if (descElement .isJsonArray ()) {
164+ StringBuilder sb = new StringBuilder ();
165+ for (JsonElement element : descElement .getAsJsonArray ()) {
166+ if (sb .length () > 0 ) sb .append ("\n " );
167+ sb .append (element .getAsString ());
168+ }
169+ return sb .toString ();
170+ } else if (descElement .isJsonPrimitive ()) {
171+ return descElement .getAsString ();
172+ }
173+ return "" ;
174+ }
175+
176+ /** Formats a fallback error message when JSON parsing fails. */
177+ private String formatFallbackError (String path , String errorMessage ) {
178+ if (errorMessage .contains (": 501 Not Implemented" )) {
179+ return "RDAP Request Failed (Code 501): Not Implemented\n "
180+ + " Description: The query for '"
181+ + path
182+ + "' was understood, but is not implemented by this registry." ;
183+ } else if (errorMessage .contains (": 404 Not Found" )) {
184+ return "RDAP Request Failed (Code 404): Not Found\n "
185+ + " Description: The resource at path '"
186+ + path
187+ + "' does not exist or no results matched the query." ;
188+ } else if (errorMessage .contains (": 422 Unprocessable Entity" )) {
189+ return "RDAP Request Failed (Code 422): Unprocessable Entity\n "
190+ + " Description: The server understood the request, but cannot process the"
191+ + " included entities." ;
192+ } else if (errorMessage .contains (": 500 Internal Server Error" )) {
193+ return "RDAP Request Failed (Code 500): Internal Server Error\n "
194+ + " Description: An unexpected error occurred on the server. Check server logs"
195+ + " for details." ;
196+ } else if (errorMessage .contains (": 503 Service Unavailable" )) {
197+ return "RDAP Request Failed (Code 503): Service Unavailable\n "
198+ + " Description: The RDAP service is temporarily unavailable. Please try again later." ;
199+ } else {
200+ String rawMessage = errorMessage .substring (0 , Math .min (errorMessage .length (), 150 ));
201+ return "Request failed for " + path + ": " + rawMessage ;
171202 }
172203 }
173204
174205 /** Recursively formats a JsonElement into a human-readable, indented, key-value string. */
175206 private String formatJsonElement (JsonElement element , String indent ) {
176207 StringBuilder sb = new StringBuilder ();
177208 if (element == null || element .isJsonNull ()) {
178- // Omit nulls for cleaner output
179209 } else if (element .isJsonObject ()) {
180210 JsonObject obj = element .getAsJsonObject ();
181211 obj .entrySet ().stream ()
182- .sorted (Map .Entry .comparingByKey ()) // Sort keys for consistent output
212+ .sorted (Map .Entry .comparingByKey ())
183213 .forEach (
184214 entry -> {
185215 if (!entry .getValue ().isJsonNull ()) {
@@ -208,4 +238,4 @@ private String formatJsonElement(JsonElement element, String indent) {
208238 }
209239 return sb .toString ();
210240 }
211- }
241+ }
0 commit comments