Skip to content

Commit

Permalink
Release 1.2.3 (#100)
Browse files Browse the repository at this point in the history
* Custom JSON Structure
  • Loading branch information
dmytrokhl authored Mar 16, 2020
1 parent ca9288c commit 7724954
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 123 deletions.
2 changes: 1 addition & 1 deletion VGSCollectSDK.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'VGSCollectSDK'
spec.version = '1.2.2'
spec.version = '1.2.3'
spec.summary = 'VGS Collect - is a product suite that allows customers to collect information securely without possession of it.'
spec.swift_version = '5.0'
spec.description = <<-DESC
Expand Down
55 changes: 55 additions & 0 deletions framework/Sources/VGSFramework/Core/Utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Utils.swift
// VGSFramework
//
// Created by Dima on 10.03.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import Foundation

/// Merge two <key:value> objects and their nested values. Returns [String: Any]. Values in d2 will override values in d1 if keys are same!!!!
func deepMerge(_ d1: [String: Any], _ d2: [String: Any]) -> [String: Any] {
var result = d1
for (k2, v2) in d2 {
if let v2 = v2 as? [String: Any], let v1 = result[k2] as? [String: Any] {
result[k2] = deepMerge(v1, v2)
} else {
result[k2] = v2
}
}
return result
}

/// Convert string key with separator into dictionary. Ex.: user.name : "Joe" -> ["user": ["name": " Joe"]]
func mapStringKVOToDictionary(key: String, value: Any, separator: String.Element) -> [String: Any] {
let components = key.split(separator: separator).map { String($0) }

var dict = [String: Any]()
// swiftlint:disable identifier_name
var i = components.count - 1

while i >= 0 {
if i == components.count - 1 {
dict[components[i]] = value
} else {
let newDict = [components[i]: dict]
dict = newDict
}
i -= 1
}
return dict
}

extension Dictionary {
/// Resturn JSON string representation of dictionary with sorted keys
@available(iOS 11.0, *)
var jsonStringRepresentation: String? {
guard let theJSONData = try? JSONSerialization.data(withJSONObject: self,
options: [.prettyPrinted, .sortedKeys]) else {
return nil
}

return String(data: theJSONData, encoding: .ascii)
}
}
72 changes: 72 additions & 0 deletions framework/Sources/VGSFramework/Core/VGSCollect+internal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// VGSCollect+internal.swift
// VGSFramework
//
// Created by Dima on 10.03.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import Foundation

internal extension VGSCollect {

/// Validate tenant id
class func tenantIDValid(_ tenantId: String) -> Bool {
return tenantId.isAlphaNumeric
}

/// Validate stored textfields input data
func validateStoredInputData() -> Error? {
return validate(storage.elements)
}

/// Validate specific textfields input data
func validate(_ input: [VGSTextField]) -> Error? {
var isRequiredErrorFields = [String]()
var isRequiredValidOnlyErrorFields = [String]()

for textField in input {
if textField.isRequired, textField.text.isNilOrEmpty {
isRequiredErrorFields.append(textField.fieldName)
}
if textField.isRequiredValidOnly && !textField.state.isValid {
isRequiredValidOnlyErrorFields.append(textField.fieldName)
}
}

if isRequiredErrorFields.count > 0 {
return VGSError(type: .inputDataRequired, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequired, description: "Input data can't be nil or empty", extraInfo: ["fields": isRequiredErrorFields]))
} else if isRequiredValidOnlyErrorFields.count > 0 {
return VGSError(type: .inputDataRequiredValidOnly, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequiredValid, description: "Input data should be valid only", extraInfo: ["fields": isRequiredValidOnlyErrorFields]))
}
return nil
}

/// Turns textfields data saved in Storage and extra data in format ready to submit
func mapStoredInputDataForSubmit(with extraData: [String: Any]? = nil) -> [String: Any] {

let textFieldsData: BodyData = storage.elements.reduce(into: BodyData()) { (dict, element) in
dict[element.fieldName] = element.rawText
}

var body = mapInputFieldsDataToDictionary(textFieldsData)

if let customData = extraData, customData.count != 0 {
// NOTE: If there are similar keys on same level, body values will override customvalues values for that keys
body = deepMerge(customData, body)
}

return body
}

