Skip to content

Commit 1bdb24b

Browse files
SK-3026 update custom header naming convention
1 parent ae3d89d commit 1bdb24b

11 files changed

Lines changed: 165 additions & 218 deletions

File tree

‎common/src/main/java/com/skyflow/enums/CustomHeaderKey.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.skyflow.enums;
22

33
public enum CustomHeaderKey {
4-
SkyflowAccountId("x-skyflow-account-id"),
5-
SkyflowAccountName("x-skyflow-account-name"),
6-
RequestIdHeader("x-request-id");
4+
SKYFLOW_ACCOUNT_ID("x-skyflow-account-id"),
5+
SKYFLOW_ACCOUNT_NAME("x-skyflow-account-name"),
6+
REQUEST_ID_HEADER("x-request-id");
77

88
private final String value;
99

‎common/src/main/java/com/skyflow/errors/SkyflowException.java‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@
99
import java.util.List;
1010
import java.util.Map;
1111

12+
/**
13+
* Exception thrown by all Skyflow SDK operations.
14+
*
15+
* <p>There are two broad categories of errors:
16+
*
17+
* <ul>
18+
* <li><b>Validation errors</b> — caught before any network call is made (e.g. missing table,
19+
* empty token list). These always have {@code httpCode = 400} and an empty
20+
* {@link #getDetails()} array. {@link #getRequestId()} and {@link #getGrpcCode()} are
21+
* {@code null}.
22+
* <li><b>API errors</b> — returned by the Skyflow server. The HTTP status code, gRPC code,
23+
* human-readable status string, error message, and request ID are all parsed from the
24+
* response and available via the corresponding getters.
25+
* </ul>
26+
*
27+
* <p>Typical error-handling pattern:
28+
* <pre>{@code
29+
* try {
30+
* response = vault.insert(request);
31+
* } catch (SkyflowException e) {
32+
* System.err.println("HTTP " + e.getHttpCode() + " — " + e.getMessage());
33+
* if (e.getRequestId() != null) {
34+
* System.err.println("Request ID: " + e.getRequestId());
35+
* }
36+
* }
37+
* }</pre>
38+
*/
1239
public class SkyflowException extends Exception {
1340
private String requestId;
1441
private Integer grpcCode;
@@ -33,6 +60,11 @@ public SkyflowException(String message, Throwable cause) {
3360
this.message = message;
3461
}
3562

63+
/**
64+
* Constructs a validation error with a fixed HTTP 400 status.
65+
* {@link #getDetails()} returns an empty array; {@link #getRequestId()} and
66+
* {@link #getGrpcCode()} return {@code null}.
67+
*/
3668
public SkyflowException(int code, String message) {
3769
super(message);
3870
this.httpCode = code;
@@ -41,6 +73,13 @@ public SkyflowException(int code, String message) {
4173
this.details = new JsonArray();
4274
}
4375

76+
/**
77+
* Constructs an API error from an HTTP response.
78+
* Parses the JSON error body to populate {@link #getMessage()}, {@link #getGrpcCode()},
79+
* {@link #getHttpStatus()}, and {@link #getDetails()}. The request ID is read from the
80+
* {@code x-request-id} response header. If the body cannot be parsed, falls back to the
81+
* raw body string as the message.
82+
*/
4483
public SkyflowException(int httpCode, Throwable cause, Map<String, List<String>> responseHeaders, String responseBody) {
4584
super(cause);
4685
this.httpCode = httpCode > 0 ? httpCode : 400;
@@ -65,6 +104,10 @@ private void setResponseBody(String responseBody, Map<String, List<String>> resp
65104
}
66105
}
67106

107+
/**
108+
* Returns the {@code x-request-id} from the server response, useful for support escalations.
109+
* {@code null} for validation errors that never reached the server.
110+
*/
68111
public String getRequestId() {
69112
return requestId;
70113
}
@@ -89,10 +132,20 @@ private void setHttpStatus() {
89132
this.httpStatus = statusElement == null ? null : statusElement.getAsString();
90133
}
91134

135+
/**
136+
* Returns the HTTP status code (e.g. 400, 404, 500).
137+
* Defaults to 400 when the server returned a non-positive code, and 0 when the
138+
* exception carries no HTTP code at all (e.g. it wraps a local failure).
139+
*/
92140
public int getHttpCode() {
93141
return httpCode == null ? 0 : httpCode;
94142
}
95143

144+
/**
145+
* Returns additional error details from the server response, or an empty array for
146+
* validation errors. Never {@code null} for validation errors; may be {@code null} for
147+
* API errors whose response body contained no {@code details} field.
148+
*/
96149
public JsonArray getDetails() {
97150
return details;
98151
}
@@ -112,10 +165,18 @@ private void setDetails(Map<String, List<String>> responseHeaders) {
112165
}
113166
}
114167

168+
/**
169+
* Returns the gRPC status code from the server response.
170+
* {@code null} for validation errors and API responses that omit this field.
171+
*/
115172
public Integer getGrpcCode() {
116173
return grpcCode;
117174
}
118175

176+
/**
177+
* Returns the human-readable HTTP status string from the server response (e.g.
178+
* {@code "Bad Request"}, {@code "Not Found"}).
179+
*/
119180
public String getHttpStatus() {
120181
return httpStatus;
121182
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ public void testToStringWithNullFields() {
138138
Assert.assertTrue(str.contains("details: null"));
139139
}
140140

141+
// Regression: skyvault used to ship its own copy of this class whose getHttpCode()
142+
// unboxed the null Integer and threw NPE for every constructor that takes no code.
143+
@Test
144+
public void testGetHttpCodeIsZeroWhenNoCodeWasSet() {
145+
Assert.assertEquals(0, new SkyflowException("local failure").getHttpCode());
146+
Assert.assertEquals(0, new SkyflowException(new RuntimeException("boom")).getHttpCode());
147+
Assert.assertEquals(0, new SkyflowException("local failure", new RuntimeException("boom")).getHttpCode());
148+
}
149+
141150
@Test
142151
public void testZeroHttpCodeDefaultsTo400() {
143152
Map<String, List<String>> headers = new HashMap<>();

‎common/src/test/java/com/skyflow/vault/data/RequestContextTests.java‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,29 @@ public void testGetHeadersReturnsEmptyMapByDefault() {
3232
@Test
3333
public void testAddHeaderIsReflectedInGetHeaders() {
3434
RequestContext context = new RequestContext("INSERT");
35-
context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value");
35+
context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "account-id-value");
3636

3737
Map<CustomHeaderKey, String> headers = context.getHeaders();
3838

3939
Assert.assertEquals(1, headers.size());
40-
Assert.assertEquals("account-id-value", headers.get(CustomHeaderKey.SkyflowAccountId));
40+
Assert.assertEquals("account-id-value", headers.get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID));
4141
}
4242

4343
@Test
4444
public void testAddHeaderOverwritesExistingValueForSameKey() {
4545
RequestContext context = new RequestContext("INSERT");
46-
context.addHeader(CustomHeaderKey.SkyflowAccountId, "first-value");
47-
context.addHeader(CustomHeaderKey.SkyflowAccountId, "second-value");
46+
context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "first-value");
47+
context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "second-value");
4848

4949
Assert.assertEquals(1, context.getHeaders().size());
50-
Assert.assertEquals("second-value", context.getHeaders().get(CustomHeaderKey.SkyflowAccountId));
50+
Assert.assertEquals("second-value", context.getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID));
5151
}
5252

