mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Dry up Parameter parsing logic
This commit is contained in:
parent
c2ed507d66
commit
bae2e8649d
2
Pods
2
Pods
|
@ -1 +1 @@
|
||||||
Subproject commit 7c62088f5a59230b1d3435c12ab4273b97c594e9
|
Subproject commit 653107b632ab7b3e8449bfaad591ac950eae41ff
|
|
@ -431,6 +431,7 @@
|
||||||
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
|
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
|
||||||
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
|
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
|
||||||
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
|
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
|
||||||
|
4C3EF7FD2107DDEE0007EBF7 /* ParamParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */; };
|
||||||
4C3EF802210918740007EBF7 /* SSKEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKEnvelopeTest.swift */; };
|
4C3EF802210918740007EBF7 /* SSKEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKEnvelopeTest.swift */; };
|
||||||
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; };
|
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; };
|
||||||
4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */; };
|
4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */; };
|
||||||
|
@ -1111,6 +1112,7 @@
|
||||||
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
|
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
|
||||||
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
|
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
|
||||||
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
|
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
|
||||||
|
4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParamParserTest.swift; sourceTree = "<group>"; };
|
||||||
4C3EF801210918740007EBF7 /* SSKEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKEnvelopeTest.swift; sourceTree = "<group>"; };
|
4C3EF801210918740007EBF7 /* SSKEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKEnvelopeTest.swift; sourceTree = "<group>"; };
|
||||||
4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = "<group>"; };
|
4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = "<group>"; };
|
||||||
4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContactDiscoveryOperationTest.swift; path = contact/ContactDiscoveryOperationTest.swift; sourceTree = "<group>"; };
|
4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContactDiscoveryOperationTest.swift; path = contact/ContactDiscoveryOperationTest.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2114,6 +2116,7 @@
|
||||||
4C3EF8002109184A0007EBF7 /* SSKTests */ = {
|
4C3EF8002109184A0007EBF7 /* SSKTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */,
|
||||||
4C3EF801210918740007EBF7 /* SSKEnvelopeTest.swift */,
|
4C3EF801210918740007EBF7 /* SSKEnvelopeTest.swift */,
|
||||||
);
|
);
|
||||||
path = SSKTests;
|
path = SSKTests;
|
||||||
|
@ -3463,6 +3466,7 @@
|
||||||
45666F581D9B2880008FE134 /* OWSScrubbingLogFormatterTest.m in Sources */,
|
45666F581D9B2880008FE134 /* OWSScrubbingLogFormatterTest.m in Sources */,
|
||||||
B660F6E01C29868000687D6E /* UtilTest.m in Sources */,
|
B660F6E01C29868000687D6E /* UtilTest.m in Sources */,
|
||||||
B660F6DA1C29868000687D6E /* ExceptionsTest.m in Sources */,
|
B660F6DA1C29868000687D6E /* ExceptionsTest.m in Sources */,
|
||||||
|
4C3EF7FD2107DDEE0007EBF7 /* ParamParserTest.swift in Sources */,
|
||||||
B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */,
|
B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */,
|
||||||
45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */,
|
45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */,
|
||||||
4C3EF802210918740007EBF7 /* SSKEnvelopeTest.swift in Sources */,
|
4C3EF802210918740007EBF7 /* SSKEnvelopeTest.swift in Sources */,
|
||||||
|
|
|
@ -110,7 +110,7 @@ public class MessageFetcherJob: NSObject {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let envelopes = messageDicts.map { buildEnvelope(messageDict: $0) }.filter { $0 != nil }.map { $0! }
|
let envelopes: [SSKEnvelope] = messageDicts.compactMap { buildEnvelope(messageDict: $0) }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
envelopes: envelopes,
|
envelopes: envelopes,
|
||||||
|
@ -119,53 +119,26 @@ public class MessageFetcherJob: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildEnvelope(messageDict: [String: Any]) -> SSKEnvelope? {
|
private func buildEnvelope(messageDict: [String: Any]) -> SSKEnvelope? {
|
||||||
|
do {
|
||||||
|
let params = ParamParser(dictionary: messageDict)
|
||||||
|
|
||||||
guard let typeInt = messageDict["type"] as? Int32 else {
|
let typeInt: Int32 = try params.required(key: "type")
|
||||||
Logger.error("\(self.logTag) message body didn't have type")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let type: SSKEnvelope.SSKEnvelopeType = SSKEnvelope.SSKEnvelopeType(rawValue: typeInt) else {
|
guard let type: SSKEnvelope.SSKEnvelopeType = SSKEnvelope.SSKEnvelopeType(rawValue: typeInt) else {
|
||||||
Logger.error("\(self.logTag) message body type was invalid")
|
Logger.error("\(self.logTag) `typeInt` was invalid: \(typeInt)")
|
||||||
return nil
|
throw ParamParser.ParseError.invalidFormat("type")
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let timestamp = messageDict["timestamp"] as? UInt64 else {
|
let timestamp: UInt64 = try params.required(key: "timestamp")
|
||||||
Logger.error("\(self.logTag) message body didn't have timestamp")
|
let source: String = try params.required(key: "source")
|
||||||
|
let sourceDevice: UInt32 = try params.required(key: "sourceDevice")
|
||||||
|
let legacyMessage = try params.optionalBase64EncodedData(key: "message")
|
||||||
|
let content: Data? = try params.optionalBase64EncodedData(key: "content")
|
||||||
|
|
||||||
|
return SSKEnvelope(timestamp: UInt64(timestamp), source: source, sourceDevice: sourceDevice, type: type, content: content, legacyMessage: legacyMessage)
|
||||||
|
} catch {
|
||||||
|
owsFail("\(self.logTag) in \(#function) error building envelope: \(error)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let source = messageDict["source"] as? String else {
|
|
||||||
Logger.error("\(self.logTag) message body didn't have source")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let sourceDevice = messageDict["sourceDevice"] as? UInt32 else {
|
|
||||||
Logger.error("\(self.logTag) message body didn't have sourceDevice")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let legacyMessage: Data? = {
|
|
||||||
if let encodedLegacyMessage = messageDict["message"] as? String {
|
|
||||||
Logger.debug("\(self.logTag) message body had legacyMessage")
|
|
||||||
if let legacyMessage = Data(base64Encoded: encodedLegacyMessage) {
|
|
||||||
return legacyMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
let content: Data? = {
|
|
||||||
if let encodedContent = messageDict["content"] as? String {
|
|
||||||
Logger.debug("\(self.logTag) message body had content")
|
|
||||||
if let content = Data(base64Encoded: encodedContent) {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
return SSKEnvelope(timestamp: timestamp, source: source, sourceDevice: sourceDevice, type: type, content: content, legacyMessage: legacyMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchUndeliveredMessages() -> Promise<(envelopes: [SSKEnvelope], more: Bool)> {
|
private func fetchUndeliveredMessages() -> Promise<(envelopes: [SSKEnvelope], more: Bool)> {
|
||||||
|
|
89
Signal/test/SSKTests/ParamParserTest.swift
Normal file
89
Signal/test/SSKTests/ParamParserTest.swift
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class ParamParserTest: XCTestCase {
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
let dict: [String: Any] = ["some_int": 11, "some_string": "asdf", "large_int": Int64.max, "negative_int": -10]
|
||||||
|
var parser: ParamParser {
|
||||||
|
return ParamParser(dictionary: dict)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
XCTAssertEqual(11, try parser.required(key: "some_int"))
|
||||||
|
XCTAssertEqual(11, try parser.optional(key: "some_int"))
|
||||||
|
|
||||||
|
let expectedString: String = "asdf"
|
||||||
|
XCTAssertEqual(expectedString, try parser.required(key: "some_string"))
|
||||||
|
XCTAssertEqual(expectedString, try parser.optional(key: "some_string"))
|
||||||
|
|
||||||
|
XCTAssertEqual(nil, try parser.optional(key: "does_not_exist") as String?)
|
||||||
|
XCTAssertThrowsError(try parser.required(key: "does_not_exist") as String)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNumeric() {
|
||||||
|
let expectedInt32: Int32 = 11
|
||||||
|
XCTAssertEqual(expectedInt32, try parser.required(key: "some_int"))
|
||||||
|
XCTAssertEqual(expectedInt32, try parser.optional(key: "some_int"))
|
||||||
|
|
||||||
|
let expectedInt64: Int64 = 11
|
||||||
|
XCTAssertEqual(expectedInt64, try parser.required(key: "some_int"))
|
||||||
|
XCTAssertEqual(expectedInt64, try parser.optional(key: "some_int"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNumericSizeFailures() {
|
||||||
|
XCTAssertThrowsError(try {
|
||||||
|
let _: Int32 = try parser.required(key: "large_int")
|
||||||
|
}())
|
||||||
|
|
||||||
|
XCTAssertThrowsError(try {
|
||||||
|
let _: Int32? = try parser.optional(key: "large_int")
|
||||||
|
}())
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try {
|
||||||
|
let _: Int64 = try parser.required(key: "large_int")
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNumericSignFailures() {
|
||||||
|
XCTAssertNoThrow(try {
|
||||||
|
let _: Int = try parser.required(key: "negative_int")
|
||||||
|
}())
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try {
|
||||||
|
let _: Int64 = try parser.required(key: "negative_int")
|
||||||
|
}())
|
||||||
|
|
||||||
|
XCTAssertThrowsError(try {
|
||||||
|
let _: UInt64 = try parser.required(key: "negative_int")
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBase64Data() {
|
||||||
|
let originalString = "asdf"
|
||||||
|
let utf8Data: Data = originalString.data(using: .utf8)!
|
||||||
|
let base64EncodedString = utf8Data.base64EncodedString()
|
||||||
|
|
||||||
|
let dict: [String: Any] = ["some_data": base64EncodedString]
|
||||||
|
let parser = ParamParser(dictionary: dict)
|
||||||
|
|
||||||
|
XCTAssertEqual(utf8Data, try parser.requiredBase64EncodedData(key: "some_data"))
|
||||||
|
XCTAssertEqual(utf8Data, try parser.optionalBase64EncodedData(key: "some_data"))
|
||||||
|
|
||||||
|
let data: Data = try! parser.requiredBase64EncodedData(key: "some_data")
|
||||||
|
let roundTripString = String(data: data, encoding: .utf8)
|
||||||
|
XCTAssertEqual(originalString, roundTripString)
|
||||||
|
}
|
||||||
|
}
|
|
@ -400,9 +400,11 @@ class CDSBatchOperation: OWSOperation {
|
||||||
throw ContactDiscoveryError.parseError(description: "missing response dict")
|
throw ContactDiscoveryError.parseError(description: "missing response dict")
|
||||||
}
|
}
|
||||||
|
|
||||||
let cipherText = try responseDict.expectBase64EncodedData(key: "data")
|
let params = ParamParser(dictionary: responseDict)
|
||||||
let initializationVector = try responseDict.expectBase64EncodedData(key: "iv")
|
|
||||||
let authTag = try responseDict.expectBase64EncodedData(key: "mac")
|
let cipherText = try params.requiredBase64EncodedData(key: "data")
|
||||||
|
let initializationVector = try params.requiredBase64EncodedData(key: "iv")
|
||||||
|
let authTag = try params.requiredBase64EncodedData(key: "mac")
|
||||||
|
|
||||||
guard let plainText = Cryptography.decryptAESGCM(withInitializationVector: initializationVector,
|
guard let plainText = Cryptography.decryptAESGCM(withInitializationVector: initializationVector,
|
||||||
ciphertext: cipherText,
|
ciphertext: cipherText,
|
||||||
|
@ -504,23 +506,3 @@ extension Array {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Dictionary where Key: Hashable {
|
|
||||||
|
|
||||||
enum DictionaryError: Error {
|
|
||||||
case missingField(Key)
|
|
||||||
case invalidFormat(Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func expectBase64EncodedData(key: Key) throws -> Data {
|
|
||||||
guard let encodedData = self[key] as? String else {
|
|
||||||
throw DictionaryError.missingField(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let data = Data(base64Encoded: encodedData) else {
|
|
||||||
throw DictionaryError.invalidFormat(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
126
SignalServiceKit/src/Util/ParamParser.swift
Normal file
126
SignalServiceKit/src/Util/ParamParser.swift
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// A DSL for parsing expected and optional values from a Dictionary, appropriate for
|
||||||
|
// validating a service response.
|
||||||
|
//
|
||||||
|
// Additionally it includes some helpers to DRY up common conversions.
|
||||||
|
//
|
||||||
|
// Rather than exhaustively enumerate accessors for types like `requireUInt32`, `requireInt64`, etc.
|
||||||
|
// We instead leverage generics at the call site.
|
||||||
|
//
|
||||||
|
// do {
|
||||||
|
// // Required
|
||||||
|
// let name: String = try paramParser.required(key: "name")
|
||||||
|
// let count: UInt32 = try paramParser.required(key: "count")
|
||||||
|
//
|
||||||
|
// // Optional
|
||||||
|
// let last_seen: Date? = try paramParser.optional(key: "last_seen")
|
||||||
|
//
|
||||||
|
// return Foo(name: name, count: count, isNew: lastSeen == nil)
|
||||||
|
// } catch {
|
||||||
|
// handleInvalidResponse(error: error)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
public class ParamParser {
|
||||||
|
public typealias Key = AnyHashable
|
||||||
|
|
||||||
|
let dictionary: Dictionary<Key, Any>
|
||||||
|
|
||||||
|
public init(dictionary: Dictionary<Key, Any>) {
|
||||||
|
self.dictionary = dictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Errors
|
||||||
|
|
||||||
|
public enum ParseError: Error {
|
||||||
|
case missingField(Key)
|
||||||
|
case invalidFormat(Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func invalid(key: Key) -> ParseError {
|
||||||
|
return ParseError.invalidFormat(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func missing(key: Key) -> ParseError {
|
||||||
|
return ParseError.missingField(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public API
|
||||||
|
|
||||||
|
public func required<T>(key: Key) throws -> T {
|
||||||
|
guard let value: T = try optional(key: key) else {
|
||||||
|
throw missing(key: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
public func optional<T>(key: Key) throws -> T? {
|
||||||
|
guard let someValue = dictionary[key] else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let typedValue = someValue as? T else {
|
||||||
|
throw invalid(key: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return typedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: FixedWidthIntegers (e.g. Int, Int32, UInt, UInt32, etc.)
|
||||||
|
|
||||||
|
// You can't blindly cast accross Integer types, so we need to specify and validate which Int type we want.
|
||||||
|
// In general, you'll find numeric types parsed into a Dictionary as `Int`.
|
||||||
|
|
||||||
|
public func required<T>(key: Key) throws -> T where T: FixedWidthInteger {
|
||||||
|
guard let value: T = try optional(key: key) else {
|
||||||
|
throw missing(key: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
public func optional<T>(key: Key) throws -> T? where T: FixedWidthInteger {
|
||||||
|
guard let someValue = dictionary[key] else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch someValue {
|
||||||
|
case let typedValue as T:
|
||||||
|
return typedValue
|
||||||
|
case let int as Int:
|
||||||
|
guard int >= T.min, int <= T.max else {
|
||||||
|
throw invalid(key: key)
|
||||||
|
}
|
||||||
|
return T(int)
|
||||||
|
default:
|
||||||
|
throw invalid(key: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Base64 Data
|
||||||
|
|
||||||
|
public func requiredBase64EncodedData(key: Key) throws -> Data {
|
||||||
|
guard let data: Data = try optionalBase64EncodedData(key: key) else {
|
||||||
|
throw ParseError.missingField(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
public func optionalBase64EncodedData(key: Key) throws -> Data? {
|
||||||
|
guard let encodedData: String = try self.optional(key: key) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = Data(base64Encoded: encodedData) else {
|
||||||
|
throw ParseError.invalidFormat(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue