This is an automated email from the ASF dual-hosted git repository.

etudenhoefner pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new 1cb88a64f8 Core: Add LoadCredentialsResponse class/parser (#11339)
1cb88a64f8 is described below

commit 1cb88a64f8a065c87eb875cf08cfc70941a2fd05
Author: Eduard Tudenhoefner <[email protected]>
AuthorDate: Thu Oct 24 12:33:27 2024 +0200

    Core: Add LoadCredentialsResponse class/parser (#11339)
---
 .../org/apache/iceberg/rest/RESTSerializers.java   |  29 +++++-
 .../rest/responses/LoadCredentialsResponse.java    |  34 +++++++
 .../responses/LoadCredentialsResponseParser.java   |  77 ++++++++++++++
 .../rest/responses/LoadTableResponseParser.java    |   8 +-
 .../rest/responses/LoadViewResponseParser.java     |   8 +-
 .../TestLoadCredentialsResponseParser.java         | 112 +++++++++++++++++++++
 6 files changed, 253 insertions(+), 15 deletions(-)

diff --git a/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java 
b/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java
index 7f39d0bc1f..6671426986 100644
--- a/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java
+++ b/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java
@@ -59,7 +59,10 @@ import org.apache.iceberg.rest.responses.ConfigResponse;
 import org.apache.iceberg.rest.responses.ConfigResponseParser;
 import org.apache.iceberg.rest.responses.ErrorResponse;
 import org.apache.iceberg.rest.responses.ErrorResponseParser;
+import org.apache.iceberg.rest.responses.ImmutableLoadCredentialsResponse;
 import org.apache.iceberg.rest.responses.ImmutableLoadViewResponse;
+import org.apache.iceberg.rest.responses.LoadCredentialsResponse;
+import org.apache.iceberg.rest.responses.LoadCredentialsResponseParser;
 import org.apache.iceberg.rest.responses.LoadTableResponse;
 import org.apache.iceberg.rest.responses.LoadTableResponseParser;
 import org.apache.iceberg.rest.responses.LoadViewResponse;
@@ -119,7 +122,13 @@ public class RESTSerializers {
         .addSerializer(ConfigResponse.class, new ConfigResponseSerializer<>())
         .addDeserializer(ConfigResponse.class, new 
ConfigResponseDeserializer<>())
         .addSerializer(LoadTableResponse.class, new 
LoadTableResponseSerializer<>())
-        .addDeserializer(LoadTableResponse.class, new 
LoadTableResponseDeserializer<>());
+        .addDeserializer(LoadTableResponse.class, new 
LoadTableResponseDeserializer<>())
+        .addSerializer(LoadCredentialsResponse.class, new 
LoadCredentialsResponseSerializer<>())
+        .addSerializer(
+            ImmutableLoadCredentialsResponse.class, new 
LoadCredentialsResponseSerializer<>())
+        .addDeserializer(LoadCredentialsResponse.class, new 
LoadCredentialsResponseDeserializer<>())
+        .addDeserializer(
+            ImmutableLoadCredentialsResponse.class, new 
LoadCredentialsResponseDeserializer<>());
 
     mapper.registerModule(module);
   }
@@ -443,4 +452,22 @@ public class RESTSerializers {
       return (T) LoadTableResponseParser.fromJson(jsonNode);
     }
   }
+
+  static class LoadCredentialsResponseSerializer<T extends 
LoadCredentialsResponse>
+      extends JsonSerializer<T> {
+    @Override
+    public void serialize(T request, JsonGenerator gen, SerializerProvider 
serializers)
+        throws IOException {
+      LoadCredentialsResponseParser.toJson(request, gen);
+    }
+  }
+
+  static class LoadCredentialsResponseDeserializer<T extends 
LoadCredentialsResponse>
+      extends JsonDeserializer<T> {
+    @Override
+    public T deserialize(JsonParser p, DeserializationContext context) throws 
IOException {
+      JsonNode jsonNode = p.getCodec().readTree(p);
+      return (T) LoadCredentialsResponseParser.fromJson(jsonNode);
+    }
+  }
 }
diff --git 
a/core/src/main/java/org/apache/iceberg/rest/responses/LoadCredentialsResponse.java
 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadCredentialsResponse.java
new file mode 100644
index 0000000000..4109812910
--- /dev/null
+++ 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadCredentialsResponse.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.iceberg.rest.responses;
+
+import java.util.List;
+import org.apache.iceberg.rest.RESTResponse;
+import org.apache.iceberg.rest.credentials.Credential;
+import org.immutables.value.Value;
+
[email protected]
+public interface LoadCredentialsResponse extends RESTResponse {
+  List<Credential> credentials();
+
+  @Override
+  default void validate() {
+    // nothing to validate
+  }
+}
diff --git 
a/core/src/main/java/org/apache/iceberg/rest/responses/LoadCredentialsResponseParser.java
 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadCredentialsResponseParser.java
new file mode 100644
index 0000000000..9ee0b9c35e
--- /dev/null
+++ 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadCredentialsResponseParser.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.iceberg.rest.responses;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import java.io.IOException;
+import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
+import org.apache.iceberg.rest.credentials.Credential;
+import org.apache.iceberg.rest.credentials.CredentialParser;
+import org.apache.iceberg.util.JsonUtil;
+
+public class LoadCredentialsResponseParser {
+  private static final String STORAGE_CREDENTIALS = "storage-credentials";
+
+  private LoadCredentialsResponseParser() {}
+
+  public static String toJson(LoadCredentialsResponse response) {
+    return toJson(response, false);
+  }
+
+  public static String toJson(LoadCredentialsResponse response, boolean 
pretty) {
+    return JsonUtil.generate(gen -> toJson(response, gen), pretty);
+  }
+
+  public static void toJson(LoadCredentialsResponse response, JsonGenerator 
gen)
+      throws IOException {
+    Preconditions.checkArgument(null != response, "Invalid load credentials 
response: null");
+
+    gen.writeStartObject();
+
+    gen.writeArrayFieldStart(STORAGE_CREDENTIALS);
+    for (Credential credential : response.credentials()) {
+      CredentialParser.toJson(credential, gen);
+    }
+
+    gen.writeEndArray();
+
+    gen.writeEndObject();
+  }
+
+  public static LoadCredentialsResponse fromJson(String json) {
+    return JsonUtil.parse(json, LoadCredentialsResponseParser::fromJson);
+  }
+
+  public static LoadCredentialsResponse fromJson(JsonNode json) {
+    Preconditions.checkArgument(
+        null != json, "Cannot parse load credentials response from null 
object");
+
+    JsonNode credentials = JsonUtil.get(STORAGE_CREDENTIALS, json);
+    Preconditions.checkArgument(
+        credentials.isArray(), "Cannot parse credentials from non-array: %s", 
credentials);
+
+    ImmutableLoadCredentialsResponse.Builder builder = 
ImmutableLoadCredentialsResponse.builder();
+    for (JsonNode credential : credentials) {
+      builder.addCredentials(CredentialParser.fromJson(credential));
+    }
+
+    return builder.build();
+  }
+}
diff --git 
a/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponseParser.java
 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponseParser.java
index 875403d703..8d34b14983 100644
--- 
a/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponseParser.java
+++ 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponseParser.java
@@ -98,13 +98,7 @@ public class LoadTableResponseParser {
     }
 
     if (json.hasNonNull(STORAGE_CREDENTIALS)) {
-      JsonNode credentials = JsonUtil.get(STORAGE_CREDENTIALS, json);
-      Preconditions.checkArgument(
-          credentials.isArray(), "Cannot parse credentials from non-array: 
%s", credentials);
-
-      for (JsonNode credential : credentials) {
-        builder.addCredential(CredentialParser.fromJson(credential));
-      }
+      
builder.addAllCredentials(LoadCredentialsResponseParser.fromJson(json).credentials());
     }
 
     return builder.build();
diff --git 
a/core/src/main/java/org/apache/iceberg/rest/responses/LoadViewResponseParser.java
 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadViewResponseParser.java
index 61d8fce1dd..aedf05cf62 100644
--- 
a/core/src/main/java/org/apache/iceberg/rest/responses/LoadViewResponseParser.java
+++ 
b/core/src/main/java/org/apache/iceberg/rest/responses/LoadViewResponseParser.java
@@ -93,13 +93,7 @@ public class LoadViewResponseParser {
     }
 
     if (json.hasNonNull(STORAGE_CREDENTIALS)) {
-      JsonNode credentials = JsonUtil.get(STORAGE_CREDENTIALS, json);
-      Preconditions.checkArgument(
-          credentials.isArray(), "Cannot parse credentials from non-array: 
%s", credentials);
-
-      for (JsonNode credential : credentials) {
-        builder.addCredentials(CredentialParser.fromJson(credential));
-      }
+      
builder.addAllCredentials(LoadCredentialsResponseParser.fromJson(json).credentials());
     }
 
     return builder.build();
diff --git 
a/core/src/test/java/org/apache/iceberg/rest/responses/TestLoadCredentialsResponseParser.java
 
b/core/src/test/java/org/apache/iceberg/rest/responses/TestLoadCredentialsResponseParser.java
new file mode 100644
index 0000000000..f2e723da25
--- /dev/null
+++ 
b/core/src/test/java/org/apache/iceberg/rest/responses/TestLoadCredentialsResponseParser.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.iceberg.rest.responses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
+import org.apache.iceberg.rest.credentials.ImmutableCredential;
+import org.junit.jupiter.api.Test;
+
+public class TestLoadCredentialsResponseParser {
+  @Test
+  public void nullCheck() {
+    assertThatThrownBy(() -> LoadCredentialsResponseParser.toJson(null))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid load credentials response: null");
+
+    assertThatThrownBy(() -> LoadCredentialsResponseParser.fromJson((JsonNode) 
null))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Cannot parse load credentials response from null object");
+  }
+
+  @Test
+  public void missingFields() {
+    assertThatThrownBy(() -> LoadCredentialsResponseParser.fromJson("{}"))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Cannot parse missing field: storage-credentials");
+
+    assertThatThrownBy(() -> LoadCredentialsResponseParser.fromJson("{\"x\": 
\"val\"}"))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Cannot parse missing field: storage-credentials");
+  }
+
+  @Test
+  public void roundTripSerde() {
+    LoadCredentialsResponse response =
+        ImmutableLoadCredentialsResponse.builder()
+            .addCredentials(
+                ImmutableCredential.builder()
+                    .prefix("s3://custom-uri")
+                    .config(
+                        ImmutableMap.of(
+                            "s3.access-key-id",
+                            "keyId",
+                            "s3.secret-access-key",
+                            "accessKey",
+                            "s3.session-token",
+                            "sessionToken"))
+                    .build())
+            .addCredentials(
+                ImmutableCredential.builder()
+                    .prefix("gs://custom-uri")
+                    .config(
+                        ImmutableMap.of(
+                            "gcs.oauth2.token", "gcsToken1", 
"gcs.oauth2.token-expires-at", "1000"))
+                    .build())
+            .addCredentials(
+                ImmutableCredential.builder()
+                    .prefix("gs")
+                    .config(
+                        ImmutableMap.of(
+                            "gcs.oauth2.token", "gcsToken2", 
"gcs.oauth2.token-expires-at", "2000"))
+                    .build())
+            .build();
+
+    String expectedJson =
+        "{\n"
+            + "  \"storage-credentials\" : [ {\n"
+            + "    \"prefix\" : \"s3://custom-uri\",\n"
+            + "    \"config\" : {\n"
+            + "      \"s3.access-key-id\" : \"keyId\",\n"
+            + "      \"s3.secret-access-key\" : \"accessKey\",\n"
+            + "      \"s3.session-token\" : \"sessionToken\"\n"
+            + "    }\n"
+            + "  }, {\n"
+            + "    \"prefix\" : \"gs://custom-uri\",\n"
+            + "    \"config\" : {\n"
+            + "      \"gcs.oauth2.token\" : \"gcsToken1\",\n"
+            + "      \"gcs.oauth2.token-expires-at\" : \"1000\"\n"
+            + "    }\n"
+            + "  }, {\n"
+            + "    \"prefix\" : \"gs\",\n"
+            + "    \"config\" : {\n"
+            + "      \"gcs.oauth2.token\" : \"gcsToken2\",\n"
+            + "      \"gcs.oauth2.token-expires-at\" : \"2000\"\n"
+            + "    }\n"
+            + "  } ]\n"
+            + "}";
+
+    String json = LoadCredentialsResponseParser.toJson(response, true);
+    assertThat(json).isEqualTo(expectedJson);
+    
assertThat(LoadCredentialsResponseParser.fromJson(json)).isEqualTo(response);
+  }
+}

Reply via email to