5353
@Test
5454
public void testAddMultipleDistinctHeaders() {
5555
RequestContext context = new RequestContext("DETOKENIZE");
56-
context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value");
57-
context.addHeader(CustomHeaderKey.SkyflowAccountName, "account-name-value");
56+
context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "account-id-value");
57+
context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_NAME, "account-name-value");
5858

5959
Assert.assertEquals(2, context.getHeaders().size());
6060
}
@@ -63,6 +63,6 @@ public void testAddMultipleDistinctHeaders() {
6363
public void testGetHeadersReturnsUnmodifiableMap() {
6464
RequestContext context = new RequestContext("INSERT");
6565

66-
context.getHeaders().put(CustomHeaderKey.RequestIdHeader, "request-id-value");
66+
context.getHeaders().put(CustomHeaderKey.REQUEST_ID_HEADER, "request-id-value");
6767
}
6868
}

‎common/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ public class RequestInterceptorTests {
88

99
@Test
1010
public void testInterceptMutatesRequestContext() {
11-
RequestInterceptor interceptor = context -> context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value");
11+
RequestInterceptor interceptor = context -> context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "account-id-value");
1212
RequestContext context = new RequestContext("INSERT");
1313

1414
interceptor.intercept(context);
1515

16-
Assert.assertEquals("account-id-value", context.getHeaders().get(CustomHeaderKey.SkyflowAccountId));
16+
Assert.assertEquals("account-id-value", context.getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID));
1717
}
1818

1919
@Test

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
* This sample demonstrates how to attach custom headers to outgoing requests via a request
2525
* interceptor on the options object.
2626
*
27-
* <p>Available keys on {@link CustomHeaderKey}: {@code SkyflowAccountId} ({@code x-skyflow-account-id}),
28-
* {@code SkyflowAccountName} ({@code x-skyflow-account-name}) and {@code RequestIdHeader}
27+
* <p>Available keys on {@link CustomHeaderKey}: {@code SKYFLOW_ACCOUNT_ID} ({@code x-skyflow-account-id}),
28+
* {@code SKYFLOW_ACCOUNT_NAME} ({@code x-skyflow-account-name}) and {@code REQUEST_ID_HEADER}
2929
* ({@code x-request-id}).
3030
*
3131
* <p>The interceptor runs once per batch, so a per-request value such as a request id is generated
@@ -85,7 +85,7 @@ public static void main(String[] args) {
8585
// Step 7: Attach a custom header through the interceptor
8686
InsertOptions options = InsertOptions.builder()
8787
.interceptor(ctx -> {
88-
ctx.addHeader(CustomHeaderKey.RequestIdHeader, getRequestId()); // pass the request id here
88+
ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, getRequestId()); // pass the request id here
8989
})
9090
.build();
9191

‎flowvault/src/main/java/com/skyflow/Skyflow.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ public VaultController vault() throws SkyflowException {
5959
return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList);
6060
}
6161

62+
public VaultController vault(String vaultId) throws SkyflowException {
63+
return resolveOrThrow(this.builder.vaultClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList);
64+
}
65+
66+
6267
public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder<VaultConfig> {
6368
private final LinkedHashMap<String, VaultController> vaultClientsMap = new LinkedHashMap<>();
6469
// Client-wide HTTP config. Resolution per vault, most specific first:

‎flowvault/src/test/java/com/skyflow/SkyflowTests.java‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,73 @@ public void testVault_throwsWhenNoConfigExists() {
442442
}
443443
}
444444

445+
// ── vault(vaultId) ────────────────────────────────────────────────────────
446+
447+
@Test
448+
public void testVaultById_returnsTheControllerForTheConfiguredId() throws SkyflowException {
449+
Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build();
450+
Assert.assertSame(client.vault(), client.vault("vault1"));
451+
}
452+
453+
@Test
454+
public void testVaultById_selectsTheMatchingVaultAmongSeveral() throws SkyflowException {
455+
VaultConfig first = buildConfig("vault1", "cluster1");
456+
first.setVaultUrl("https://first.example.com");
457+
VaultConfig second = buildConfig("vault2", "cluster2");
458+
second.setVaultUrl("https://second.example.com");
459+
Skyflow client = Skyflow.builder().addVaultConfig(first).addVaultConfig(second).build();
460+
461+
Assert.assertEquals("https://first.example.com", client.vault("vault1").currentVaultURL);
462+
Assert.assertEquals("https://second.example.com", client.vault("vault2").currentVaultURL);
463+
}
464+
465+
@Test
466+
public void testVaultById_nullIdResolvesToTheFirstConfiguredVault() throws SkyflowException {
467+
Skyflow client = Skyflow.builder()
468+
.addVaultConfig(buildConfig("vault1", "cluster1"))
469+
.addVaultConfig(buildConfig("vault2", "cluster2"))
470+
.build();
471+
Assert.assertSame(client.vault(), client.vault(null));
472+
}
473+
474+
@Test
475+
public void testVaultById_throwsForUnknownVaultId() throws SkyflowException {
476+
Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build();
477+
try {
478+
client.vault("vault-unknown");
479+
Assert.fail(EXCEPTION_NOT_THROWN);
480+
} catch (SkyflowException e) {
481+
Assert.assertNotNull(e.getMessage());
482+
}
483+
}
484+
485+
@Test
486+
public void testVaultById_throwsWhenNoConfigExists() {
487+
try {
488+
Skyflow.builder().build().vault("vault1");
489+
Assert.fail(EXCEPTION_NOT_THROWN);
490+
} catch (SkyflowException e) {
491+
Assert.assertNotNull(e.getMessage());
492+
}
493+
}
494+
495+
@Test
496+
public void testVaultById_removedVaultThrowsWhileOthersStillResolve() throws SkyflowException {
497+
Skyflow client = Skyflow.builder()
498+
.addVaultConfig(buildConfig("vault1", "cluster1"))
499+
.addVaultConfig(buildConfig("vault2", "cluster2"))
500+
.build();
501+
client.removeVaultConfig("vault1");
502+
503+
Assert.assertNotNull(client.vault("vault2"));
504+
try {
505+
client.vault("vault1");
506+
Assert.fail(EXCEPTION_NOT_THROWN);
507+
} catch (SkyflowException e) {
508+
Assert.assertNotNull(e.getMessage());
509+
}
510+
}
511+
445512
// ── getVaultConfig ────────────────────────────────────────────────────────
446513

