Skip to content

Commit ffdd1a7

Browse files
Merge pull request #428 from skyflowapi/SK-3131-unary-operations-in-java-flowvault-sdk
SK-3131 unary operations in java flowvault sdk
2 parents d6b030f + 29217de commit ffdd1a7

11 files changed

Lines changed: 532 additions & 42 deletions

File tree

‎common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,22 @@ public void testNonJsonBodyFallsBackToRawBodyAsMessage() {
192192
Assert.assertEquals("plain text error response", ex.getMessage());
193193
}
194194

195+
@Test
196+
public void testJsonBodyWithGenericApiErrorEnvelopeStillWorks() {
197+
Map<String, List<String>> headers = new HashMap<>();
198+
String json = "{\"error\":{\"grpc_code\":5,\"http_code\":404,"
199+
+ "\"message\":\"Invalid request. Vault not found for vaultID: vault123. "
200+
+ "Specify a valid vaultID.\",\"http_status\":\"Not Found\",\"details\":[]}}";
201+
SkyflowException ex = new SkyflowException(404, new RuntimeException("fail"), headers, json);
202+
Assert.assertEquals("Invalid request. Vault not found for vaultID: vault123. "
203+
+ "Specify a valid vaultID.", ex.getMessage());
204+
Assert.assertEquals(Integer.valueOf(5), ex.getGrpcCode());
205+
Assert.assertEquals("Not Found", ex.getHttpStatus());
206+
Assert.assertEquals(404, ex.getHttpCode());
207+
Assert.assertNotNull(ex.getDetails());
208+
Assert.assertEquals(0, ex.getDetails().size());
209+
}
210+
195211
@Test
196212
public void testNullBodyNullCauseMessageFallsBackToErrorOccurred() {
197213
Map<String, List<String>> headers = new HashMap<>();

‎flowvault/samples/src/main/java/com/example/vault/DeleteExample.java‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
import com.skyflow.Skyflow;
44
import com.skyflow.config.Credentials;
55
import com.skyflow.config.VaultConfig;
6+
import com.skyflow.enums.CustomHeaderKey;
67
import com.skyflow.enums.Env;
78
import com.skyflow.enums.LogLevel;
89
import com.skyflow.errors.SkyflowException;
9-
import com.skyflow.vault.data.DeleteRequest;
10-
import com.skyflow.vault.data.DeleteResponse;
11-
import com.skyflow.vault.data.DeleteResponseRecord;
10+
import com.skyflow.vault.data.*;
1211

1312
import java.util.ArrayList;
1413
import java.util.List;
@@ -45,6 +44,8 @@ public static void main(String[] args) {
4544

4645
// Step 4: Prepare the skyflow IDs to delete.
4746
// Either ids or uniqueValues is required; specifying both fails validation.
47+
// Running this actually removes the record — rerunning GetExample/UpdateExample
48+
// against the same skyflowId afterward will then fail, since it's gone.
4849
List<String> ids = new ArrayList<>();
4950
ids.add("<YOUR_SKYFLOW_ID>");
5051

@@ -54,7 +55,12 @@ public static void main(String[] args) {
5455
.ids(ids)
5556
.build();
5657

57-
DeleteResponse response = skyflowClient.vault().delete(request);
58+
DeleteOptions options = DeleteOptions.builder()
59+
.interceptor(ctx -> {
60+
ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "DeleteOptions"); // pass the request id here
61+
})
62+
.build();
63+
DeleteResponse response = skyflowClient.vault().delete(request, options);
5864

5965
// Step 6: Read the outcome. A record succeeded when its error is null.
6066
for (DeleteResponseRecord record : response.getRecords()) {
@@ -66,7 +72,7 @@ public static void main(String[] args) {
6672
}
6773
} catch (SkyflowException e) {
6874
// Step 7: Handle any errors that occur during the process
69-
System.err.println("Error in delete operation:\t" + e.getMessage());
75+
System.err.println("Error in delete operation:\t" + e);
7076
}
7177
}
7278
}

