Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ public void testNonJsonBodyFallsBackToRawBodyAsMessage() {
Assert.assertEquals("plain text error response", ex.getMessage());
}

@Test
public void testJsonBodyWithGenericApiErrorEnvelopeStillWorks() {
Map<String, List<String>> headers = new HashMap<>();
String json = "{\"error\":{\"grpc_code\":5,\"http_code\":404,"
+ "\"message\":\"Invalid request. Vault not found for vaultID: vault123. "
+ "Specify a valid vaultID.\",\"http_status\":\"Not Found\",\"details\":[]}}";
SkyflowException ex = new SkyflowException(404, new RuntimeException("fail"), headers, json);
Assert.assertEquals("Invalid request. Vault not found for vaultID: vault123. "
+ "Specify a valid vaultID.", ex.getMessage());
Assert.assertEquals(Integer.valueOf(5), ex.getGrpcCode());
Assert.assertEquals("Not Found", ex.getHttpStatus());
Assert.assertEquals(404, ex.getHttpCode());
Assert.assertNotNull(ex.getDetails());
Assert.assertEquals(0, ex.getDetails().size());
}

@Test
public void testNullBodyNullCauseMessageFallsBackToErrorOccurred() {
Map<String, List<String>> headers = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import com.skyflow.Skyflow;
import com.skyflow.config.Credentials;
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.CustomHeaderKey;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
import com.skyflow.errors.SkyflowException;
import com.skyflow.vault.data.DeleteRequest;
import com.skyflow.vault.data.DeleteResponse;
import com.skyflow.vault.data.DeleteResponseRecord;
import com.skyflow.vault.data.*;

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

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

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

DeleteResponse response = skyflowClient.vault().delete(request);
DeleteOptions options = DeleteOptions.builder()
.interceptor(ctx -> {
ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "DeleteOptions"); // pass the request id here
})
.build();
DeleteResponse response = skyflowClient.vault().delete(request, options);

// Step 6: Read the outcome. A record succeeded when its error is null.
for (DeleteResponseRecord record : response.getRecords()) {
Expand All @@ -66,7 +72,7 @@ public static void main(String[] args) {
}
} catch (SkyflowException e) {
// Step 7: Handle any errors that occur during the process
System.err.println("Error in delete operation:\t" + e.getMessage());
System.err.println("Error in delete operation:\t" + e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static void main(String[] args) {
List<TokenGroupRedactions> tokenGroupRedactions = new ArrayList<>();
tokenGroupRedactions.add(TokenGroupRedactions.builder()
.tokenGroupName("<YOUR_TOKEN_GROUP_NAME>")
.redaction("PLAIN_TEXT")
.redaction("plain_text")
.build());

// Step 5: Build and execute the detokenize request
Expand Down
25 changes: 14 additions & 11 deletions flowvault/samples/src/main/java/com/example/vault/GetExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public static void main(String[] args) {
// Step 2: Configure the vault with required parameters
VaultConfig vaultConfig = new VaultConfig();
vaultConfig.setVaultId("<YOUR_VAULT_ID>");
vaultConfig.setClusterId("<YOUR_CLUSTER_ID>");
vaultConfig.setVaultUrl("<YOUR_VAULT_URL>");
// vaultConfig.setClusterId("<YOUR_CLUSTER_ID>");
vaultConfig.setEnv(Env.PROD);
vaultConfig.setCredentials(credentials);

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

// Step 4: Prepare the skyflow IDs to fetch and any column redactions
ArrayList<String> ids = new ArrayList<>();
ids.add("<YOUR_SKYFLOW_ID>");

ids.add("<YOUR_SKYFLOW_ID_1>");
ids.add("<YOUR_SKYFLOW_ID_2>");
List<ColumnRedactions> columnRedactions = new ArrayList<>();
columnRedactions.add(ColumnRedactions.builder()
.columnName("<YOUR_COLUMN_NAME_1>")
.redaction("PLAIN_TEXT")
.redaction("plain_text")
.build());

// Step 5: Build and execute the get request
Expand All @@ -68,16 +69,18 @@ public static void main(String[] args) {
// Step 6: Read the fetched records
for (GetResponseRecord record : response.getRecords()) {
System.out.printf("get: %s -> skyflowId=%s%n", record.getTableName(), record.getSkyflowId());
for (Map.Entry<String, List<Token>> column : record.getTokens().entrySet()) {
for (Token token : column.getValue()) {
System.out.printf(" %s[%s] -> %s%n",
column.getKey(), token.getTokenGroupName(), token.getToken());
}
}
System.out.println("dta"+ record.getData());

// for (Map.Entry<String, List<Token>> column : record.getTokens().entrySet()) {
// for (Token token : column.getValue()) {
// System.out.printf(" %s[%s] -> %s%n",
// column.getKey(), token.getTokenGroupName(), token.getToken());
// }
// }
}
} catch (SkyflowException e) {
// Step 7: Handle any errors that occur during the process
System.err.println("Error in get operation:\t" + e.getMessage());
System.err.println("Error in get operation:\t" + e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
import com.skyflow.Skyflow;
import com.skyflow.config.Credentials;
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.CustomHeaderKey;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
import com.skyflow.errors.SkyflowException;
import com.skyflow.vault.data.InsertRequest;
import com.skyflow.vault.data.InsertRequestRecord;
import com.skyflow.vault.data.InsertResponse;
import com.skyflow.vault.data.InsertResponseRecord;
import com.skyflow.vault.data.Token;
import com.skyflow.vault.data.*;

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

InsertRequestRecord record = InsertRequestRecord.builder()
.data(data)
.tableName("<YOUR_TABLE_NAME>")
.build();

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

// Step 5: Build and execute the insert request
InsertRequest request = InsertRequest.builder()
.tableName("<YOUR_TABLE_NAME>")
// .tableName("<YOUR_TABLE_NAME>")
.records(records)
.build();
InsertOptions options = InsertOptions.builder()
.interceptor(ctx -> {
ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "demo"); // pass the request id here
})
.build();
InsertResponse response = skyflowClient.vault().insert(request, options);

InsertResponse response = skyflowClient.vault().insert(request);

// Step 6: Read the outcome. A record succeeded when its error is null.
// Step 6: Print every field on each response record.
for (InsertResponseRecord insertedRecord : response.getRecords()) {
if (insertedRecord.getError() == null) {
System.out.printf("insert: %s -> skyflowId=%s%n",
insertedRecord.getTableName(), insertedRecord.getSkyflowId());
for (Map.Entry<String, List<Token>> column : insertedRecord.getTokens().entrySet()) {
for (Token token : column.getValue()) {
System.out.printf(" %s[%s] -> %s%n",
column.getKey(), token.getTokenGroupName(), token.getToken());
}
}
} else {
System.out.printf("insert failed (%d): %s%n", insertedRecord.getHttpCode(), insertedRecord.getError());
}
System.out.println("tableName:\t" + insertedRecord.getTableName());
System.out.println("skyflowId:\t" + insertedRecord.getSkyflowId());
System.out.println("tokens:\t\t" + insertedRecord.getTokens());
System.out.println("data:\t\t" + insertedRecord.getData());
System.out.println("hashedData:\t" + insertedRecord.getHashedData());
System.out.println("httpCode:\t" + insertedRecord.getHttpCode());
System.out.println("error:\t\t" + insertedRecord.getError());
// System.out.println("request id " +insertedRecord.ge);
}
} catch (SkyflowException e) {
// Step 7: Handle any errors that occur during the process
System.err.println("Error in insert operation:\t" + e.getMessage());
System.err.println("Error in insert operation:\t" + e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static void main(String[] args) {
for (QueryResponseRecord record : response.getRecords()) {
System.out.println("query row: " + record.getData());
}
System.out.println("columns: " + response.getColumns());
System.out.println("columns: " + (response.getMetadata() != null ? response.getMetadata().getColumns() : null));
} catch (SkyflowException e) {
// Step 6: Handle any errors that occur during the process
System.err.println("Error in query operation:\t" + e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
import com.skyflow.enums.UpdateType;
import com.skyflow.errors.SkyflowException;
import com.skyflow.vault.data.UpdateRequest;
import com.skyflow.vault.data.UpdateRequestRecord;
Expand Down Expand Up @@ -55,18 +56,19 @@ public static void main(String[] args) {
records.add(updateRecord);

// Step 5: Build and execute the update request.
// updateType accepts "UPDATE" (default) or "REPLACE".
// updateType accepts UpdateType.UPDATE (default) or UpdateType.REPLACE.
UpdateRequest request = UpdateRequest.builder()
.tableName("<YOUR_TABLE_NAME>")
.records(records)
.updateType("REPLACE")
.updateType(UpdateType.REPLACE)
.build();

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

// Step 6: Read the outcome. A record succeeded when its error is null.
for (UpdateResponseRecord record : response.getRecords()) {
if (record.getError() == null) {
System.out.println("data" + record.getTokens());
System.out.printf("update: %s -> skyflowId=%s%n", record.getTableName(), record.getSkyflowId());
} else {
System.out.printf("update failed (%d): %s%n", record.getHttpCode(), record.getError());
Expand Down
125 changes: 125 additions & 0 deletions flowvault/src/main/java/com/skyflow/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,131 @@ public static ErrorRecord createErrorRecord(Map<String, Object> recordMap, int i
return err;
}

// ── Unary "records"-shaped exception fallback ─────────────────────────────
//
// A unary call's own (only) record can fail outright — e.g. an invalid column on the sole
// record in an update/insert/get/delete request — and the vault reflects that as the overall
// HTTP status, so the generated client throws ApiClientApiException instead of returning a
// normal response body. When the exception body still has the familiar per-record shape
// ({"records": [...]} for insert/update/get/delete, {"response": [...]} for detokenize), the
// failure belongs on the response the same way a 200 partial-success does — not as a thrown
// exception. Each handler below returns null when the body doesn't match that shape, so the
// caller falls back to throwing a SkyflowException as before.

/** Record maps under {@code key} in an exception body, or null if the shape doesn't match. */
private static List<Map<String, Object>> extractExceptionRecords(ApiClientApiException apiException, String key) {
Object rawBody = apiException.body();
if (!(rawBody instanceof Map)) {
return null;
}
Object recordsField = ((Map<?, ?>) rawBody).get(key);
if (!(recordsField instanceof List)) {
return null;
}
List<Map<String, Object>> records = new ArrayList<>();
for (Object recordObj : (List<?>) recordsField) {
if (recordObj instanceof Map) {
//noinspection unchecked
records.add((Map<String, Object>) recordObj);
}
}
return records.isEmpty() ? null : records;
}

public static InsertResponse handleInsertRequestException(ApiClientApiException apiException) {
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
if (recordMaps == null) {
return null;
}
String requestId = extractRequestId(apiException.headers());
List<InsertResponseRecord> records = new ArrayList<>();
for (Map<String, Object> recordMap : recordMaps) {
records.add(new InsertResponseRecord(
readString(recordMap, "tableName"),
readString(recordMap, "skyflowID"),
null, null, null,
readHttpCode(recordMap, apiException.statusCode()),
readErrorMessage(recordMap),
requestId));
}
return new InsertResponse(records);
}

public static UpdateResponse handleUpdateRequestException(ApiClientApiException apiException) {
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
if (recordMaps == null) {
return null;
}
String requestId = extractRequestId(apiException.headers());
List<UpdateResponseRecord> records = new ArrayList<>();
for (Map<String, Object> recordMap : recordMaps) {
records.add(new UpdateResponseRecord(
readString(recordMap, "tableName"),
readString(recordMap, "skyflowID"),
null, null, null,
readHttpCode(recordMap, apiException.statusCode()),
readErrorMessage(recordMap),
requestId));
}
return new UpdateResponse(records);
}

public static GetResponse handleGetRequestException(ApiClientApiException apiException) {
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
if (recordMaps == null) {
return null;
}
String requestId = extractRequestId(apiException.headers());
List<GetResponseRecord> records = new ArrayList<>();
for (Map<String, Object> recordMap : recordMaps) {
records.add(new GetResponseRecord(
readString(recordMap, "tableName"),
readString(recordMap, "skyflowID"),
null, null, null,
readHttpCode(recordMap, apiException.statusCode()),
readErrorMessage(recordMap),
requestId));
}
return new GetResponse(records);
}

public static DeleteResponse handleDeleteRequestException(ApiClientApiException apiException) {
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "records");
if (recordMaps == null) {
return null;
}
String requestId = extractRequestId(apiException.headers());
List<DeleteResponseRecord> records = new ArrayList<>();
for (Map<String, Object> recordMap : recordMaps) {
records.add(new DeleteResponseRecord(
readString(recordMap, "skyflowID"),
readHttpCode(recordMap, apiException.statusCode()),
readErrorMessage(recordMap),
requestId));
}
return new DeleteResponse(records);
}

public static DetokenizeResponse handleDetokenizeRequestException(ApiClientApiException apiException) {
List<Map<String, Object>> recordMaps = extractExceptionRecords(apiException, "response");
if (recordMaps == null) {
return null;
}
String requestId = extractRequestId(apiException.headers());
List<DetokenizeResponseRecord> records = new ArrayList<>();
for (Map<String, Object> recordMap : recordMaps) {
records.add(new DetokenizeResponseRecord(
readString(recordMap, "token"),
null,
readString(recordMap, "tokenGroupName"),
null,
readHttpCode(recordMap, apiException.statusCode()),
readErrorMessage(recordMap),
requestId));
}
return new DetokenizeResponse(records);
}

// Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto
// the unified BulkInsertResponseRecord shape that bulk insert now returns.
public static List<BulkInsertResponseRecord> handleBulkInsertBatchException(
Expand Down
Loading
Loading