447514
@Test

‎flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,14 @@ public void testBulkInsert_interceptorAddsCustomHeader() throws Exception {
197197
records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build());
198198
BulkInsertRequest request = BulkInsertRequest.builder().records(records).build();
199199

200-
RequestInterceptor interceptor = ctx -> ctx.addHeader(CustomHeaderKey.SkyflowAccountId, "acct-123");
200+
RequestInterceptor interceptor = ctx -> ctx.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "acct-123");
201201
BulkInsertOptions options = BulkInsertOptions.builder().interceptor(interceptor).build();
202202

203203
controller.bulkInsert(request, options);
204204

205205
ArgumentCaptor<RequestOptions> captor = ArgumentCaptor.forClass(RequestOptions.class);
206206
Mockito.verify(mockRaw).insert(any(), captor.capture());
207-
Assert.assertEquals("acct-123", captor.getValue().getHeaders().get(CustomHeaderKey.SkyflowAccountId.toString()));
207+
Assert.assertEquals("acct-123", captor.getValue().getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID.toString()));
208208
}
209209

210210
// ── bulkDetokenize ────────────────────────────────────────────────────────
@@ -741,7 +741,7 @@ public void intercept(com.skyflow.vault.data.RequestContext context) {
741741
callNumber = contexts.size();
742742
contexts.add(context);
743743
}
744-
context.addHeader(CustomHeaderKey.SkyflowAccountId, "batch-" + callNumber);
744+
context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "batch-" + callNumber);
745745
}
746746

747747
int callCount() {
@@ -781,7 +781,7 @@ private static void assertInterceptorRanOncePerBatch(CountingInterceptor interce
781781
Assert.assertEquals(EXPECTED_BATCH_COUNT, capturedOptions.size());
782782
java.util.Set<String> headerValues = new java.util.HashSet<>();
783783
for (RequestOptions options : capturedOptions) {
784-
String value = options.getHeaders().get(CustomHeaderKey.SkyflowAccountId.toString());
784+
String value = options.getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID.toString());
785785
Assert.assertNotNull("Interceptor header missing on a batch", value);
786786
headerValues.add(value);
787787
}

‎flowvault/src/test/java/com/skyflow/vault/data/RequestContextTests.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ public void testFirstBatchIsZeroNotMinusOne() {
3838
@Test
3939
public void testHeadersStillWorkAlongsideTheBatchPosition() {
4040
RequestContext context = new RequestContext("DETOKENIZE", 1, 3);
41-
context.addHeader(CustomHeaderKey.RequestIdHeader, "req-" + context.getBatchIndex());
41+
context.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "req-" + context.getBatchIndex());
4242

43-
Assert.assertEquals("req-1", context.getHeaders().get(CustomHeaderKey.RequestIdHeader));
43+
Assert.assertEquals("req-1", context.getHeaders().get(CustomHeaderKey.REQUEST_ID_HEADER));
4444
}
4545

4646
@Test
4747
public void testHeadersRemainUnmodifiable() {
4848
RequestContext context = new RequestContext("INSERT", 0, 1);
4949
try {
50-
context.getHeaders().put(CustomHeaderKey.RequestIdHeader, "x");
50+
context.getHeaders().put(CustomHeaderKey.REQUEST_ID_HEADER, "x");
5151
Assert.fail("the exposed header map must not be mutable");
5252
} catch (UnsupportedOperationException expected) {
5353
Assert.assertTrue(true);

0 commit comments

Comments
 (0)