mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			143 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Swift
		
	
		
		
			
		
	
	
			143 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Swift
		
	
| 
											5 years ago
										 | // | ||
|  | //  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 | ||
|  |     } | ||
|  | 
 | ||
|  |     public convenience init?(responseObject: Any?) { | ||
|  |         guard let responseDict = responseObject as? [String: AnyObject] else { | ||
|  |             return nil | ||
|  |         } | ||
|  | 
 | ||
|  |         self.init(dictionary: responseDict) | ||
|  |     } | ||
|  | 
 | ||
|  |     // 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 !(someValue is NSNull) 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: Any = try optional(key: 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) | ||
|  |         } | ||
|  | 
 | ||
|  |         guard data.count > 0 else { | ||
|  |             return nil | ||
|  |         } | ||
|  | 
 | ||
|  |         return data | ||
|  |     } | ||
|  | } |