‎flowvault/samples/src/main/java/com/example/vault/DetokenizeExample.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static void main(String[] args) {
4848
List<TokenGroupRedactions> tokenGroupRedactions = new ArrayList<>();
4949
tokenGroupRedactions.add(TokenGroupRedactions.builder()
5050
.tokenGroupName("<YOUR_TOKEN_GROUP_NAME>")
51-
.redaction("PLAIN_TEXT")
51+
.redaction("plain_text")
5252
.build());
5353

5454
// Step 5: Build and execute the detokenize request

‎flowvault/samples/src/main/java/com/example/vault/GetExample.java‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public static void main(String[] args) {
3636
// Step 2: Configure the vault with required parameters
3737
VaultConfig vaultConfig = new VaultConfig();
3838
vaultConfig.setVaultId("<YOUR_VAULT_ID>");
39-
vaultConfig.setClusterId("<YOUR_CLUSTER_ID>");
39+
vaultConfig.setVaultUrl("<YOUR_VAULT_URL>");
40+
// vaultConfig.setClusterId("<YOUR_CLUSTER_ID>");
4041
vaultConfig.setEnv(Env.PROD);
4142
vaultConfig.setCredentials(credentials);
4243

@@ -48,12 +49,12 @@ public static void main(String[] args) {
4849

4950
// Step 4: Prepare the skyflow IDs to fetch and any column redactions
5051
ArrayList<String> ids = new ArrayList<>();
51-
ids.add("<YOUR_SKYFLOW_ID>");
52-
52+
ids.add("<YOUR_SKYFLOW_ID_1>");
53+
ids.add("<YOUR_SKYFLOW_ID_2>");
5354
List<ColumnRedactions> columnRedactions = new ArrayList<>();
5455
columnRedactions.add(ColumnRedactions.builder()
5556
.columnName("<YOUR_COLUMN_NAME_1>")
56-
.redaction("PLAIN_TEXT")
57+
.redaction("plain_text")
5758
.build());
5859

5960
// Step 5: Build and execute the get request
@@ -68,16 +69,18 @@ public static void main(String[] args) {
6869
// Step 6: Read the fetched records
6970
for (GetResponseRecord record : response.getRecords()) {
7071
System.out.printf("get: %s -> skyflowId=%s%n", record.getTableName(), record.getSkyflowId());
71-
for (Map.Entry<String, List<Token>> column : record.getTokens().entrySet()) {
72-
for (Token token : column.getValue()) {
73-
System.out.printf(" %s[%s] -> %s%n",
74-
column.getKey(), token.getTokenGroupName(), token.getToken());
75-
}
76-
}
72+
System.out.println("dta"+ record.getData());
73+
74+
// for (Map.Entry<String, List<Token>> column : record.getTokens().entrySet()) {
75+
// for (Token token : column.getValue()) {
76+
// System.out.printf(" %s[%s] -> %s%n",
77+
// column.getKey(), token.getTokenGroupName(), token.getToken());
78+
// }
79+
// }
7780
}
7881
} catch (SkyflowException e) {
7982
// Step 7: Handle any errors that occur during the process
80-
System.err.println("Error in get operation:\t" + e.getMessage());
83+
System.err.println("Error in get operation:\t" + e);
8184
}
8285
}
8386
}

‎flowvault/samples/src/main/java/com/example/vault/InsertExample.java‎

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33
import com.skyflow.Skyflow;
44
import com.skyflow.config.Credentials;
55
import com.skyflow.config.VaultConfig;
6+
import com.skyflow.enums.CustomHeaderKey;
67
import com.skyflow.enums.Env;
78
import com.skyflow.enums.LogLevel;
89
import com.skyflow.errors.SkyflowException;
9-
import com.skyflow.vault.data.InsertRequest;
10-
import com.skyflow.vault.data.InsertRequestRecord;
11-
import com.skyflow.vault.data.InsertResponse;
12-
import com.skyflow.vault.data.InsertResponseRecord;
13-
import com.skyflow.vault.data.Token;
10+
import com.skyflow.vault.data.*;
1411

1512
import java.util.ArrayList;
1613
import java.util.HashMap;
@@ -50,37 +47,38 @@ public static void main(String[] args) {
5047

5148
InsertRequestRecord record = InsertRequestRecord.builder()
5249
.data(data)
50+
.tableName("<YOUR_TABLE_NAME>")
5351
.build();
5452

5553
List<InsertRequestRecord> records = new ArrayList<>();
5654
records.add(record);
5755

5856
// Step 5: Build and execute the insert request
5957
InsertRequest request = InsertRequest.builder()
60-
.tableName("<YOUR_TABLE_NAME>")
58+
// .tableName("<YOUR_TABLE_NAME>")
6159
.records(records)
6260
.build();
61+
InsertOptions options = InsertOptions.builder()
62+
.interceptor(ctx -> {
63+
ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "demo"); // pass the request id here
64+
})
65+
.build();
66+
InsertResponse response = skyflowClient.vault().insert(request, options);
6367

64-
InsertResponse response = skyflowClient.vault().insert(request);
65-
66-
// Step 6: Read the outcome. A record succeeded when its error is null.
68+
// Step 6: Print every field on each response record.
6769
for (InsertResponseRecord insertedRecord : response.getRecords()) {
68-
if (insertedRecord.getError() == null) {
69-
System.out.printf("insert: %s -> skyflowId=%s%n",
70-
insertedRecord.getTableName(), insertedRecord.getSkyflowId());
71-
for (Map.Entry<String, List<Token>> column : insertedRecord.getTokens().entrySet()) {
72-
for (Token token : column.getValue()) {
73-
System.out.printf(" %s[%s] -> %s%n",
74-
column.getKey(), token.getTokenGroupName(), token.getToken());
75-
}
76-
}
77-
} else {
78-
System.out.printf("insert failed (%d): %s%n", insertedRecord.getHttpCode(), insertedRecord.getError());
79-
}
70+
System.out.println("tableName:\t" + insertedRecord.getTableName());
71+
System.out.println("skyflowId:\t" + insertedRecord.getSkyflowId());
72+
System.out.println("tokens:\t\t" + insertedRecord.getTokens());
73+
System.out.println("data:\t\t" + insertedRecord.getData());
74+
System.out.println("hashedData:\t" + insertedRecord.getHashedData());
75+
System.out.println("httpCode:\t" + insertedRecord.getHttpCode());
76+
System.out.println("error:\t\t" + insertedRecord.getError());
77+
// System.out.println("request id " +insertedRecord.ge);
8078
}
8179
} catch (SkyflowException e) {
8280
// Step 7: Handle any errors that occur during the process
83-
System.err.println("Error in insert operation:\t" + e.getMessage());
81+
System.err.println("Error in insert operation:\t" + e);
8482
}
8583
}
8684
}

‎flowvault/samples/src/main/java/com/example/vault/QueryExample.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static void main(String[] args) {
4747
for (QueryResponseRecord record : response.getRecords()) {
4848
System.out.println("query row: " + record.getData());
4949
}
50-
System.out.println("columns: " + response.getColumns());
50+
System.out.println("columns: " + (response.getMetadata() != null ? response.getMetadata().getColumns() : null));
5151
} catch (SkyflowException e) {
5252
// Step 6: Handle any errors that occur during the process
5353
System.err.println("Error in query operation:\t" + e.getMessage());

‎flowvault/samples/src/main/java/com/example/vault/UpdateExample.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.skyflow.config.VaultConfig;
66
import com.skyflow.enums.Env;
77
import com.skyflow.enums.LogLevel;
8+
import com.skyflow.enums.UpdateType;
89
import com.skyflow.errors.SkyflowException;
910
import com.skyflow.vault.data.UpdateRequest;
1011
import com.skyflow.vault.data.UpdateRequestRecord;
@@ -55,18 +56,19 @@ public static void main(String[] args) {
5556
records.add(updateRecord);
5657

5758
// Step 5: Build and execute the update request.
58-
// updateType accepts "UPDATE" (default) or "REPLACE".
59+
// updateType accepts UpdateType.UPDATE (default) or UpdateType.REPLACE.
5960
UpdateRequest request = UpdateRequest.builder()
6061
.tableName("<YOUR_TABLE_NAME>")
6162
.records(records)
62-
.updateType("REPLACE")
63+
.updateType(UpdateType.REPLACE)
6364
.build();
6465

6566
UpdateResponse response = skyflowClient.vault().update(request);
6667

6768
// Step 6: Read the outcome. A record succeeded when its error is null.
6869
for (UpdateResponseRecord record : response.getRecords()) {
6970
if (record.getError() == null) {
71+
System.out.println("data" + record.getTokens());
7072
System.out.printf("update: %s -> skyflowId=%s%n", record.getTableName(), record.getSkyflowId());
7173
} else {
7274
System.out.printf("update failed (%d): %s%n", record.getHttpCode(), record.getError());

‎flowvault/src/main/java/com/skyflow/utils/Utils.java‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,131 @@ public static ErrorRecord createErrorRecord(Map<String, Object> recordMap, int i
539539
return err;
540540
}
541541

542+
// ── Unary "records"-shaped exception fallback ─────────────────────────────
543+
//
544+
// A unary call's own (only) record can fail outright — e.g. an invalid column on the sole
545+
// record in an update/insert/get/delete request — and the vault reflects that as the overall
546+
// HTTP status, so the generated client throws ApiClientApiException instead of returning a
547+
// normal response body. When the exception body still has the familiar per-record shape
548+
// ({"records": [...]} for insert/update/get/delete, {"response": [...]} for detokenize), the
549+
// failure belongs on the response the same way a 200 partial-success does — not as a thrown
550+
// exception. Each handler below returns null when the body doesn't match that shape, so the
551+
// caller falls back to throwing a SkyflowException as before.
552+
553+
/** Record maps under {@code key} in an exception body, or null if the shape doesn't match. */
554+
private static List<Map<String, Object>> extractExceptionRecords(ApiClientApiException apiException, String key) {
555+
Object rawBody = apiException.body();
556+
if (!(rawBody instanceof Map)) {
557+
return null;
558+
}
559+
Object recordsField = ((Map<?, ?>) rawBody).get(key);
560+
if (!(recordsField instanceof List)) {
561+
return null;
562+
}
563+
List<Map<String, Object>> records = new ArrayList<>();
564+
for (Object recordObj : (List<?>) recordsField) {
565+
if (recordObj instanceof Map) {
566+
//noinspection unchecked
567+
records.add((Map<String, Object>) recordObj);
568+
}
569+
}
570+
return records.isEmpty() ? null : records;
571+
}
572+
573+
public static InsertResponse handleInsertRequestException(ApiClientApiException apiException) {
574+
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
575+
if (recordMaps == null) {
576+
return null;
577+
}
578+
String requestId = extractRequestId(apiException.headers());
579+
List<InsertResponseRecord> records = new ArrayList<>();
580+
for (Map<String, Object> recordMap : recordMaps) {
581+
records.add(new InsertResponseRecord(
582+
readString(recordMap, "tableName"),
583+
readString(recordMap, "skyflowID"),
584+
null, null, null,
585+
readHttpCode(recordMap, apiException.statusCode()),
586+
readErrorMessage(recordMap),
587+
requestId));
588+
}
589+
return new InsertResponse(records);
590+
}
591+
592+
public static UpdateResponse handleUpdateRequestException(ApiClientApiException apiException) {
593+
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
594+
if (recordMaps == null) {
595+
return null;
596+
}
597+
String requestId = extractRequestId(apiException.headers());
598+
List<UpdateResponseRecord> records = new ArrayList<>();
599+
for (Map<String, Object> recordMap : recordMaps) {
600+
records.add(new UpdateResponseRecord(
601+
readString(recordMap, "tableName"),
602+
readString(recordMap, "skyflowID"),
603+
null, null, null,
604+
readHttpCode(recordMap, apiException.statusCode()),
605+
readErrorMessage(recordMap),
606+
requestId));
607+
}
608+
return new UpdateResponse(records);
609+
}
610+
611+
public static GetResponse handleGetRequestException(ApiClientApiException apiException) {
612+
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
613+
if (recordMaps == null) {
614+
return null;
615+
}
616+
String requestId = extractRequestId(apiException.headers());
617+
List<GetResponseRecord> records = new ArrayList<>();
618+
for (Map<String, Object> recordMap : recordMaps) {
619+
records.add(new GetResponseRecord(
620+
readString(recordMap, "tableName"),
621+
readString(recordMap, "skyflowID"),
622+
null, null, null,
623+
readHttpCode(recordMap, apiException.statusCode()),
624+
readErrorMessage(recordMap),
625+
requestId));
626+
}
627+
return new GetResponse(records);
628+
}
629+
630+
public static DeleteResponse handleDeleteRequestException(ApiClientApiException apiException) {
631+
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
632+
if (recordMaps == null) {
633+
return null;
634+
}
635+
String requestId = extractRequestId(apiException.headers());
636+
List<DeleteResponseRecord> records = new ArrayList<>();
637+
for (Map<String, Object> recordMap : recordMaps) {
638+
records.add(new DeleteResponseRecord(
639+
readString(recordMap, "skyflowID"),
640+
readHttpCode(recordMap, apiException.statusCode()),
641+
readErrorMessage(recordMap),
642+
requestId));
643+
}
644+
return new DeleteResponse(records);
645+
}
646+
647+
public static DetokenizeResponse handleDetokenizeRequestException(ApiClientApiException apiException) {
648+
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "response");
649+
if (recordMaps == null) {
650+
return null;
651+
}
652+
String requestId = extractRequestId(apiException.headers());
653+
List<DetokenizeResponseRecord> records = new ArrayList<>();
654+
for (Map<String, Object> recordMap : recordMaps) {
655+
records.add(new DetokenizeResponseRecord(
656+
readString(recordMap, "token"),
657+
null,
658+
readString(recordMap, "tokenGroupName"),
659+
null,
660+
readHttpCode(recordMap, apiException.statusCode()),
661+
readErrorMessage(recordMap),
662+
requestId));
663+
}
664+
return new DetokenizeResponse(records);
665+
}
666+
542667
// Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto
543668
// the unified BulkInsertResponseRecord shape that bulk insert now returns.
544669
public static List<BulkInsertResponseRecord> handleBulkInsertBatchException(

0 commit comments

Comments
 (0)