From 3998545793dd3cc31e940ead411853db952b0528 Mon Sep 17 00:00:00 2001 From: Devesh Bhardwaj Date: Thu, 13 Aug 2026 21:13:29 +0530 Subject: [PATCH] SK-3061: revert getFields() to Map, keep getTokens() typed getTokens() itself (item 6, commit 8148504e) stays Map>. But getFields() delegating straight to it changed its return-type generics from Object to List - a needless japicmp break for a deprecated alias nobody should be adding new calls to anyway, and one this PR had flagged as avoidable but initially left in favor of just regenerating the baseline. Reverted getFields() to its original Map contract by rendering getTokens()'s typed data back into that raw shape via a new package-private Token.toRawTokens(Map>) - the inverse of Token.parseTokens(). Package-private keeps it outside the accessModifier=protected japicmp contract, so it doesn't itself become a compatibility commitment. The round trip is lossless for map-shaped wire input (the normal case) but lossy for the bare-value edge case parseTokens() also handles (a raw "tok-abc" string loses its way back to {"token": "tok-abc", "tokenGroupName": null} - still valid data, just not byte-identical to the original wire shape). Updated every test asserting on getFields() accordingly, and added direct toRawTokens() coverage (null input, single/multiple token groups, round-trip-with-map-input) in ResponseComponentTests. Regenerated flowvault/api-report/skyflow-flowvault-java.baseline.jar via scripts/contract-snapshot-update.sh flowvault - this supersedes the baseline regenerated in f326ec20, since that one still had getFields() returning the typed map. mvn -pl common,flowvault -am verify passes clean against it. Co-Authored-By: Claude Sonnet 5 --- flowvault/README.md | 2 +- .../skyflow-flowvault-java.baseline.jar | Bin 469240 -> 469540 bytes .../vault/data/InsertResponseRecord.java | 9 ++- .../java/com/skyflow/vault/data/Token.java | 31 ++++++++ .../java/com/skyflow/utils/UtilsTests.java | 10 ++- .../controller/VaultControllerTests.java | 6 +- .../skyflow/vault/data/BulkResponseTests.java | 10 ++- .../vault/data/ResponseComponentTests.java | 68 ++++++++++++++++-- 8 files changed, 122 insertions(+), 14 deletions(-) diff --git a/flowvault/README.md b/flowvault/README.md index da25cb02..e51f1baa 100644 --- a/flowvault/README.md +++ b/flowvault/README.md @@ -767,7 +767,7 @@ Every bulk response exposes `getSummary()` and `getRecords()`. The records list | `getError()` | failures only | Error message for this item. `null` means this item succeeded. | | `getRequestId()` | failures only | The `x-request-id` of the batch this item was in — quote it in support escalations. Items from the same batch share one id. | -The success payload sits alongside those fields on the same object: `getSkyflowId()`/`getTokens()`/`getData()` for insert (`getFields()` is a deprecated alias for `getTokens()`), `getValue()`/`getTokenGroupName()`/`getMetadata()` for detokenize, `getTokens()` for tokenize, `getToken()` for delete. +The success payload sits alongside those fields on the same object: `getSkyflowId()`/`getTokens()`/`getData()` for insert (`getFields()` is deprecated — it returns the same data in its original, pre-typed `Map` shape, not `getTokens()`'s `Token` objects), `getValue()`/`getTokenGroupName()`/`getMetadata()` for detokenize, `getTokens()` for tokenize, `getToken()` for delete. Summaries per operation: diff --git a/flowvault/api-report/skyflow-flowvault-java.baseline.jar b/flowvault/api-report/skyflow-flowvault-java.baseline.jar index 021d9cdb1a6519ada54953ae0526b221c6a746b0..88ee3efb155f6af79d0e811f17a5a16aa5ba9346 100644 GIT binary patch delta 18127 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 B-zNY7 diff --git a/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java index 57c7bcf9..f889437f 100644 --- a/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java +++ b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java @@ -55,12 +55,15 @@ public Map> getTokens() { } /** - * @deprecated Response key 'fields' is deprecated. Use {@link #getTokens()} instead. + * @deprecated Response key 'fields' is deprecated. Use {@link #getTokens()} instead. This + * still returns {@code Map}, matching its original (pre-typed) contract — + * see {@link Token#toRawTokens(Map)} for how {@link #getTokens()}'s typed data is rendered + * back into that generic shape. */ @Deprecated(since = "1.0.2", forRemoval = true) - public Map> getFields() { + public Map getFields() { LogUtil.printWarningLog(InfoLogs.DEPRECATED_INSERT_FIELDS_GETTER.getLog()); - return getTokens(); + return Token.toRawTokens(getTokens()); } public Map getData() { diff --git a/flowvault/src/main/java/com/skyflow/vault/data/Token.java b/flowvault/src/main/java/com/skyflow/vault/data/Token.java index e84efb8f..ed95403b 100644 --- a/flowvault/src/main/java/com/skyflow/vault/data/Token.java +++ b/flowvault/src/main/java/com/skyflow/vault/data/Token.java @@ -100,4 +100,35 @@ private static Token toToken(Object entry) { } return null; } + + /** + * The inverse of {@link #parseTokens(Map)}: renders parsed {@link Token} objects back into + * the generic {@code Map} shape {@code getFields()} returned before it was + * deprecated, for callers who haven't migrated to {@link InsertResponseRecord#getTokens()} + * yet. Each column's value becomes a {@code List>}, one map per + * {@code Token} with {@code "token"}/{@code "tokenGroupName"} keys — this doesn't reproduce + * the exact original wire shape (a single-group column may originally have been a bare + * value or an unwrapped map rather than a one-element list), since that distinction is lost + * once parsed, but it's a consistent, self-describing shape every caller can read the same + * way regardless of how many groups a column has. + * + *

Returns {@code null} when {@code tokens} is {@code null}. + */ + static Map toRawTokens(Map> tokens) { + if (tokens == null) { + return null; + } + Map raw = new LinkedHashMap<>(); + for (Map.Entry> entry : tokens.entrySet()) { + List> rawEntries = new ArrayList<>(); + for (Token token : entry.getValue()) { + Map rawEntry = new LinkedHashMap<>(); + rawEntry.put("token", token.getToken()); + rawEntry.put("tokenGroupName", token.getTokenGroupName()); + rawEntries.add(rawEntry); + } + raw.put(entry.getKey(), rawEntries); + } + return raw; + } } diff --git a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java index 2ba3effd..80fc8361 100644 --- a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java @@ -1704,8 +1704,14 @@ public void testFormatBulkInsertResponse_success() { // The wire type's raw tokens map is parsed into typed Token objects before reaching the // caller - see ResponseComponentTests's Token.parseTokens() tests for the parsing logic. Assert.assertEquals("tok-abc", inserted.getTokens().get("name").get(0).getToken()); - // getFields() is deprecated but still delegates to getTokens() for backward compatibility. - Assert.assertEquals("tok-abc", inserted.getFields().get("name").get(0).getToken()); + // getFields() is deprecated, and now renders that typed data back into its original + // Map shape rather than returning getTokens()'s value directly - a bare + // string column comes back as a one-element List instead of the original bare value, + // since that distinction is lost once the raw data is parsed into Token objects. + Object nameField = inserted.getFields().get("name"); + Map nameToken = (Map) ((List) nameField).get(0); + Assert.assertEquals("tok-abc", nameToken.get("token")); + Assert.assertNull(nameToken.get("tokenGroupName")); Assert.assertEquals(data, inserted.getData()); Assert.assertEquals(0, inserted.getIndex()); Assert.assertEquals(200, inserted.getHttpCode()); diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java index 7d668283..a7899f51 100644 --- a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java @@ -706,8 +706,10 @@ public void testBulkInsert_successWithListOfMapsTokenShape() throws Exception { BulkInsertResponseRecord inserted = response.getRecords().get(0); Assert.assertNotNull(inserted.getTokens()); - // getFields() is deprecated but still delegates to getTokens() for backward compatibility. - Assert.assertEquals(inserted.getTokens(), inserted.getFields()); + // getFields() is deprecated and now renders getTokens()'s typed data back into its + // original raw shape - a lossless round trip for this input, so it equals the raw map + // the mock returned in the first place. + Assert.assertEquals(tokens, inserted.getFields()); // The wire type's List token shape is parsed into typed Token objects - no casting. List field1Tokens = inserted.getTokens().get("field1"); Assert.assertEquals(1, field1Tokens.size()); diff --git a/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java b/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java index 8b58b873..031099b4 100644 --- a/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java @@ -73,8 +73,14 @@ public void testBulkInsertResponse_recordsPreserveIndexAndInheritedFields() { Assert.assertEquals("table1", actual.getTableName()); Assert.assertEquals("id-1", actual.getSkyflowId()); Assert.assertEquals(tokens, actual.getTokens()); - // getFields() is deprecated but still delegates to getTokens() for backward compatibility. - Assert.assertEquals(tokens, actual.getFields()); + // getFields() is deprecated, and now returns its original (pre-typed) shape - a Map rendered back from the typed getTokens() data, not getTokens()'s value itself. + Map rawToken = new HashMap<>(); + rawToken.put("token", "token-name"); + rawToken.put("tokenGroupName", "group1"); + Map expectedFields = new HashMap<>(); + expectedFields.put("name", Collections.singletonList(rawToken)); + Assert.assertEquals(expectedFields, actual.getFields()); Assert.assertEquals(data, actual.getData()); Assert.assertEquals(hashedData, actual.getHashedData()); Assert.assertEquals(200, actual.getHttpCode()); diff --git a/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java b/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java index b3dfa849..61462e86 100644 --- a/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java @@ -19,6 +19,18 @@ */ public class ResponseComponentTests { + // getFields()'s deprecated, pre-typed shape for a single column with one token group - + // {"name": [{"token": "tok-1", "tokenGroupName": "group1"}]} - matching + // tokens.put("name", Collections.singletonList(new Token("tok-1", "group1"))). + private static Map singleColumnRawFields() { + Map rawToken = new HashMap<>(); + rawToken.put("token", "tok-1"); + rawToken.put("tokenGroupName", "group1"); + Map rawFields = new HashMap<>(); + rawFields.put("name", Collections.singletonList(rawToken)); + return rawFields; + } + // Tests for Success and Summary were removed: the bulk insert response contract replaced // those classes with BulkInsertResponseRecord / BulkSummary, covered below. Token was removed // in the same rework, then reintroduced (with the same shape it had before) as the type @@ -42,8 +54,9 @@ public void testBulkInsertResponseRecord_gettersReturnConstructorValues() { Assert.assertEquals("persons", record.getTableName()); Assert.assertEquals("skyflow-id-1", record.getSkyflowId()); Assert.assertEquals(tokens, record.getTokens()); - // getFields() is deprecated but still delegates to getTokens() for backward compatibility. - Assert.assertEquals(tokens, record.getFields()); + // getFields() is deprecated, and now returns its original (pre-typed) shape - a Map rendered back from the typed getTokens() data, not getTokens()'s value itself. + Assert.assertEquals(singleColumnRawFields(), record.getFields()); Assert.assertEquals(data, record.getData()); Assert.assertEquals(hashedData, record.getHashedData()); Assert.assertEquals(200, record.getHttpCode()); @@ -67,7 +80,7 @@ public void testBulkInsertResponseRecord_deprecatedConstructorAndGetFieldsStillW 2, "persons", "skyflow-id-1", tokens, hashedData, 200, null, null); Assert.assertEquals(tokens, record.getTokens()); - Assert.assertEquals(tokens, record.getFields()); + Assert.assertEquals(singleColumnRawFields(), record.getFields()); Assert.assertNull(record.getData()); Assert.assertEquals(hashedData, record.getHashedData()); } @@ -89,7 +102,7 @@ public void testInsertResponseRecord_deprecatedConstructorDefaultsDataToNull() { Assert.assertEquals("persons", record.getTableName()); Assert.assertEquals("skyflow-id-1", record.getSkyflowId()); Assert.assertEquals(tokens, record.getTokens()); - Assert.assertEquals(tokens, record.getFields()); + Assert.assertEquals(singleColumnRawFields(), record.getFields()); Assert.assertNull(record.getData()); Assert.assertEquals(hashedData, record.getHashedData()); Assert.assertEquals(200, record.getHttpCode()); @@ -254,6 +267,53 @@ public void testParseTokens_skipsNullEntriesWithinAList() { Assert.assertEquals("tok-a", col1.get(0).getToken()); } + @Test + public void testToRawTokens_returnsNullWhenTokensIsNull() { + Assert.assertNull(Token.toRawTokens(null)); + } + + @Test + public void testToRawTokens_rendersEachTokenAsAMapWithBothKeys() { + Map> tokens = new HashMap<>(); + tokens.put("col1", Collections.singletonList(new Token("tok-a", "tg1"))); + + Map raw = Token.toRawTokens(tokens); + + List col1 = (List) raw.get("col1"); + Assert.assertEquals(1, col1.size()); + Map entry = (Map) col1.get(0); + Assert.assertEquals("tok-a", entry.get("token")); + Assert.assertEquals("tg1", entry.get("tokenGroupName")); + } + + @Test + public void testToRawTokens_rendersMultipleTokenGroupsAsSeparateMapEntries() { + Map> tokens = new HashMap<>(); + tokens.put("col1", Arrays.asList(new Token("tok-a", "tg1"), new Token("tok-b", "tg2"))); + + List col1 = (List) Token.toRawTokens(tokens).get("col1"); + + Assert.assertEquals(2, col1.size()); + Assert.assertEquals("tok-a", ((Map) col1.get(0)).get("token")); + Assert.assertEquals("tok-b", ((Map) col1.get(1)).get("token")); + } + + @Test + public void testToRawTokens_isTheInverseOfParseTokensForMapShapedInput() { + // parseTokens() followed by toRawTokens() round-trips losslessly when every column's raw + // value was already a {token, tokenGroupName} map (or list of them) - the shape toRawTokens + // always produces. Only the bare-value case (see UtilsTests) loses information on the way. + Map entry = new HashMap<>(); + entry.put("token", "tok-a"); + entry.put("tokenGroupName", "tg1"); + Map rawTokens = new HashMap<>(); + rawTokens.put("col1", Collections.singletonList(entry)); + + Map roundTripped = Token.toRawTokens(Token.parseTokens(rawTokens)); + + Assert.assertEquals(rawTokens, roundTripped); + } + // ── BulkSummary ────────────────────────────────────────────────────────── @Test