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 021d9cdb..88ee3efb 100644 Binary files a/flowvault/api-report/skyflow-flowvault-java.baseline.jar and b/flowvault/api-report/skyflow-flowvault-java.baseline.jar differ 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