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