Result.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // Copyright (c) 2015 Rob Rix. All rights reserved.
  2. /// An enum representing either a failure with an explanatory error, or a success with a result value.
  3. public enum Result<T, Error: ResultErrorType>: ResultType, CustomStringConvertible, CustomDebugStringConvertible {
  4. case Success(T)
  5. case Failure(Error)
  6. // MARK: Constructors
  7. /// Constructs a success wrapping a `value`.
  8. public init(value: T) {
  9. self = .Success(value)
  10. }
  11. /// Constructs a failure wrapping an `error`.
  12. public init(error: Error) {
  13. self = .Failure(error)
  14. }
  15. /// Constructs a result from an Optional, failing with `Error` if `nil`.
  16. #if swift(>=3)
  17. public init(_ value: T?, failWith: @autoclosure () -> Error) {
  18. self = value.map(Result.Success) ?? .Failure(failWith())
  19. }
  20. #else
  21. public init(_ value: T?, @autoclosure failWith: () -> Error) {
  22. self = value.map(Result.Success) ?? .Failure(failWith())
  23. }
  24. #endif
  25. /// Constructs a result from a function that uses `throw`, failing with `Error` if throws.
  26. #if swift(>=3)
  27. public init(_ f: @autoclosure () throws -> T) {
  28. self.init(attempt: f)
  29. }
  30. #else
  31. public init(@autoclosure _ f: () throws -> T) {
  32. self.init(attempt: f)
  33. }
  34. #endif
  35. /// Constructs a result from a function that uses `throw`, failing with `Error` if throws.
  36. #if swift(>=3)
  37. public init(attempt f: @noescape () throws -> T) {
  38. do {
  39. self = .Success(try f())
  40. } catch {
  41. self = .Failure(error as! Error)
  42. }
  43. }
  44. #else
  45. public init(@noescape attempt f: () throws -> T) {
  46. do {
  47. self = .Success(try f())
  48. } catch {
  49. self = .Failure(error as! Error)
  50. }
  51. }
  52. #endif
  53. // MARK: Deconstruction
  54. /// Returns the value from `Success` Results or `throw`s the error.
  55. public func dematerialize() throws -> T {
  56. switch self {
  57. case let .Success(value):
  58. return value
  59. case let .Failure(error):
  60. throw error
  61. }
  62. }
  63. /// Case analysis for Result.
  64. ///
  65. /// Returns the value produced by applying `ifFailure` to `Failure` Results, or `ifSuccess` to `Success` Results.
  66. #if swift(>=3)
  67. public func analysis<Result>(ifSuccess: @noescape (T) -> Result, ifFailure: @noescape (Error) -> Result) -> Result {
  68. switch self {
  69. case let .Success(value):
  70. return ifSuccess(value)
  71. case let .Failure(value):
  72. return ifFailure(value)
  73. }
  74. }
  75. #else
  76. public func analysis<Result>(@noescape ifSuccess ifSuccess: T -> Result, @noescape ifFailure: Error -> Result) -> Result {
  77. switch self {
  78. case let .Success(value):
  79. return ifSuccess(value)
  80. case let .Failure(value):
  81. return ifFailure(value)
  82. }
  83. }
  84. #endif
  85. // MARK: Errors
  86. /// The domain for errors constructed by Result.
  87. public static var errorDomain: String { return "com.antitypical.Result" }
  88. /// The userInfo key for source functions in errors constructed by Result.
  89. public static var functionKey: String { return "\(errorDomain).function" }
  90. /// The userInfo key for source file paths in errors constructed by Result.
  91. public static var fileKey: String { return "\(errorDomain).file" }
  92. /// The userInfo key for source file line numbers in errors constructed by Result.
  93. public static var lineKey: String { return "\(errorDomain).line" }
  94. #if os(Linux)
  95. private typealias UserInfoType = Any
  96. #else
  97. private typealias UserInfoType = AnyObject
  98. #endif
  99. /// Constructs an error.
  100. #if swift(>=3)
  101. public static func error(_ message: String? = nil, function: String = #function, file: String = #file, line: Int = #line) -> NSError {
  102. var userInfo: [String: UserInfoType] = [
  103. functionKey: function,
  104. fileKey: file,
  105. lineKey: line,
  106. ]
  107. if let message = message {
  108. userInfo[NSLocalizedDescriptionKey] = message
  109. }
  110. return NSError(domain: errorDomain, code: 0, userInfo: userInfo)
  111. }
  112. #else
  113. public static func error(message: String? = nil, function: String = #function, file: String = #file, line: Int = #line) -> NSError {
  114. var userInfo: [String: UserInfoType] = [
  115. functionKey: function,
  116. fileKey: file,
  117. lineKey: line,
  118. ]
  119. if let message = message {
  120. userInfo[NSLocalizedDescriptionKey] = message
  121. }
  122. return NSError(domain: errorDomain, code: 0, userInfo: userInfo)
  123. }
  124. #endif
  125. // MARK: CustomStringConvertible
  126. public var description: String {
  127. return analysis(
  128. ifSuccess: { ".Success(\($0))" },
  129. ifFailure: { ".Failure(\($0))" })
  130. }
  131. // MARK: CustomDebugStringConvertible
  132. public var debugDescription: String {
  133. return description
  134. }
  135. }
  136. // MARK: - Derive result from failable closure
  137. #if swift(>=3)
  138. public func materialize<T>(_ f: @noescape () throws -> T) -> Result<T, NSError> {
  139. return materialize(try f())
  140. }
  141. public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, NSError> {
  142. do {
  143. return .Success(try f())
  144. } catch let error as NSError {
  145. return .Failure(error)
  146. }
  147. }
  148. #else
  149. public func materialize<T>(@noescape f: () throws -> T) -> Result<T, NSError> {
  150. return materialize(try f())
  151. }
  152. public func materialize<T>(@autoclosure f: () throws -> T) -> Result<T, NSError> {
  153. do {
  154. return .Success(try f())
  155. } catch let error as NSError {
  156. return .Failure(error)
  157. }
  158. }
  159. #endif
  160. // MARK: - Cocoa API conveniences
  161. #if !os(Linux)
  162. /// Constructs a Result with the result of calling `try` with an error pointer.
  163. ///
  164. /// This is convenient for wrapping Cocoa API which returns an object or `nil` + an error, by reference. e.g.:
  165. ///
  166. /// Result.try { NSData(contentsOfURL: URL, options: .DataReadingMapped, error: $0) }
  167. #if swift(>=3)
  168. public func `try`<T>(_ function: String = #function, file: String = #file, line: Int = #line, `try`: (NSErrorPointer) -> T?) -> Result<T, NSError> {
  169. var error: NSError?
  170. return `try`(&error).map(Result.Success) ?? .Failure(error ?? Result<T, NSError>.error(function: function, file: file, line: line))
  171. }
  172. #else
  173. public func `try`<T>(function: String = #function, file: String = #file, line: Int = #line, `try`: NSErrorPointer -> T?) -> Result<T, NSError> {
  174. var error: NSError?
  175. return `try`(&error).map(Result.Success) ?? .Failure(error ?? Result<T, NSError>.error(function: function, file: file, line: line))
  176. }
  177. #endif
  178. /// Constructs a Result with the result of calling `try` with an error pointer.
  179. ///
  180. /// This is convenient for wrapping Cocoa API which returns a `Bool` + an error, by reference. e.g.:
  181. ///
  182. /// Result.try { NSFileManager.defaultManager().removeItemAtURL(URL, error: $0) }
  183. #if swift(>=3)
  184. public func `try`(_ function: String = #function, file: String = #file, line: Int = #line, `try`: (NSErrorPointer) -> Bool) -> Result<(), NSError> {
  185. var error: NSError?
  186. return `try`(&error) ?
  187. .Success(())
  188. : .Failure(error ?? Result<(), NSError>.error(function: function, file: file, line: line))
  189. }
  190. #else
  191. public func `try`(function: String = #function, file: String = #file, line: Int = #line, `try`: NSErrorPointer -> Bool) -> Result<(), NSError> {
  192. var error: NSError?
  193. return `try`(&error) ?
  194. .Success(())
  195. : .Failure(error ?? Result<(), NSError>.error(function: function, file: file, line: line))
  196. }
  197. #endif
  198. #endif
  199. // MARK: - ErrorTypeConvertible conformance
  200. extension NSError: ErrorTypeConvertible {
  201. #if swift(>=3)
  202. public static func errorFromErrorType(_ error: ResultErrorType) -> Self {
  203. func cast<T: NSError>(_ error: ResultErrorType) -> T {
  204. return error as! T
  205. }
  206. return cast(error)
  207. }
  208. #else
  209. public static func errorFromErrorType(error: ResultErrorType) -> Self {
  210. func cast<T: NSError>(error: ResultErrorType) -> T {
  211. return error as! T
  212. }
  213. return cast(error)
  214. }
  215. #endif
  216. }
  217. // MARK: -
  218. /// An “error” that is impossible to construct.
  219. ///
  220. /// This can be used to describe `Result`s where failures will never
  221. /// be generated. For example, `Result<Int, NoError>` describes a result that
  222. /// contains an `Int`eger and is guaranteed never to be a `Failure`.
  223. public enum NoError: ResultErrorType { }
  224. import Foundation