/// Maps textfield string key with separator into nesting Dictionary
func mapInputFieldsDataToDictionary(_ body: [String: Any]) -> [String: Any] {
var resultDict = [String: Any]()
for (key, value) in body {
let mappedDict = mapStringKVOToDictionary(key: key, value: value, separator: ".")
let newDict = deepMerge(resultDict, mappedDict)
resultDict = newDict
}
return resultDict
}
}
47 changes: 2 additions & 45 deletions framework/Sources/VGSFramework/Core/VGSCollect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,64 +84,21 @@ extension VGSCollect {
extension VGSCollect {
public func submit(path: String, method: HTTPMethod = .post, extraData: [String: Any]? = nil, completion block:@escaping (_ data: JsonData?, _ error: Error?) -> Void) {

let elements = storage.elements
if let error = validate(elements) {
if let error = validateStoredInputData() {
block(nil, error)
return
}

var body: BodyData = elements.reduce(into: BodyData()) { (dict, element) in
dict[element.fieldName] = element.rawText
}

if extraData?.count != 0 {
extraData?.forEach { (key, value) in body[key] = value }
}
let body = mapStoredInputDataForSubmit(with: extraData)

apiClient.sendRequest(path: path, method: method, value: body) { (json, error) in

if let error = error {
block(json, error)
return
} else {
let allKeys = json?.keys
allKeys?.forEach({ key in
if let element = elements.filter({ $0.fieldName == key }).first {
element.token = json?[key] as? String
}
})
block(json, nil)
return
}
}
}
}

// MARK: - Validation
internal extension VGSCollect {

func validate(_ input: [VGSTextField]) -> Error? {
var isRequiredErrorFields = [String]()
var isRequiredValidOnlyErrorFields = [String]()

for textField in input {
if textField.isRequired, textField.text.isNilOrEmpty {
isRequiredErrorFields.append(textField.fieldName)
}
if textField.isRequiredValidOnly && !textField.state.isValid {
isRequiredValidOnlyErrorFields.append(textField.fieldName)
}
}

if isRequiredErrorFields.count > 0 {
return VGSError(type: .inputDataRequired, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequired, description: "Input data can't be nil or empty", extraInfo: ["fields": isRequiredErrorFields]))
} else if isRequiredValidOnlyErrorFields.count > 0 {
return VGSError(type: .inputDataRequiredValidOnly, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequiredValid, description: "Input data should be valid only", extraInfo: ["fields": isRequiredValidOnlyErrorFields]))
}
return nil
}

class func tenantIDValid(_ tenantId: String) -> Bool {
return tenantId.isAlphaNumeric
}
}
124 changes: 124 additions & 0 deletions framework/Tests/FrameworkTests/UtilsTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// UtilsTest.swift
// VGSFramework
//
// Created by Dima on 10.03.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import XCTest
@testable import VGSFramework

class UtilsTest: XCTestCase {

func testMapStringKVOToDictionary() {
let stringToMap = "user.data.card_number"
let val = "any"
let separator: String.Element = "."
let expectedResult = [
"user": [
"data": [
"card_number": val
]
]
]

let result = mapStringKVOToDictionary(key: stringToMap, value: val, separator: separator)
XCTAssertTrue(result.jsonStringRepresentation == expectedResult.jsonStringRepresentation)
}

func testMapStringKVOToDictionaryWithExtraSeparators() {
let stringToMap = ".user.data...card_number."
let val = "any"
let separator: String.Element = "."
let expectedResult = [
"user": [
"data": [
"card_number": val
]
]
]

let result = mapStringKVOToDictionary(key: stringToMap, value: val, separator: separator)
XCTAssertTrue(result.jsonStringRepresentation == expectedResult.jsonStringRepresentation)
}

func testMapStringKVOToDictionaryWithNoSeparators() {
let stringToMap = "card_number"
let val = ["anyValue": "anyKey"]
let separator: String.Element = "."
let expectedResult = [
"card_number": val
]

let result = mapStringKVOToDictionary(key: stringToMap, value: val, separator: separator)
XCTAssertTrue(result.jsonStringRepresentation == expectedResult.jsonStringRepresentation)
}

func testDeepMergeWithoutSimilarKeys() {
let d1 = [
"user": [
"data": [
"card_number": "any"
]
]
]

let d2 = [
"data": [
"user": [
"id": "any"
]
]
]

let expectedResult = [
"user": [
"data": [
"card_number": "any"
]
],
"data": [
"user": [
"id": "any"
]
]
]
let result = deepMerge(d1, d2)
XCTAssertTrue(expectedResult.jsonStringRepresentation == result.jsonStringRepresentation)
}

func testDeepMergeWithSimilarKeys() {
let d1 = [
"user": [
"data": [
"card_number": "any",
"Id": "CapitalId"
],
"accountNumber": 1111
]
]

let d2 = [
"user": [
"data": [
"id": "anyId"
],
"accountNumber": 2222
]
]

let expectedResult = [
"user": [
"data": [
"card_number": "any",
"id": "anyId",
"Id": "CapitalId"
],
"accountNumber": 2222
]
]
let result = deepMerge(d1, d2)
XCTAssertTrue(expectedResult.jsonStringRepresentation == result.jsonStringRepresentation)
}
}
Loading

0 comments on commit 7724954

Please sign in to comment.