diff --git a/README.md b/README.md index 2ad2537..74c63ff 100644 --- a/README.md +++ b/README.md @@ -165,11 +165,13 @@ let collectElementInput = Skyflow.CollectElementInput( inputStyles: Skyflow.Styles, // optional styles that should be applied to the form element labelStyles: Skyflow.Styles, // optional styles that will be applied to the label of the collect element errorTextStyles: Skyflow.Styles, // optional styles that will be applied to the errorText of the collect element + iconStyles: Skyflow.Styles, // optional styles that will be applied to the card icon of the collect element label: String, // optional label for the form element placeholder: String, // optional placeholder for the form element altText: String, // (DEPRECATED) optional that acts as an initial value for the collect element - validations: ValidationSet, // optional set of validations for the input element type: Skyflow.ElementType, // Skyflow.ElementType enum + validations: ValidationSet, // optional set of validations for the input element + skyflowId: String, // optional, the skyflow_id of the record to update ) ``` The `table` and `column` fields indicate which table and column in the vault the Element corresponds to. @@ -218,7 +220,7 @@ let styles = Skyflow.Styles( empty: style, // optional focus: style, // optional invalid: style, // optional - requiredAsterisk: style // optional + requiredAstrisk: style // optional ) ``` @@ -226,7 +228,7 @@ The `labelStyles` and `errorTextStyles` fields accept the above mentioned `Skyfl The states that are available for `labelStyles` are `base` and `focus`. -`requiredAsterisk`: Styles applied for the Asterisk symbol in the label. Defaults to red. +`requiredAstrisk`: Styles applied for the Asterisk symbol in the label. Defaults to red. The state that is available for `errorTextStyles` is only the `base` state, it shows up when there is some error in the collect element. @@ -238,7 +240,7 @@ The parameters in `Skyflow.Style` object that are respected for `label` and `err Other parameters in the `Skyflow.Style` object are ignored for `label` and `errorText` text views. -Finally, the `type` parameter takes a Skyflow.ElementType. Each type applies the appropriate regex and validations to the form element. There are currently 5 types: +Finally, the `type` parameter takes a Skyflow.ElementType. Each type applies the appropriate regex and validations to the form element. There are currently 8 types: - `INPUT_FIELD` - `CARDHOLDER_NAME` - `CARD_NUMBER` @@ -342,11 +344,13 @@ let collectElementInput = Skyflow.CollectElementInput( inputStyles: Skyflow.Styles, // optional styles that should be applied to the form element labelStyles: Skyflow.Styles, // optional styles that will be applied to the label of the collect element errorTextStyles: Skyflow.Styles, // optional styles that will be applied to the errorText of the collect element + iconStyles: Skyflow.Styles, // optional styles that will be applied to the card icon of the collect element label: String, // optional label for the form element placeholder: String, // optional placeholder for the form element altText: String, // (DEPRECATED) optional that acts as an initial value for the collect element - validations: ValidationSet, // optional set of validations for the input element type: Skyflow.ElementType, // Skyflow.ElementType enum + validations: ValidationSet, // optional set of validations for the input element + skyflowId: String, // optional, the skyflow_id of the record to update ) let collectElementOptions = Skyflow.CollectElementOptions( @@ -382,14 +386,14 @@ func clearFieldsOnSubmit(_ elements: [TextField]) { ``` #### Step 4 : Collect data from Elements -When you submit the form, call the `collect(callback: Skyflow.CollectCallback, options: Skyflow.CollectOptions? = nil)` method on the container object. The options parameter takes a `Skyflow.CollectOptions` object as shown below: +When you submit the form, call the `collect(callback: Skyflow.CollectCallback, options: Skyflow.CollectOptions? = Skyflow.CollectOptions())` method on the container object. The options parameter takes a `Skyflow.CollectOptions` object as shown below: ```swift // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"]) + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"]) ]) // Upsert -let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] +let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using `upsert` field of CollectOptions (optional) let options = Skyflow.CollectOptions(additionalFields: nonPCIRecords) @@ -460,11 +464,11 @@ let skyflowElement = container?.create(input: input, options: requiredOption) // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"]) + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"]) ]) //Upsert options - let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] + let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the Non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using optional field `upsert` of CollectOptions. let collectOptions = Skyflow.CollectOptions(additionalFields: nonPCIRecords, upsert: upsertOptions) @@ -499,16 +503,16 @@ container?.collect(callback: insertCallback, options: collectOptions) "records": [ { "tableName": "cards", - "skyflowID": "f1714ef8-8deb-489a-a18d-77e0e007f403", - "fields": { + "skyflowId": "f1714ef8-8deb-489a-a18d-77e0e007f403", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 }, { "tableName": "persons", - "skyflowID": "77dc3caf-c452-49e1-8625-07219d7567bf", - "fields": { + "skyflowId": "77dc3caf-c452-49e1-8625-07219d7567bf", + "tokens": { "gender": [{"token": "12f670af-6c7d-4837-83fb-30365fbc0b1e", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 @@ -525,15 +529,15 @@ Successful and failed records are both returned together in the same `records` a "records": [ { "tableName": "cards", - "skyflowID": "f1714ef8-8deb-489a-a18d-77e0e007f403", - "fields": { + "skyflowId": "f1714ef8-8deb-489a-a18d-77e0e007f403", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 }, { "error": "Invalid request. Table name table not present for record. Specify a valid table name.", - "skyflowID": null, + "skyflowId": null, "tableName": "", "httpCode": 400 } @@ -630,15 +634,15 @@ func clearFieldsOnSubmit(_ elements: [TextField]) { ### Step 4 : Update data from Elements When the form is ready to submit, call the `collect(options?)` method on the container object. The `options` parameter takes a object of optional parameters as shown below: - `additionalFields`: A `Skyflow.AdditionalFields` object - non-PCI records to update or insert into the vault alongside whatever's collected from the mounted elements. -- `upsert`: An array of `Skyflow.UpsertOption` objects to support upsert while collecting data from Skyflow elements. Each option specifies the `table`, the `uniqueColumns` used to match existing records, and an optional `updateType` (`UpdateType.UPDATE` merges the new fields into the matched record, `UpdateType.REPLACE` replaces it). +- `upsert`: An array of `Skyflow.UpsertOption` objects to support upsert while collecting data from Skyflow elements. Each option specifies the `tableName`, the `uniqueColumns` used to match existing records, and an optional `updateType` (`UpdateType.UPDATE` merges the new fields into the matched record, `UpdateType.REPLACE` replaces it). ```swift // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"], skyflowId: "value") + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"], skyflowId: "value") ]) // Upsert -let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] +let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using `upsert` field of CollectOptions (optional) let options = Skyflow.CollectOptions(additionalFields: nonPCIRecords) @@ -709,12 +713,12 @@ let skyflowElement = container?.create(input: input, options: requiredOption) // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"]), - Skyflow.AdditionalFieldsRecord(table: "cards", fields: ["first_name": "Joe"], skyflowId: "431eaa6c-5c15-4513-aa15-29f50babe882") + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"]), + Skyflow.AdditionalFieldsRecord(tableName: "cards", data: ["first_name": "Joe"], skyflowId: "431eaa6c-5c15-4513-aa15-29f50babe882") ]) //Upsert options - let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] + let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the Non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using optional field `upsert` of CollectOptions. let collectOptions = Skyflow.CollectOptions(additionalFields: nonPCIRecords, upsert: upsertOptions) @@ -748,16 +752,16 @@ container?.collect(callback: insertCallback, options: collectOptions) "records": [ { "tableName": "persons", - "skyflowID": "77dc3caf-c452-49e1-8625-07219d7567bf", - "fields": { + "skyflowId": "77dc3caf-c452-49e1-8625-07219d7567bf", + "tokens": { "gender": [{"token": "12f670af-6c7d-4837-83fb-30365fbc0b1e", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 }, { "tableName": "cards", - "skyflowID": "431eaa6c-5c15-4513-aa15-29f50babe882", - "fields": { + "skyflowId": "431eaa6c-5c15-4513-aa15-29f50babe882", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}], "first_name": [{"token": "131e70dc-6f76-4319-bdd3-96281e051051", "tokenGroupName": "deterministic_string"}] }, @@ -774,15 +778,15 @@ container?.collect(callback: insertCallback, options: collectOptions) "records": [ { "tableName": "cards", - "skyflowID": "431eaa6c-5c15-4513-aa15-29f50babe882", - "fields": { + "skyflowId": "431eaa6c-5c15-4513-aa15-29f50babe882", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 }, { "error": "Update failed. skyflow_ids [77dc3caf-c452-49e1-8625-07219d7567bf] are invalid. Specify valid Skyflow IDs.", - "skyflowID": null, + "skyflowId": null, "tableName": "", "httpCode": 400 } @@ -882,7 +886,7 @@ element!.on(eventName: Skyflow.EventName) { _ in } ``` -There are 4 events in `Skyflow.EventName` +4 of `Skyflow.EventName`'s events apply to an individual Element's `.on(eventName:)` listener: - `CHANGE` Change event is triggered when the Element's value changes. - `READY` @@ -891,6 +895,9 @@ There are 4 events in `Skyflow.EventName` Focus event is triggered when the Element gains focus - `BLUR` Blur event is triggered when the Element loses focus. + +`Skyflow.EventName` has a 5th case, `SUBMIT`, but it's a no-op on an individual Element's `.on(eventName:)` - it only fires on a Composable **container's** `.on(eventName:)`, see [Set an event listener on a composable container](#set-an-event-listener-on-a-composable-container). + The handler ```(state: [String: Any]) -> Void``` is a callback function you provide, that will be called when the event is fired with the state object as shown below. ```swift @@ -1106,11 +1113,13 @@ let composableElementInput = Skyflow.CollectElementInput( inputStyles: Skyflow.Styles, // optional styles that should be applied to the form element labelStyles: Skyflow.Styles, // optional styles that will be applied to the label of the collect element errorTextStyles: Skyflow.Styles, // optional styles that will be applied to the errorText of the collect element + iconStyles: Skyflow.Styles, // optional styles that will be applied to the card icon of the collect element label: String, // optional label for the form element placeholder: String, // optional placeholder for the form element altText: String, // (DEPRECATED) optional that acts as an initial value for the collect element - validations: ValidationSet, // optional set of validations for the input element type: Skyflow.ElementType, // Skyflow.ElementType enum + validations: ValidationSet, // optional set of validations for the input element + skyflowId: String, // optional, the skyflow_id of the record to update ) ``` The `table` and `column` fields indicate which table and column in the vault the Element correspond to. @@ -1193,6 +1202,8 @@ Skyflow.CollectElementOptions( enableCardIcon: Boolean, // Indicates whether card icon should be enabled (only for CARD_NUMBER inputs) format: String, // Format for the element translation: [Character: String] // Indicates the allowed data type value for format. + enableCopy: Boolean, // Indicates whether to enable the copy icon in collect elements to copy text to clipboard. Defaults to 'false' + cardMetaData: [String: [Skyflow.CardType]] // Optional, metadata to control card number element behavior. (only applicable for CARD_NUMBER ElementType). ) ``` - `required`: Indicates whether the field is marked as required or not. Default is `false`. @@ -1224,11 +1235,13 @@ let composableElementInput = Skyflow.CollectElementInput( inputStyles: Skyflow.Styles, // optional styles that should be applied to the form element labelStyles: Skyflow.Styles, // optional styles that will be applied to the label of the collect element errorTextStyles: Skyflow.Styles, // optional styles that will be applied to the errorText of the collect element + iconStyles: Skyflow.Styles, // optional styles that will be applied to the card icon of the collect element label: String, // optional label for the form element placeholder: String, // optional placeholder for the form element altText: String, // (DEPRECATED) optional that acts as an initial value for the collect element - validations: ValidationSet, // optional set of validations for the input element type: Skyflow.ElementType, // Skyflow.ElementType enum + validations: ValidationSet, // optional set of validations for the input element + skyflowId: String, // optional, the skyflow_id of the record to update ) let collectElementOptions = Skyflow.CollectElementOptions( @@ -1264,20 +1277,20 @@ func clearFieldsOnSubmit(_ elements: [TextField]) { } ``` ### Step 4: Collect data from elements -When you submit the form, call the `collect(callback: Skyflow.Callback, options: Skyflow.CollectOptions? = Skyflow.CollectOptions())` method on the container object. +When you submit the form, call the `collect(callback: Skyflow.CollectCallback, options: Skyflow.CollectOptions? = Skyflow.CollectOptions())` method on the container object. The options parameter takes a `Skyflow.CollectOptions` object as shown below: - `tokens`: Whether or not tokens for the collected data are returned. Defaults to 'true' - `additionalFields`: A `Skyflow.AdditionalFields` object - non-PCI records to insert into the vault alongside whatever's collected from the mounted elements. -- `upsert`: An array of `Skyflow.UpsertOption` objects to support upsert while collecting data from Skyflow elements. Each option specifies the `table`, the `uniqueColumns` used to match existing records, and an optional `updateType` (`UpdateType.UPDATE` merges the new fields into the matched record, `UpdateType.REPLACE` replaces it). +- `upsert`: An array of `Skyflow.UpsertOption` objects to support upsert while collecting data from Skyflow elements. Each option specifies the `tableName`, the `uniqueColumns` used to match existing records, and an optional `updateType` (`UpdateType.UPDATE` merges the new fields into the matched record, `UpdateType.REPLACE` replaces it). ```swift // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"]) + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"]) ]) // Upsert -let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] +let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using `upsert` field of CollectOptions (optional) let options = Skyflow.CollectOptions(additionalFields: nonPCIRecords, upsert: upsertOptions) @@ -1377,11 +1390,11 @@ do { // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"]) + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"]) ]) //Upsert options -let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] +let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the Non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using optional field `upsert` of CollectOptions. let collectOptions = Skyflow.CollectOptions(additionalFields: nonPCIRecords, upsert: upsertOptions) @@ -1413,16 +1426,16 @@ container?.collect(callback: insertCallback, options: collectOptions) "records": [ { "tableName": "cards", - "skyflowID": "f1714ef8-8deb-489a-a18d-77e0e007f403", - "fields": { + "skyflowId": "f1714ef8-8deb-489a-a18d-77e0e007f403", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 }, { "tableName": "persons", - "skyflowID": "77dc3caf-c452-49e1-8625-07219d7567bf", - "fields": { + "skyflowId": "77dc3caf-c452-49e1-8625-07219d7567bf", + "tokens": { "gender": [{"token": "12f670af-6c7d-4837-83fb-30365fbc0b1e", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 @@ -1438,15 +1451,15 @@ container?.collect(callback: insertCallback, options: collectOptions) "records": [ { "tableName": "cards", - "skyflowID": "f1714ef8-8deb-489a-a18d-77e0e007f403", - "fields": { + "skyflowId": "f1714ef8-8deb-489a-a18d-77e0e007f403", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 }, { "error": "Invalid request. Table name table not present for record. Specify a valid table name.", - "skyflowID": null, + "skyflowId": null, "tableName": "", "httpCode": 400 } @@ -1466,13 +1479,15 @@ element!.on(eventName: Skyflow.EventName) { _ in } ``` -The SDK supports four events: +4 of `Skyflow.EventName`'s events apply to an individual Element's `.on(eventName:)` listener: - CHANGE: Triggered when the Element's value changes. - READY: Triggered when the Element is fully rendered. - FOCUS: Triggered when the Element gains focus. - BLUR: Triggered when the Element loses focus. +`Skyflow.EventName` has a 5th case, `SUBMIT`, but it's a no-op on an individual Element's `.on(eventName:)` - it only fires on a Composable **container's** `.on(eventName:)`, see [Set an event listener on a composable container](#set-an-event-listener-on-a-composable-container) below. + The handler ```(state: [String: Any]) -> Void``` is a callback function you provide, that will be called when the event is fired with the state object as shown below. ```swift @@ -1717,12 +1732,13 @@ let composableElementInput = Skyflow.CollectElementInput( inputStyles: Skyflow.Styles, // optional styles that should be applied to the form element labelStyles: Skyflow.Styles, // optional styles that will be applied to the label of the collect element errorTextStyles: Skyflow.Styles, // optional styles that will be applied to the errorText of the collect element + iconStyles: Skyflow.Styles, // optional styles that will be applied to the card icon of the collect element label: String, // optional label for the form element placeholder: String, // optional placeholder for the form element altText: String, // (DEPRECATED) optional that acts as an initial value for the collect element - validations: ValidationSet, // optional set of validations for the input element type: Skyflow.ElementType, // Skyflow.ElementType enum - skyflowId: String, // The skyflow_id of the record to be updated. + validations: ValidationSet, // optional set of validations for the input element + skyflowId: String, // The skyflow_id of the record to be updated. ) ``` The `table` and `column` fields indicate which table and column in the vault the Element correspond to. @@ -1771,20 +1787,20 @@ func clearFieldsOnSubmit(_ elements: [TextField]) { } ``` ### Step 4: Update data from Elements -When you submit the form, call the `collect(callback: Skyflow.Callback, options: Skyflow.CollectOptions? = Skyflow.CollectOptions())` method on the container object. +When you submit the form, call the `collect(callback: Skyflow.CollectCallback, options: Skyflow.CollectOptions? = Skyflow.CollectOptions())` method on the container object. The options parameter takes a `Skyflow.CollectOptions` object as shown below: - `tokens`: Whether or not tokens for the collected data are returned. Defaults to 'true' - `additionalFields`: A `Skyflow.AdditionalFields` object - non-PCI records to insert into the vault alongside whatever's collected from the mounted elements. -- `upsert`: An array of `Skyflow.UpsertOption` objects to support upsert while collecting data from Skyflow elements. Each option specifies the `table`, the `uniqueColumns` used to match existing records, and an optional `updateType` (`UpdateType.UPDATE` merges the new fields into the matched record, `UpdateType.REPLACE` replaces it). +- `upsert`: An array of `Skyflow.UpsertOption` objects to support upsert while collecting data from Skyflow elements. Each option specifies the `tableName`, the `uniqueColumns` used to match existing records, and an optional `updateType` (`UpdateType.UPDATE` merges the new fields into the matched record, `UpdateType.REPLACE` replaces it). ```swift // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"]) + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"]) ]) // Upsert -let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] +let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using `upsert` field of CollectOptions (optional) let options = Skyflow.CollectOptions(additionalFields: nonPCIRecords, upsert: upsertOptions) @@ -1887,11 +1903,11 @@ do { // Non-PCI records let nonPCIRecords = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "persons", fields: ["gender": "MALE"], skyflowId: "77dc3caf-c452-49e1-8625-07219d7567bf") + Skyflow.AdditionalFieldsRecord(tableName: "persons", data: ["gender": "MALE"], skyflowId: "77dc3caf-c452-49e1-8625-07219d7567bf") ]) //Upsert options - let upsertOptions = [Skyflow.UpsertOption(table: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] + let upsertOptions = [Skyflow.UpsertOption(tableName: "cards", uniqueColumns: ["cardNumber"], updateType: .UPDATE)] // Send the Non-PCI records as additionalFields of CollectOptions (optional) and apply upsert using optional field `upsert` of CollectOptions. let collectOptions = Skyflow.CollectOptions(additionalFields: nonPCIRecords, upsert: upsertOptions) @@ -1921,16 +1937,16 @@ container?.collect(callback: insertCallback, options: collectOptions) "records": [ { "tableName": "persons", - "skyflowID": "77dc3caf-c452-49e1-8625-07219d7567bf", - "fields": { + "skyflowId": "77dc3caf-c452-49e1-8625-07219d7567bf", + "tokens": { "gender": [{"token": "12f670af-6c7d-4837-83fb-30365fbc0b1e", "tokenGroupName": "deterministic_string"}] }, "httpCode": 200 }, { "tableName": "cards", - "skyflowID": "431eaa6c-5c15-4513-aa15-29f50babe882", - "fields": { + "skyflowId": "431eaa6c-5c15-4513-aa15-29f50babe882", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}], "first_name": [{"token": "131e70dc-6f76-4319-bdd3-96281e051051", "tokenGroupName": "deterministic_string"}], "cvv": [{"token": "098834fe-de99-4fc8-abdf-88c18a28a2cf", "tokenGroupName": "deterministic_string"}] @@ -1947,8 +1963,8 @@ Successful and failed records are both returned together in the same `records` a "records": [ { "tableName": "cards", - "skyflowID": "431eaa6c-5c15-4513-aa15-29f50babe882", - "fields": { + "skyflowId": "431eaa6c-5c15-4513-aa15-29f50babe882", + "tokens": { "cardNumber": [{"token": "f3907186-e7e2-466f-91e5-48e12c2bcbc1", "tokenGroupName": "deterministic_string"}], "first_name": [{"token": "131e70dc-6f76-4319-bdd3-96281e051051", "tokenGroupName": "deterministic_string"}], "cvv": [{"token": "098834fe-de99-4fc8-abdf-88c18a28a2cf", "tokenGroupName": "deterministic_string"}] @@ -1957,7 +1973,7 @@ Successful and failed records are both returned together in the same `records` a }, { "error": "Update failed. skyflow_ids [77dc3caf-c452-49e1-8625-07219d7567bf] are invalid. Specify valid Skyflow IDs.", - "skyflowID": null, + "skyflowId": null, "tableName": "", "httpCode": 400 } @@ -2000,7 +2016,7 @@ let revealElementInput = Skyflow.RevealElementInput( inputStyles: Skyflow.Styles(), // optional, styles to be applied to the element labelStyles: Skyflow.Styles(), // optional, styles to be applied to the label of the reveal element errorTextStyles: Skyflow.Styles(), // optional styles that will be applied to the errorText of the reveal element - label: "cardNumber", // optional, label for the element, + label: "cardNumber", // required, label for the element, altText: "XXXX XXXX XXXX XXXX", // optional, string that is shown before reveal, will show token if it is not provided ) ``` diff --git a/Samples/CollectAndRevealSample/CollectAndRevealSample/CollectAndRevealViewController.swift b/Samples/CollectAndRevealSample/CollectAndRevealSample/CollectAndRevealViewController.swift index d360b5e..ab72cf7 100644 --- a/Samples/CollectAndRevealSample/CollectAndRevealSample/CollectAndRevealViewController.swift +++ b/Samples/CollectAndRevealSample/CollectAndRevealSample/CollectAndRevealViewController.swift @@ -181,8 +181,8 @@ class CollectAndRevealViewController: UIViewController { print("Record failed:", error, "httpCode:", result.httpCode) } } - if let fields = response.records.first?.fields { - updateRevealInputs(tokens: fields) + if let tokens = response.records.first?.tokens { + updateRevealInputs(tokens: tokens) } print("Successfully got response:", response) } diff --git a/Samples/ComposableElements/ComposableElements/ViewController.swift b/Samples/ComposableElements/ComposableElements/ViewController.swift index 9bc833b..a77458d 100644 --- a/Samples/ComposableElements/ComposableElements/ViewController.swift +++ b/Samples/ComposableElements/ComposableElements/ViewController.swift @@ -193,13 +193,13 @@ class ViewController: UIViewController { // If a record with a matching card_number already exists, its fields are merged // in (updateType: .UPDATE) instead of creating a duplicate row. let upsertOptions = [ - Skyflow.UpsertOption(table: "credit_cards", uniqueColumns: ["card_number"], updateType: .UPDATE) + Skyflow.UpsertOption(tableName: "credit_cards", uniqueColumns: ["card_number"], updateType: .UPDATE) ] // additionalFields: extra records submitted alongside whatever's collected from // the mounted elements, one entry per table. let additionalFields = Skyflow.AdditionalFields(records: [ - Skyflow.AdditionalFieldsRecord(table: "credit_cards", fields: ["billing_zip": "94105"]) + Skyflow.AdditionalFieldsRecord(tableName: "credit_cards", data: ["billing_zip": "94105"]) ]) container!.collect(callback: collectCallback, options: Skyflow.CollectOptions(additionalFields: additionalFields, upsert: upsertOptions)) @@ -212,8 +212,8 @@ class ViewController: UIViewController { print("Record failed:", error, "httpCode:", result.httpCode) } } - if let fields = response.records.first?.fields { - updateRevealInputs(tokens: fields) + if let tokens = response.records.first?.tokens { + updateRevealInputs(tokens: tokens) } print("Successfully got response:", response) } diff --git a/Samples/InputFormatting/InputFormatting/ViewController.swift b/Samples/InputFormatting/InputFormatting/ViewController.swift index fa93680..9b2ff04 100644 --- a/Samples/InputFormatting/InputFormatting/ViewController.swift +++ b/Samples/InputFormatting/InputFormatting/ViewController.swift @@ -218,8 +218,8 @@ class ViewController: UIViewController { print("Record failed:", error, "httpCode:", result.httpCode) } } - if let fields = response.records.first?.fields { - updateRevealInputs(tokens: fields) + if let tokens = response.records.first?.tokens { + updateRevealInputs(tokens: tokens) } print("Successfully got response:", response) } diff --git a/Samples/UpsertFeature/UpsertFeature/ViewController.swift b/Samples/UpsertFeature/UpsertFeature/ViewController.swift index 1e82b7c..072819f 100644 --- a/Samples/UpsertFeature/UpsertFeature/ViewController.swift +++ b/Samples/UpsertFeature/UpsertFeature/ViewController.swift @@ -169,7 +169,7 @@ class ViewController: UIViewController { @objc func submitForm() { let collectCallback = Skyflow.CollectCallback(onSuccess: updateSuccess, onFailure: updateFailure) - let upsertOptions = [Skyflow.UpsertOption(table: "persons", uniqueColumns: ["cardnumber"], updateType: .UPDATE)] + let upsertOptions = [Skyflow.UpsertOption(tableName: "persons", uniqueColumns: ["cardnumber"], updateType: .UPDATE)] container!.collect(callback: collectCallback, options: Skyflow.CollectOptions(upsert: upsertOptions)) } @@ -180,8 +180,8 @@ class ViewController: UIViewController { print("Record failed:", error, "httpCode:", result.httpCode) } } - if let fields = response.records.first?.fields { - updateRevealInputs(tokens: fields) + if let tokens = response.records.first?.tokens { + updateRevealInputs(tokens: tokens) } print("Successfully got response:", response) } diff --git a/Sources/Skyflow/collect/AdditionalFields.swift b/Sources/Skyflow/collect/AdditionalFields.swift index 62fd0c9..659ec24 100644 --- a/Sources/Skyflow/collect/AdditionalFields.swift +++ b/Sources/Skyflow/collect/AdditionalFields.swift @@ -8,14 +8,14 @@ import Foundation public struct AdditionalFieldsRecord { - public let table: String - public let fields: [String: Any] + public let tableName: String + public let data: [String: Any] // Present to update an existing record, absent to insert a new one. public let skyflowId: String? - public init(table: String, fields: [String: Any], skyflowId: String? = nil) { - self.table = table - self.fields = fields + public init(tableName: String, data: [String: Any], skyflowId: String? = nil) { + self.tableName = tableName + self.data = data self.skyflowId = skyflowId } } diff --git a/Sources/Skyflow/collect/CollectContainer.swift b/Sources/Skyflow/collect/CollectContainer.swift index 2a5b1d9..8696d75 100644 --- a/Sources/Skyflow/collect/CollectContainer.swift +++ b/Sources/Skyflow/collect/CollectContainer.swift @@ -108,10 +108,10 @@ public extension Container { } private func checkRecord(record: AdditionalFieldsRecord, index: Int) -> ErrorCodes? { - if record.table.isEmpty { + if record.tableName.isEmpty { return .EMPTY_TABLE_NAME() } - if record.fields.isEmpty { + if record.data.isEmpty { return .EMPTY_FIELDS_KEY(value: "\(index)") } return nil diff --git a/Sources/Skyflow/collect/CollectResponse.swift b/Sources/Skyflow/collect/CollectResponse.swift index 4aa6ee7..4aaea9f 100644 --- a/Sources/Skyflow/collect/CollectResponse.swift +++ b/Sources/Skyflow/collect/CollectResponse.swift @@ -19,9 +19,9 @@ public struct CollectResponse { // with its own httpCode. public struct CollectRecord { public let tableName: String? - public let skyflowID: String? + public let skyflowId: String? // Keyed by column name, e.g. "card_number": [{"token": "...", "tokenGroupName": "..."}]. - public let fields: [String: Any]? + public let tokens: [String: Any]? // Keyed by column name, e.g. "card_number": [{"data": "...", "hashName": "..."}]. public let hashedData: [String: Any]? public let httpCode: Int @@ -29,8 +29,12 @@ public struct CollectRecord { init(_ dict: [String: Any]) { self.tableName = dict["tableName"] as? String - self.skyflowID = dict["skyflowID"] as? String - self.fields = dict["fields"] as? [String: Any] + // Wire key is intentionally left as "skyflowID" here - only the public Swift-facing + // property name changed, not the response dict this reads from. + self.skyflowId = dict["skyflowID"] as? String + // Dict key is intentionally left as "fields" here - only the public Swift-facing + // property name changed, not the response dict this reads from. + self.tokens = dict["fields"] as? [String: Any] self.hashedData = dict["hashedData"] as? [String: Any] self.httpCode = dict["httpCode"] as? Int ?? 0 self.error = dict["error"] as? String diff --git a/Sources/Skyflow/collect/FlowVaultCollectRequestBody.swift b/Sources/Skyflow/collect/FlowVaultCollectRequestBody.swift index aa53482..e6dc76d 100644 --- a/Sources/Skyflow/collect/FlowVaultCollectRequestBody.swift +++ b/Sources/Skyflow/collect/FlowVaultCollectRequestBody.swift @@ -68,8 +68,8 @@ internal class FlowVaultCollectRequestBody { if let additionalFields = additionalFields { for entry in additionalFields.records { - let tableName = entry.table - let fields = entry.fields + let tableName = entry.tableName + let fields = entry.data // Matches the element-based check below: an empty string is treated the same as // absent, falling back to a plain insert rather than an update targeting "". if let skyflowId = entry.skyflowId, !skyflowId.isEmpty { diff --git a/Sources/Skyflow/collect/FlowVaultICOptions.swift b/Sources/Skyflow/collect/FlowVaultICOptions.swift index b4ba236..f72f47d 100644 --- a/Sources/Skyflow/collect/FlowVaultICOptions.swift +++ b/Sources/Skyflow/collect/FlowVaultICOptions.swift @@ -28,7 +28,7 @@ internal struct FlowVaultICOptions { } for (index, currUpsertOption) in self.upsert!.enumerated() { - if currUpsertOption.table == "" { + if currUpsertOption.tableName == "" { let errorCode = ErrorCodes.TABLE_NAME_IS_EMPTY_FOR_ATLEAST_ONE_UPSERT_OPTION(value: "\(index)") self.callback!.onFailure(errorCode.getErrorObject(contextOptions: self.contextOptions!)) return true diff --git a/Sources/Skyflow/collect/FlowVaultInsertRequestBody.swift b/Sources/Skyflow/collect/FlowVaultInsertRequestBody.swift index e2ef86f..0c35872 100644 --- a/Sources/Skyflow/collect/FlowVaultInsertRequestBody.swift +++ b/Sources/Skyflow/collect/FlowVaultInsertRequestBody.swift @@ -14,7 +14,7 @@ internal class FlowVaultInsertRequestBody { temp["data"] = record["fields"] if let tableName = record["table"] as? String { temp["tableName"] = tableName - if let upsertOptions = options.upsert, let match = upsertOptions.first(where: { $0.table == tableName }) { + if let upsertOptions = options.upsert, let match = upsertOptions.first(where: { $0.tableName == tableName }) { var upsertPayload: [String: Any] = ["uniqueColumns": match.uniqueColumns] if let updateType = match.updateType { upsertPayload["updateType"] = updateType.rawValue diff --git a/Sources/Skyflow/collect/UpsertOption.swift b/Sources/Skyflow/collect/UpsertOption.swift index 1801178..63d2d4d 100644 --- a/Sources/Skyflow/collect/UpsertOption.swift +++ b/Sources/Skyflow/collect/UpsertOption.swift @@ -12,12 +12,12 @@ public enum UpdateType: String { } public struct UpsertOption { - public let table: String + public let tableName: String public let uniqueColumns: [String] public let updateType: UpdateType? - public init(table: String, uniqueColumns: [String], updateType: UpdateType? = nil) { - self.table = table + public init(tableName: String, uniqueColumns: [String], updateType: UpdateType? = nil) { + self.tableName = tableName self.uniqueColumns = uniqueColumns self.updateType = updateType } diff --git a/Sources/Skyflow/composable/ComposableContainer.swift b/Sources/Skyflow/composable/ComposableContainer.swift index 68f1510..bbae810 100644 --- a/Sources/Skyflow/composable/ComposableContainer.swift +++ b/Sources/Skyflow/composable/ComposableContainer.swift @@ -292,10 +292,10 @@ public extension Container { } private func checkRecord(record: AdditionalFieldsRecord, index: Int) -> ErrorCodes? { - if record.table.isEmpty { + if record.tableName.isEmpty { return .EMPTY_TABLE_NAME() } - if record.fields.isEmpty { + if record.data.isEmpty { return .EMPTY_FIELDS_KEY(value: "\(index)") } return nil diff --git a/Sources/Skyflow/reveal/RevealResponse.swift b/Sources/Skyflow/reveal/RevealResponse.swift index 2b7b91c..f9baa0c 100644 --- a/Sources/Skyflow/reveal/RevealResponse.swift +++ b/Sources/Skyflow/reveal/RevealResponse.swift @@ -31,7 +31,7 @@ public struct RevealResponse { public struct RevealRecord { public let token: String? public let tokenGroupName: String? - // Keyed by whatever fields the vault includes (e.g. "skyflowID", "tableName"). + // Keyed by whatever fields the vault includes (e.g. "skyflowId", "tableName"). public let metadata: [String: Any]? public let httpCode: Int public let error: String? @@ -39,10 +39,22 @@ public struct RevealRecord { init(_ dict: [String: Any]) { self.token = dict["token"] as? String self.tokenGroupName = dict["tokenGroupName"] as? String - self.metadata = dict["metadata"] as? [String: Any] + self.metadata = RevealRecord.normalizeMetadataKeys(dict["metadata"] as? [String: Any]) self.httpCode = dict["httpCode"] as? Int ?? 0 self.error = dict["error"] as? String } + + // The vault sends this key as "skyflowID" - normalized here to "skyflowId" so metadata's + // casing is consistent with Skyflow.CollectRecord.skyflowId rather than exposing the raw + // wire casing directly. + private static func normalizeMetadataKeys(_ metadata: [String: Any]?) -> [String: Any]? { + guard let metadata = metadata else { return nil } + var normalized: [String: Any] = [:] + for (key, value) in metadata { + normalized[key == "skyflowID" ? "skyflowId" : key] = value + } + return normalized + } } // A ready-made Skyflow.Callback that unwraps the raw responseBody/error into RevealResponse diff --git a/Tests/skyflow-iOS-collectTests/skyflow_iOS_collectTests.swift b/Tests/skyflow-iOS-collectTests/skyflow_iOS_collectTests.swift index 152bedd..52ce210 100644 --- a/Tests/skyflow-iOS-collectTests/skyflow_iOS_collectTests.swift +++ b/Tests/skyflow-iOS-collectTests/skyflow_iOS_collectTests.swift @@ -757,7 +757,7 @@ final class skyflow_iOS_collectTests: XCTestCase { } func testCollectEmptyTableAddionalFields() { - let additionalFields = AdditionalFields(records: [AdditionalFieldsRecord(table: "", fields: ["field": "value"])]) + let additionalFields = AdditionalFields(records: [AdditionalFieldsRecord(tableName: "", data: ["field": "value"])]) let container = skyflow.container(type: ContainerType.COLLECT) let expectation = XCTestExpectation() @@ -770,7 +770,7 @@ final class skyflow_iOS_collectTests: XCTestCase { } func testCollectEmptyFieldsAddionalFields() { - let additionalFields = AdditionalFields(records: [AdditionalFieldsRecord(table: "table", fields: [:])]) + let additionalFields = AdditionalFields(records: [AdditionalFieldsRecord(tableName: "table", data: [:])]) let container = skyflow.container(type: ContainerType.COLLECT) let expectation = XCTestExpectation() @@ -795,7 +795,7 @@ final class skyflow_iOS_collectTests: XCTestCase { func testCollectEmptyUniqueColumnsUpsertOption() { let container = skyflow.container(type: ContainerType.COLLECT) - let upsertOptions = [UpsertOption(table: "card1", uniqueColumns: [])] + let upsertOptions = [UpsertOption(tableName: "card1", uniqueColumns: [])] let expectation = XCTestExpectation() let callback = DemoAPICallback(expectation: expectation) container?.collect(callback: callback.asCollectCallback, options: CollectOptions(upsert:upsertOptions)) @@ -807,7 +807,7 @@ final class skyflow_iOS_collectTests: XCTestCase { func testCollectEmptyTableNameUpsertOption() { let container = skyflow.container(type: ContainerType.COLLECT) - let upsertOptions = [UpsertOption(table: "", uniqueColumns: ["person"])] + let upsertOptions = [UpsertOption(tableName: "", uniqueColumns: ["person"])] let expectation = XCTestExpectation() let callback = DemoAPICallback(expectation: expectation) container?.collect(callback: callback.asCollectCallback, options: CollectOptions(upsert:upsertOptions)) @@ -839,7 +839,7 @@ final class skyflow_iOS_collectTests: XCTestCase { func testInsertEmptyUniqueColumnsUpsertOption() { let container = skyflow.container(type: ContainerType.COLLECT) - let upsertOptions = [UpsertOption(table: "card1", uniqueColumns: [])] + let upsertOptions = [UpsertOption(tableName: "card1", uniqueColumns: [])] let expectation = XCTestExpectation() let records = [ "records" : [[ @@ -860,7 +860,7 @@ final class skyflow_iOS_collectTests: XCTestCase { func testInsertEmptyTableNameUpsertOption() { let container = skyflow.container(type: ContainerType.COLLECT) - let upsertOptions = [UpsertOption(table: "", uniqueColumns: ["person"])] + let upsertOptions = [UpsertOption(tableName: "", uniqueColumns: ["person"])] let expectation = XCTestExpectation() let records = [ "records" : [[ diff --git a/Tests/skyflow-iOS-composableTests/skyflow_iOS_composableEelementsTests.swift b/Tests/skyflow-iOS-composableTests/skyflow_iOS_composableEelementsTests.swift index 19d8060..550e1c2 100644 --- a/Tests/skyflow-iOS-composableTests/skyflow_iOS_composableEelementsTests.swift +++ b/Tests/skyflow-iOS-composableTests/skyflow_iOS_composableEelementsTests.swift @@ -455,7 +455,7 @@ final class skyflow_iOS_composableEelementsTests: XCTestCase { } func testInsertEmptyTableNameForUpsertOption() { _ = skyflow.container(type: ContainerType.COMPOSABLE) - let upsertOptions = [UpsertOption(table: "", uniqueColumns: ["person"])] + let upsertOptions = [UpsertOption(tableName: "", uniqueColumns: ["person"])] let expectation = XCTestExpectation() let records = [ "records" : [[ diff --git a/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift b/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift index 813668c..56ce3c4 100644 --- a/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift +++ b/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift @@ -291,7 +291,7 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { let callback = DemoAPICallback(expectation: expectation) let fields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "persons", fields: ["cvv": "123", "name": "John Doe"]) + AdditionalFieldsRecord(tableName: "persons", data: ["cvv": "123", "name": "John Doe"]) ]) FlowVaultCollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], additionalFields: fields,callback: callback, contextOptions: ContextOptions(interface: .COLLECT_CONTAINER)) wait(for: [expectation], timeout: 10.0) @@ -317,8 +317,8 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { let callback = DemoAPICallback(expectation: expectation) let fields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "persons", fields: ["duplicate": "123", "name": "John Doe"]), - AdditionalFieldsRecord(table: "persons", fields: ["duplicate": "123"]) + AdditionalFieldsRecord(tableName: "persons", data: ["duplicate": "123", "name": "John Doe"]), + AdditionalFieldsRecord(tableName: "persons", data: ["duplicate": "123"]) ]) FlowVaultCollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], additionalFields: fields,callback: callback, contextOptions: ContextOptions(interface: .COLLECT_CONTAINER)) wait(for: [expectation], timeout: 10.0) @@ -343,8 +343,8 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { let callback = DemoAPICallback(expectation: expectation) let fields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "persons", fields: ["cvv": "123"]), - AdditionalFieldsRecord(table: "persons", fields: ["duplicate": "123"]) + AdditionalFieldsRecord(tableName: "persons", data: ["cvv": "123"]), + AdditionalFieldsRecord(tableName: "persons", data: ["duplicate": "123"]) ]) FlowVaultCollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], additionalFields: fields,callback: callback, contextOptions: ContextOptions(interface: .COLLECT_CONTAINER)) wait(for: [expectation], timeout: 10.0) @@ -401,7 +401,7 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { func testCreateRequestBodyWithSkyflowIDInAdditionalFields() { let additionalFields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "table1", fields: ["column1": "value1"], skyflowId: "id1") + AdditionalFieldsRecord(tableName: "table1", data: ["column1": "value1"], skyflowId: "id1") ]) let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Update via additionalFields")) let requestBody = FlowVaultCollectRequestBody.createRequestBody(elements: [], additionalFields: additionalFields, callback: callback, contextOptions: ContextOptions()) @@ -417,7 +417,7 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { // Matches element-based skyflowId handling: an empty string is treated the same as // absent (falls back to a plain insert), rather than creating an update targeting "". let additionalFields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "table1", fields: ["column1": "value1"], skyflowId: "") + AdditionalFieldsRecord(tableName: "table1", data: ["column1": "value1"], skyflowId: "") ]) let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Empty skyflowId falls back to insert")) let requestBody = FlowVaultCollectRequestBody.createRequestBody(elements: [], additionalFields: additionalFields, callback: callback, contextOptions: ContextOptions()) @@ -435,8 +435,8 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { // Two additionalFields entries that both target the same skyflowId should merge their // fields into a single update payload entry, not clobber or duplicate it. let additionalFields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "persons", fields: ["name": "John"], skyflowId: "id1"), - AdditionalFieldsRecord(table: "persons", fields: ["email": "john@example.com"], skyflowId: "id1") + AdditionalFieldsRecord(tableName: "persons", data: ["name": "John"], skyflowId: "id1"), + AdditionalFieldsRecord(tableName: "persons", data: ["email": "john@example.com"], skyflowId: "id1") ]) let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Merge additionalFields sharing a skyflowId")) let requestBody = FlowVaultCollectRequestBody.createRequestBody(elements: [], additionalFields: additionalFields, callback: callback, contextOptions: ContextOptions()) @@ -515,7 +515,7 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { window.addSubview(insertElement!) let additionalFields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "accounts", fields: ["status": "active"]) + AdditionalFieldsRecord(tableName: "accounts", data: ["status": "active"]) ]) let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Element insert + additionalFields insert")) @@ -544,7 +544,7 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { window.addSubview(updateElement!) let additionalFields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "accounts", fields: ["status": "active"]) + AdditionalFieldsRecord(tableName: "accounts", data: ["status": "active"]) ]) let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Element update + additionalFields insert")) @@ -581,8 +581,8 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { window.addSubview(updateElement!) let additionalFields = AdditionalFields(records: [ - AdditionalFieldsRecord(table: "accounts", fields: ["status": "active"]), - AdditionalFieldsRecord(table: "billing", fields: ["zip": "94105"], skyflowId: "id2") + AdditionalFieldsRecord(tableName: "accounts", data: ["status": "active"]), + AdditionalFieldsRecord(tableName: "billing", data: ["zip": "94105"], skyflowId: "id2") ]) let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Full combination")) @@ -600,7 +600,7 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { XCTAssertEqual((update["id2"] as! [String: Any])["table"] as! String, "billing") // Apply upsert on top, matching only the "cards" table. - let upsertOptions = [UpsertOption(table: "cards", uniqueColumns: ["card_number"], updateType: .UPDATE)] + let upsertOptions = [UpsertOption(tableName: "cards", uniqueColumns: ["card_number"], updateType: .UPDATE)] let wireBody = FlowVaultInsertRequestBody.createRequestBody(vaultID: "vault123", records: requestBody!, options: FlowVaultICOptions(upsert: upsertOptions)) let wireRecords = wireBody["records"] as! [[String: Any]] diff --git a/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift b/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift index 7f3662e..76ead7e 100644 --- a/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift +++ b/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift @@ -553,8 +553,8 @@ final class skyflow_iOS_collectUtilTests: XCTestCase { return } XCTAssertEqual(collectResponse.records.count, 2) - XCTAssertTrue(collectResponse.records.contains { $0.tableName == "cards" && $0.skyflowID == "insertedId" }) - XCTAssertTrue(collectResponse.records.contains { $0.tableName == "persons" && $0.skyflowID == "id1" }) + XCTAssertTrue(collectResponse.records.contains { $0.tableName == "cards" && $0.skyflowId == "insertedId" }) + XCTAssertTrue(collectResponse.records.contains { $0.tableName == "persons" && $0.skyflowId == "id1" }) } func testInsertSucceedsButUpdateFailsSurfacesAsSkyflowError() { diff --git a/Tests/skyflow-iOS-utilTests/skyflow_iOS_revealUtilTests.swift b/Tests/skyflow-iOS-utilTests/skyflow_iOS_revealUtilTests.swift index 66730bb..9c01c42 100644 --- a/Tests/skyflow-iOS-utilTests/skyflow_iOS_revealUtilTests.swift +++ b/Tests/skyflow-iOS-utilTests/skyflow_iOS_revealUtilTests.swift @@ -696,6 +696,32 @@ final class skyflow_iOS_revealUtilTests: XCTestCase { XCTAssertEqual(failure.httpCode, 404) } + // The vault sends metadata's skyflowId field as "skyflowID" on the wire - RevealRecord + // normalizes it to "skyflowId" so it's consistent with Skyflow.CollectRecord.skyflowId, + // rather than exposing the raw wire casing to consumers. + func testRevealRecordNormalizesSkyflowIDKeyInMetadata() { + let record = RevealRecord(["token": "abc", "httpCode": 200, "metadata": ["skyflowID": "SID", "tableName": "table"]]) + let metadata = record.metadata! + XCTAssertEqual(metadata["skyflowId"] as? String, "SID") + XCTAssertNil(metadata["skyflowID"]) + XCTAssertEqual(metadata["tableName"] as? String, "table") + } + + // Exact metadata shape reported from a real device run: {"skyflowID": "426279b9-...", + // "tableName": "table5"}. + func testRevealRecordNormalizesSkyflowIDKeyInMetadataWithReportedRealWorldValues() { + let record = RevealRecord([ + "token": "abc", + "httpCode": 200, + "metadata": ["skyflowID": "426279b9-efe0-44df-bc39-16839bc86e86", "tableName": "table5"] + ]) + print("metadata:", record.metadata as Any) + let metadata = record.metadata! + XCTAssertEqual(metadata["skyflowId"] as? String, "426279b9-efe0-44df-bc39-16839bc86e86") + XCTAssertNil(metadata["skyflowID"]) + XCTAssertEqual(metadata["tableName"] as? String, "table5") + } + func testRevealResponseInitReturnsNilForMalformedBody() { XCTAssertNil(RevealResponse("not a dictionary")) XCTAssertNil(RevealResponse(["typo": []])) diff --git a/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift b/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift index 28acb53..9ee85ed 100644 --- a/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift +++ b/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift @@ -70,7 +70,7 @@ final class skyflow_iOS_utilTests: XCTestCase { } func testConstructV2RequestBodyWithUpsert() { - let upsert = [UpsertOption(table: "table", uniqueColumns: ["field1"], updateType: .REPLACE)] + let upsert = [UpsertOption(tableName: "table", uniqueColumns: ["field1"], updateType: .REPLACE)] let result = FlowVaultInsertRequestBody.createRequestBody(vaultID: "vault123", records: ["records": [["table": "table", "fields": ["field1": "value1"]]]], options: FlowVaultICOptions(upsert: upsert)) let records = result["records"] as! [[String: Any]] let upsertPayload = records[0]["upsert"] as! [String: Any] @@ -82,7 +82,7 @@ final class skyflow_iOS_utilTests: XCTestCase { // updateType today), but the nil default is part of the public API - confirms // "updateType" is omitted from the wire payload rather than serialized as null. func testConstructV2RequestBodyWithUpsertAndNilUpdateType() { - let upsert = [UpsertOption(table: "table", uniqueColumns: ["field1"])] + let upsert = [UpsertOption(tableName: "table", uniqueColumns: ["field1"])] let result = FlowVaultInsertRequestBody.createRequestBody(vaultID: "vault123", records: ["records": [["table": "table", "fields": ["field1": "value1"]]]], options: FlowVaultICOptions(upsert: upsert)) let records = result["records"] as! [[String: Any]] let upsertPayload = records[0]["upsert"] as! [String: Any]