1*daeefc7dSWill Schurman // Copyright © 2021 650 Industries. All rights reserved. 2*daeefc7dSWill Schurman 3*daeefc7dSWill Schurman import Foundation 4*daeefc7dSWill Schurman 5*daeefc7dSWill Schurman public enum SerializerError: Error { 6*daeefc7dSWill Schurman case emptyKey 7*daeefc7dSWill Schurman case invalidCharacterInKey(key: String, character: Character) 8*daeefc7dSWill Schurman case invalidCharacterInString(string: String, character: Character) 9*daeefc7dSWill Schurman } 10*daeefc7dSWill Schurman 11*daeefc7dSWill Schurman private extension Character { fromHexnull12*daeefc7dSWill Schurman static func fromHex(_ hex: String) -> Character { 13*daeefc7dSWill Schurman // swiftlint:disable:next force_unwrapping 14*daeefc7dSWill Schurman return Character(Unicode.Scalar(Int(hex, radix: 16)!)!) 15*daeefc7dSWill Schurman } 16*daeefc7dSWill Schurman 17*daeefc7dSWill Schurman var isDigit: Bool { 18*daeefc7dSWill Schurman return self >= Character("0") && self <= Character("9") 19*daeefc7dSWill Schurman } 20*daeefc7dSWill Schurman 21*daeefc7dSWill Schurman var isLcAlpha: Bool { 22*daeefc7dSWill Schurman self >= Character("a") && self <= Character("z") 23*daeefc7dSWill Schurman } 24*daeefc7dSWill Schurman 25*daeefc7dSWill Schurman var isAlpha: Bool { 26*daeefc7dSWill Schurman return (self >= Character("A") && self <= Character("Z")) || isLcAlpha 27*daeefc7dSWill Schurman } 28*daeefc7dSWill Schurman } 29*daeefc7dSWill Schurman 30*daeefc7dSWill Schurman /** 31*daeefc7dSWill Schurman Derived from expo-structured-headers Android implementation. 32*daeefc7dSWill Schurman */ 33*daeefc7dSWill Schurman public class StringStringDictionarySerializer { checkKeynull34*daeefc7dSWill Schurman private static func checkKey(key: String) throws { 35*daeefc7dSWill Schurman guard !key.isEmpty else { 36*daeefc7dSWill Schurman throw SerializerError.emptyKey 37*daeefc7dSWill Schurman } 38*daeefc7dSWill Schurman 39*daeefc7dSWill Schurman for (i, c) in key.enumerated() { 40*daeefc7dSWill Schurman let failureCondition1 = i == 0 && (c != Character("*") && !c.isLcAlpha) 41*daeefc7dSWill Schurman let failureCondition2 = !(c.isLcAlpha || c.isDigit || c == Character("_") || c == Character("-") || c == Character(".") || c == Character("*")) 42*daeefc7dSWill Schurman if failureCondition1 || failureCondition2 { 43*daeefc7dSWill Schurman throw SerializerError.invalidCharacterInKey(key: key, character: c) 44*daeefc7dSWill Schurman } 45*daeefc7dSWill Schurman } 46*daeefc7dSWill Schurman } 47*daeefc7dSWill Schurman checkValuenull48*daeefc7dSWill Schurman private static func checkValue(value: String) throws { 49*daeefc7dSWill Schurman for c in value { 50*daeefc7dSWill Schurman if c < Character.fromHex("20") || c >= Character.fromHex("7f") { 51*daeefc7dSWill Schurman throw SerializerError.invalidCharacterInString(string: value, character: c) 52*daeefc7dSWill Schurman } 53*daeefc7dSWill Schurman } 54*daeefc7dSWill Schurman } 55*daeefc7dSWill Schurman serializeValuenull56*daeefc7dSWill Schurman private static func serializeValue(value: String) -> String { 57*daeefc7dSWill Schurman let escapedString = value.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") 58*daeefc7dSWill Schurman return "\"\(escapedString)\"" 59*daeefc7dSWill Schurman } 60*daeefc7dSWill Schurman serializenull61*daeefc7dSWill Schurman public static func serialize(dictionary: [String: String]) throws -> String { 62*daeefc7dSWill Schurman return try dictionary 63*daeefc7dSWill Schurman .map { (key: String, value: String) in 64*daeefc7dSWill Schurman try checkKey(key: key) 65*daeefc7dSWill Schurman try checkValue(value: value) 66*daeefc7dSWill Schurman return "\(key)=\(serializeValue(value: value))" 67*daeefc7dSWill Schurman } 68*daeefc7dSWill Schurman .joined(separator: ", ") 69*daeefc7dSWill Schurman } 70*daeefc7dSWill Schurman } 71