├── .github └── workflows │ ├── docc.yml │ ├── nightly.yml │ └── swift.yml ├── .gitignore ├── LICENSE.md ├── Package.swift ├── README.md ├── Sources └── OptionalAPI │ ├── AndThen.swift │ ├── AsyncMaps.swift │ ├── Cast.swift │ ├── Filter.swift │ ├── Folds.swift │ ├── Maps.swift │ ├── OptionalCodable.swift │ ├── OptionalCollection.swift │ ├── OptionalError.swift │ ├── OptionalFreeFunctions.swift │ ├── OptionalProperties.swift │ ├── Or.swift │ ├── PrivacyInfo.xcprivacy │ └── RunWhen.swift ├── Tests ├── LinuxMain.swift └── OptionalAPITests │ ├── AndThenTests.swift │ ├── AsyncMapsTests.swift │ ├── CastTests.swift │ ├── FilterTests.swift │ ├── FreeFunctionsTest.swift │ ├── OptionalAPITests.swift │ ├── OptionalCodableTests.swift │ ├── RunWhenTests.swift │ ├── SUTs.swift │ └── TryAsyncMapsTests.swift └── renovate.json /.github/workflows/docc.yml: -------------------------------------------------------------------------------- 1 | # Check original post at 2 | # https://danielsaidi.com/blog/2024/03/10/automating-docc-for-a-swift-package-with-github-actions/ 3 | # for more info about this setup. 4 | 5 | name: DocC Runner 6 | 7 | on: 8 | push: 9 | branches: ["main", "master"] 10 | 11 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | # Allow one concurrent deployment 18 | concurrency: 19 | group: "pages" 20 | cancel-in-progress: true 21 | 22 | # A single job that builds and deploys the DocC documentation 23 | jobs: 24 | deploy: 25 | environment: 26 | name: github-pages 27 | url: $ 28 | runs-on: macos-13 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | - id: pages 33 | name: Setup Pages 34 | uses: actions/configure-pages@v5 35 | - name: Select Xcode 15.1 36 | uses: maxim-lobanov/setup-xcode@v1 37 | with: 38 | xcode-version: '15.1.0' 39 | - name: Build DocC 40 | run: | 41 | swift package resolve; 42 | 43 | xcodebuild docbuild -scheme OptionalAPI -derivedDataPath /tmp/docbuild -destination 'generic/platform=iOS'; 44 | 45 | $(xcrun --find docc) process-archive \ 46 | transform-for-static-hosting /tmp/docbuild/Build/Products/Debug-iphoneos/OptionalAPI.doccarchive \ 47 | --output-path docs \ 48 | --hosting-base-path 'OptionalAPI'; 49 | 50 | echo "" > docs/index.html; 51 | - name: Upload artifact 52 | uses: actions/upload-pages-artifact@v3 53 | with: 54 | path: 'docs' 55 | - id: deployment 56 | name: Deploy to GitHub Pages 57 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly build and test 2 | 3 | on: 4 | schedule: 5 | - cron: '42 0 * * *' 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: macos-13 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Build 17 | run: swift build -v 18 | 19 | - name: Run tests 20 | run: swift test -v 21 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Merge or commit 2 | 3 | on: 4 | push: 5 | branches: ["master", "main", "develop"] 6 | pull_request: 7 | branches: ["master", "main", "develop"] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-13 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Build 19 | run: swift build -v 20 | 21 | - name: Run tests 22 | run: swift test -v 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Skip to content 2 | Search or jump to… 3 | 4 | Pull requests 5 | Issues 6 | Marketplace 7 | Explore 8 | 9 | github 10 | / 11 | gitignore 12 | 3.1k 13 | 102k 14 | 53.9k 15 | Code 16 | Pull requests 132 Actions 17 | Projects 0 18 | Security 0 19 | Insights 20 | gitignore/Swift.gitignore 21 | 22 | 23 | # Xcode 24 | # 25 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 26 | 27 | ## User settings 28 | xcuserdata/ 29 | 30 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 31 | *.xcscmblueprint 32 | *.xccheckout 33 | 34 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 35 | build/ 36 | DerivedData/ 37 | *.moved-aside 38 | *.pbxuser 39 | !default.pbxuser 40 | *.mode1v3 41 | !default.mode1v3 42 | *.mode2v3 43 | !default.mode2v3 44 | *.perspectivev3 45 | !default.perspectivev3 46 | 47 | ## Obj-C/Swift specific 48 | *.hmap 49 | 50 | ## App packaging 51 | *.ipa 52 | *.dSYM.zip 53 | *.dSYM 54 | 55 | ## Playgrounds 56 | timeline.xctimeline 57 | playground.xcworkspace 58 | 59 | # Swift Package Manager 60 | # 61 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 62 | Packages/ 63 | Package.pins 64 | Package.resolved 65 | *.xcodeproj 66 | 67 | # 68 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 69 | # hence it is not needed unless you have added a package configuration file to your project 70 | 71 | .swiftpm 72 | 73 | .build/ 74 | 75 | # CocoaPods 76 | # 77 | # We recommend against adding the Pods directory to your .gitignore. However 78 | # you should judge for yourself, the pros and cons are mentioned at: 79 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 80 | # 81 | # Pods/ 82 | # 83 | # Add this line if you want to avoid checking in source code from the Xcode workspace 84 | # *.xcworkspace 85 | 86 | # Carthage 87 | # 88 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 89 | # Carthage/Checkouts 90 | 91 | Carthage/Build/ 92 | 93 | # Accio dependency management 94 | Dependencies/ 95 | .accio/ 96 | 97 | # fastlane 98 | # 99 | # It is recommended to not store the screenshots in the git repo. 100 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 101 | # For more information about the recommended setup visit: 102 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 103 | 104 | fastlane/report.xml 105 | fastlane/Preview.html 106 | fastlane/screenshots/**/*.png 107 | fastlane/test_output 108 | 109 | # Code Injection 110 | # 111 | # After new code Injection tools there's a generated folder /iOSInjectionProject 112 | # https://github.com/johnno1962/injectionforxcode 113 | 114 | iOSInjectionProject/ 115 | © 2020 GitHub, Inc. 116 | Terms 117 | Privacy 118 | Security 119 | Status 120 | Help 121 | Contact GitHub 122 | Pricing 123 | API 124 | Training 125 | Blog 126 | About 127 | Octotree 128 | Login to unlock more features 129 | 130 | .fake -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "OptionalAPI", 8 | platforms: [ 9 | .macOS(.v10_15), 10 | .iOS(.v16), 11 | .tvOS(.v16), 12 | .watchOS(.v9) 13 | ], 14 | products: [ 15 | .library( 16 | name: "OptionalAPI", 17 | type: .dynamic, 18 | targets: ["OptionalAPI"] 19 | ), 20 | ], 21 | 22 | dependencies: [ 23 | .package( 24 | url: "https://github.com/pointfreeco/swift-snapshot-testing.git", 25 | from: "1.18.4" 26 | ), 27 | .package( 28 | url: "https://github.com/sloik/AliasWonderland.git", 29 | from: "4.0.1" // use latest version instead 30 | ) 31 | ], 32 | 33 | targets: [ 34 | .target( 35 | name: "OptionalAPI", 36 | dependencies: [ 37 | "AliasWonderland" 38 | ], 39 | resources: [ 40 | .copy("PrivacyInfo.xcprivacy"), 41 | ] 42 | ), 43 | 44 | .testTarget( 45 | name: "OptionalAPITests", 46 | dependencies: [ 47 | "OptionalAPI", 48 | .product(name: "SnapshotTesting", package: "swift-snapshot-testing"), 49 | ] 50 | ), 51 | ] 52 | ) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fsloik%2FOptionalAPI%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/sloik/OptionalAPI) 2 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fsloik%2FOptionalAPI%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/sloik/OptionalAPI) 3 | ![Main](https://github.com/sloik/OptionalAPI/actions/workflows/swift.yml/badge.svg?branch=master) 4 | ![Nightly](https://github.com/sloik/OptionalAPI/actions/workflows/nightly.yml/badge.svg) 5 | 6 | # OptionalAPI 7 | 8 | Optional extensions for Swift Optional Monad... use it or not... it's optional. 9 | 10 | # Why 11 | 12 | Some common idioms pop-up when working with Optionals in Swift. Here is a bunch of useful extensions for some types. 13 | 14 | # Installation 15 | 16 | Just copy and paste files to your project 🍝 17 | 18 | Or use SPM 😎 19 | 20 | # Documentation 21 | 22 | GitHub Pages: [OptionalAPI](https://sloik.github.io/OptionalAPI/documentation/optionalapi/swift/optional) 23 | 24 | # Examples: 25 | 26 | ## Running some code if none or some 27 | 28 | Old: 29 | ```swift 30 | someOptional == nil ? True branch : False branch 31 | ``` 32 | 33 | New: 34 | 35 | ```swift 36 | someOptional.isSome ? True branch : False branch 37 | someOptional.isNone ? True branch : False branch 38 | 39 | someOptional.isNotSome ? True branch : False branch 40 | someOptional.isNotNone ? True branch : False branch 41 | ``` 42 | 43 | ## Sequencing of operations that might also return optional 44 | 45 | Operation that returns optional: 46 | ```swift 47 | func maybeIncrement(_ i: Int) -> Int? { i + 1 } 48 | ``` 49 | 50 | Old the terrible way: 51 | 52 | ```swift 53 | if let trueInt = someIntOptional { 54 | let incrementedOnce = maybeIncrement(trueInt) { 55 | // you get the idea ;) 56 | } 57 | } 58 | ``` 59 | 60 | ## `andThen` 61 | 62 | ```swift 63 | someOptional 64 | .andThen(maybeIncrement) 65 | .andThen(maybeIncrement) 66 | // ... you get the idea :) 67 | ``` 68 | 69 | In this case result of this chaining is a instance of `Int?`. If the `someOptional` was nil then whole computation results with nil. If it had some value (42) ten it would be incremented so many times. 70 | 71 | ## Recovering from `none` case 72 | 73 | Let's say you have a chain of operations and there is a chance that the result might return `none`. 74 | 75 | ```swift 76 | func returningNone(_ i: Int) -> Int? { Bool.random() ? .none : i } 77 | 78 | someOptional 79 | .andThen(maybeIncrement) 80 | .andThen(returningNone) // <-- returns nil 81 | .andThen(maybeIncrement) 82 | ``` 83 | 84 | Final result is `nil`. And you can't use a `??`. Use `mapNone` it's like normal `map` on Optional but for the `nil` case. 85 | 86 | ```swift 87 | func returningNone(_ i: Int) -> Int? { .none } 88 | 89 | someOptional 90 | .andThen(maybeIncrement) 91 | .andThen(returningNone) 92 | .mapNone(42) 93 | .andThen(maybeIncrement) 94 | ``` 95 | 96 | If `someOptional` started with `10` and we had luck (returningNone did not returned nil) then the final result is `12`. But if were not so lucky then the `mapNone` would take over and the final result would be `43`. 97 | 98 | You can also use more than one `mapNone` to handle any failures along the way. Oh and you can use an more friendly name `defaultSome` like so: 99 | 100 | ```swift 101 | someOptional 102 | // if someOptional is nil then start computation with default value 103 | .defaultSome(5) 104 | // increment whatever is there 105 | .andThen(maybeIncrement) 106 | // are you feeling lucky? 107 | .andThen(returningNone) 108 | // cover your ass if you had bad luck 109 | .defaultSome(42) 110 | // do some work with what's there 111 | .andThen(maybeIncrement) 112 | // what... again 113 | .andThen(returningNone) 114 | // saved 115 | .defaultSome(10) 116 | ``` 117 | 118 | I hope you can see that this gives you a very flexible API to handle Optionals in your code. 119 | 120 | ## `andThenTry` 121 | 122 | This operator expects an transformation that may throw an error. When this happens it returns `.none` which alows to recover with other operators. 123 | 124 | ```swift 125 | let jsonData: Data? = ... 126 | 127 | jsonData 128 | .andThenTry{ data in 129 | try JSONDecoder().decode(CodableStruct.self, from: data) 130 | } 131 | // this can also explode! 132 | .andThenTry( functionTakingCodbaleStructAndThrowing ) 133 | // if any did thow an error then just recover with this one 134 | .defaultSome( CodableStruct.validInstance ) 135 | ``` 136 | 137 | You can _revocer_ differently after different tries. Or you can totaly ignore it. Either way you have a nice API. 138 | 139 | # But wait there's more! 140 | 141 | Sometimes you are working with a Optional collection. Most common case is a `String` and and Optional Array of something. This **Optional API** has you covered to! 142 | 143 | In the examples below I will be using those Optionals: 144 | 145 | ```swift 146 | let noneString : String? = .none 147 | let emptySomeString: String? = "" 148 | let someSomeString : String? = "some string" 149 | 150 | let noneIntArray : [Int]? = .none 151 | let emptyIntArray: [Int]? = [] 152 | let someIntArray : [Int]? = [11, 22, 33] 153 | ``` 154 | 155 | I think this should cover all the cases 156 | 157 | # Optional collection has values is nil or empty 158 | 159 | A lot of ifology is made when working whit a collection inside a Optional context. Those properties should help. 160 | 161 | ## `hasElements` 162 | 163 | ```swift 164 | noneString.hasElements // false 165 | emptySomeString.hasElements // false 166 | someSomeString.hasElements // true 167 | 168 | noneIntArray.hasElements // false 169 | emptyIntArray.hasElements // false 170 | someIntArray.hasElements // true 171 | ``` 172 | 173 | ## `isNoneOrEmpty` 174 | 175 | ```swift 176 | noneString.isNoneOrEmpty // true 177 | emptySomeString.isNoneOrEmpty // true 178 | someSomeString.isNoneOrEmpty // false 179 | 180 | noneIntArray.isNoneOrEmpty // true 181 | emptyIntArray.isNoneOrEmpty // true 182 | someIntArray.isNoneOrEmpty // false 183 | ``` 184 | 185 | ## `recoverFromEmpty` 186 | 187 | This is called **only** if the underlying collection is empty. That is if your optional is `nil` or has some value this will not be called. As String is a collection I will only show examples for `[Int]?` :) 188 | 189 | ```swift 190 | noneIntArray.recoverFromEmpty([42]) // nil 191 | emptyIntArray.recoverFromEmpty([42]) // [42] 192 | someIntArray.recoverFromEmpty([42]) // [11, 22, 33] 193 | ``` 194 | 195 | If you need a default value for the none case then **defaultSome** is the thing you want. 196 | 197 | ```swift 198 | noneIntArray.defaultSome([42]) // [42] 199 | emptyIntArray.defaultSome([42]) // [] 200 | someIntArray.defaultSome([42]) // [11, 22, 33] 201 | ``` 202 | 203 | # `or` 204 | 205 | There are cases when you need an actual result from an Optional `or` a default non optional value. This is exactly the case for `or` 206 | 207 | ```swift 208 | let noneInt: Int? = .none 209 | let someInt: Int? = .some(42) 210 | 211 | var result: Int = someInt.or(69) // 42 212 | ``` 213 | 214 | In this case `result` variable stores value `42`. It's an honest Int not an optional. But what happens when it's `none`: 215 | 216 | ```swift 217 | result = noneInt.or(69) // 69 218 | ``` 219 | 220 | Here the _final_ result is `69` as everything evaluates to `none`. Once again after `or` you have a honest value or some default. 221 | 222 | ## default value with `or` 223 | 224 | If the wrapped type has a empty initializer (init that takes no arguments) you can call it to get an instance: 225 | 226 | ```swift 227 | someOptional 228 | .or(.init()) // creates an instance 229 | ``` 230 | 231 | To put it in a context if you have some optionals you can use this to get _zero_ value like so: 232 | 233 | ```swift 234 | let noneInt: Int? = nil 235 | noneInt.or( .init() ) // 0 236 | noneInt.or( .zero ) // 0 237 | 238 | let noneDouble: Double? = nil 239 | noneDouble.or( .init() ) // 0 240 | 241 | let defaults: UserDefaults? = nil 242 | defaults.or( .standard ) // custom or "standard" 243 | 244 | let view: UIView? = nil 245 | view.or( .init() ) 246 | 247 | // or any other init ;) 248 | view.or( .init(frame: .zero) ) 249 | 250 | // Collections 251 | let noneIntArray : [Int]? = .none 252 | noneIntArray.or( .init() ) // [] 253 | 254 | let emptySomeString: String? = "" 255 | noneString.or( .init() ) // "" 256 | 257 | // Enums 258 | enum Either { 259 | case left, right 260 | } 261 | let noneEither: Either? = nil 262 | noneEither.or(.right) 263 | 264 | ``` 265 | 266 | Anything that you can call on this type (static methods) can be used here. 267 | 268 | # `cast` 269 | 270 | Have you ever wrote code similar to this one: 271 | 272 | ```swift 273 | if let customVC = mysteryVC as? CustomVC { 274 | // do stuff 275 | } 276 | ``` 277 | 278 | With `cast` you can streamline your code to this: 279 | 280 | ```swift 281 | let someViewController: UIViewController? = ... 282 | someViewController 283 | .cast( CustomVC.self ) 284 | .andThen({ (vc: CustomVC) in 285 | // work with a non optional instance of CustomVC 286 | }) 287 | ``` 288 | 289 | If the type can be inferred from the context then you do not have to type it in. 290 | 291 | ```swift 292 | let anyString: Any? = ... 293 | 294 | let result: String? = anyString.cast() 295 | ``` 296 | 297 | As you can see compiler is able to inferred the correct type. But be aware that in more complex cases this can slow down your compilation. 298 | 299 | > If you want to have faster compilation then always be explicit about your types. In all of your code not only using this package. 300 | 301 | # `encode` & `decode` 302 | 303 | One of the common places when you want to encode or decode something is when you have some data from the network. Flow might look something like this: 304 | 305 | * make a API call for a resource 306 | * get JSON data 307 | 308 | To keep is simple let's say our Data Transfer Model (DTO) looks like this: 309 | 310 | ```swift 311 | struct CodableStruct: Codable, Equatable { 312 | let number: Int 313 | let message: String 314 | } 315 | ``` 316 | 317 | What happens is that a JSON string is send thru the network as data. To simulate this in code one could write this: 318 | 319 | ```swift 320 | let codableStructAsData: Data? = 321 | """ 322 | { 323 | "number": 55, 324 | "message": "data message" 325 | } 326 | """.data(using: .utf8) 327 | ``` 328 | 329 | Stage is set: 330 | 331 | ## `decode` 332 | 333 | Networking code will hand us an instance of `Data?` that we want to decode. 334 | 335 | ```swift 336 | let result: CodableStruct? = codableStructAsData.decode() 337 | ``` 338 | 339 | It's that simple. Compiler can infer the type so there's no need to add it explicitly. Buy you can do it in some longer pipelines eg.: 340 | 341 | ```swift 342 | codableStructAsData 343 | .decode( CodableStruct.self ) 344 | .andThen({ instance in 345 | // work with not optional instance 346 | }) 347 | ``` 348 | 349 | ## `encode` 350 | 351 | Encode goes other way. You have a instance that you want to encode to send it as a json. 352 | 353 | ```swift 354 | let codableStruct: CodableStruct? = 355 | CodableStruct( 356 | number: 69, 357 | message: "codable message" 358 | ) 359 | ``` 360 | 361 | To get the desired encoded vale just use the method: 362 | 363 | ```swift 364 | codableStruct 365 | .encode() // <- encoding part if you missed it ;) 366 | .andThen({ instance in 367 | // work with not optional instance 368 | }) 369 | ``` 370 | 371 | # `whenSome` and `whenNone` 372 | 373 | When working with optionals it happens that **you want to run some code but not change the optional**. This is where `whenSome` and `whenNone` can be used. 374 | 375 | ```swift 376 | let life: Int? = 42 377 | 378 | life 379 | .whenSome { value in 380 | print("Value of life is:", value) 381 | } 382 | ``` 383 | 384 | This code prints to the console: _Value of life is: 42_. 385 | 386 | `whenSome` also comes in a favor that does not need the argument. 387 | 388 | ```swift 389 | let life: Int? = 42 390 | 391 | life 392 | .whenSome { 393 | print("Life is a mistery. But I know it's there!") 394 | } 395 | ``` 396 | 397 | This is a very nice way of triggering some logic without having to write `if` statements. But what about when the optional is none (or how it's known nil)? 398 | 399 | `whenNone` is here for the rescue. 400 | 401 | ```swift 402 | let life: Int? = .none 403 | 404 | life 405 | .whenNone { 406 | print("No life here!") 407 | } 408 | ``` 409 | 410 | _No life here!_ will be printed in the console. 411 | 412 | But what's eaven more cool is that you can chain them! 413 | 414 | ```swift 415 | let life: Int? = 42 416 | 417 | life 418 | .whenSome { value in 419 | print("Value of life is:", value) 420 | } 421 | .whenSome { 422 | print("Life is a mistery. But I know it's there!") 423 | } 424 | .whenNone { 425 | print("No life here!") 426 | } 427 | ``` 428 | 429 | Depending on the operator and the value of optional different blocks will be called. And efcourse other operators can be thrown in to the mix. 430 | 431 | # filter 432 | 433 | Sometimes you need a value only when it passes some predicate. 434 | 435 | ```swift 436 | let arrayWithTwoElements: [Int]? = [42, 69] 437 | 438 | arrayWithTwoElements 439 | .filter { array in array.count > 1 } 440 | .andThen { ... } // work with array 441 | ``` 442 | 443 | There is also a free version of this operator: 444 | 445 | ```swift 446 | filter(_ predicate: @escaping (W) -> Bool ) -> (W?) -> W? 447 | ``` 448 | 449 | Use it to create a filter functions with a given predicate baked in. 450 | 451 | # Async/Await 452 | 453 | With new API for handeling asynchronous you can write code that uses asynchronous functions. 454 | 455 | ```swift 456 | // we are in asynchronous context 457 | 458 | let someInt: Int? = 42 459 | 460 | let result: Int? = await someInt 461 | .asyncFlatMap { 462 | try! await Task.sleep(nanoseconds: 42) 463 | return $0 + 1 464 | } 465 | .flatMap { fromAsync in 466 | fromAsync * 10 467 | } 468 | ``` 469 | 470 | As you can see it's easy to mix synchronous code with asynchronous. Just rember that `await` must be at the start of the pipeline. If you don't then you will have a friendly reminder from the compiler. 471 | 472 | # `tryAsyncMap` & `tryAsyncFlatMap` 473 | 474 | `tryAsyncMap` & `tryAsyncFlatMap` are methods that allow you to perform an asynchronous transformation on an optional value in Swift. They take a closure that performs an asynchronous operation on the optional value, and return an optional value of a different type. 475 | 476 | Usage 477 | 478 | Here's an example of how to use `tryAsyncMap`: 479 | 480 | ```swift 481 | enum MyError: Error { 482 | case invalidInput 483 | } 484 | 485 | func doAsyncTransformation(value: Int?) async throws -> String { 486 | guard let value = value else { 487 | throw MyError.invalidInput 488 | } 489 | 490 | await Task.sleep(1_000_000_000) // Simulate long-running task. 491 | 492 | return "Transformed value: \(value)" 493 | } 494 | 495 | let optionalValue: Int? = 42 496 | 497 | do { 498 | let transformedValue = try await optionalValue.tryAsyncMap { value in 499 | try doAsyncTransformation(value: value) 500 | } 501 | 502 | print(transformedValue) // Prints "Transformed value: 42". 503 | } catch { 504 | print(error) 505 | } 506 | ``` 507 | 508 | # `zip` -- moved 509 | 510 | This functionality was moved to [Zippy 🤐 Swift Package](https://github.com/sloik/Zippy). It has definitions for `zip` functions for more types than just optionals. 511 | 512 | # 🐇🕳 Rabbit Hole 513 | 514 | This project is part of the [🐇🕳 Rabbit Hole Packages Collection](https://github.com/sloik/RabbitHole) 515 | 516 | # That's it 517 | 518 | Hope it will help you :) 519 | 520 | Cheers! :D 521 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/AndThen.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public extension Optional { 5 | 6 | /// More readable wrapper on ```flatMap``` function defined on Optional in the standard library. 7 | /// 8 | /// 9 | /// It gives a readable way of chaining multiple operations. Also those that return an Optional. 10 | /// With this you can define pipelines of data transformations like so: 11 | /// 12 | /// ```swift 13 | /// let host: String? = "www.host.com" 14 | /// let url: URL? = 15 | /// host 16 | /// .andThen{ $0 + "/" } // appends "/" 17 | /// .andThen{ $0 + "page.html" } // appends "page.html" 18 | /// .andThen( URL.init ) // creates an URL 19 | /// url // www.host.com/page.html 20 | /// ``` 21 | /// Notice that we started with `String?` and that `URL.init` also returns and Optional. 22 | /// So at the end we should have and `URL??` but `flatMap` removes of one layer of packaging. 23 | /// 24 | /// What's more cool is that you can define blocks of code to run as when appending path components. 25 | /// And use point-free style by passing function symbols. In each case this function is working 26 | /// with a real value not an Optional. 27 | /// 28 | /// And then is called only when the instance is `.some(Wrapped)`. For `.none` case it does 29 | /// nothing. That is a very safe way of working with optionals: 30 | /// 31 | /// ```swift 32 | /// let host: String? = "www.host.com" 33 | /// let url: URL? = 34 | /// host 35 | /// // append "/" 36 | /// .andThen{ $0 + "/" } 37 | /// // "s" is "www.host.com/" but function fails 38 | /// .andThen{ (s: String) -> String? in nil } 39 | /// // is not getting called 40 | /// .andThen( URL.init ) 41 | /// 42 | /// url // nil 43 | /// ``` 44 | /// If the success path is all you need then `andThen` gets you covered. 45 | @discardableResult 46 | func andThen(_ transform: (Wrapped) -> T?) -> T? { flatMap(transform) } 47 | 48 | 49 | /// When optional is `some` then tries to run `transform` to produce value of type `T`. 50 | /// However when this transform fails then this error is catch-ed and `.none` is returned 51 | /// as a final result. 52 | /// 53 | /// ```swift 54 | /// let jsonData: Data? = ... 55 | /// 56 | /// jsonData 57 | /// .andThenTry{ data in try JSONDecoder().decode(CodableStruct.self, from: data) } 58 | /// .andThenTry( functionTakingCodbaleStructAndThrowing ) 59 | /// .andThen{ ... 60 | /// ``` 61 | /// 62 | /// You can still use other operators to recover from failed _tried_ operators. 63 | @discardableResult 64 | func andThenTry(_ transform: (Wrapped) throws -> T) -> T? { 65 | try? flatMap(transform) 66 | } 67 | 68 | @discardableResult 69 | func andThenTryOrThrow(_ transform: (Wrapped) throws -> T) throws -> T? { 70 | try flatMap(transform) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/AsyncMaps.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public extension Optional { 5 | 6 | @discardableResult 7 | func asyncMap(_ transform: (Wrapped) async -> T) async -> T? { 8 | switch self { 9 | case .some(let wrapped): return await transform(wrapped) 10 | case .none : return .none 11 | } 12 | } 13 | 14 | /** 15 | Performs an asynchronous transformation on an optional value. 16 | 17 | - Parameters: 18 | - transform: A closure that takes an optional `Wrapped` value 19 | and returns a new value of type `T` wrapped in 20 | a `Task` that may throw an error. 21 | 22 | - Returns: An optional value of type `T`. If the original value 23 | was non-nil and the transformation succeeded, returns 24 | the transformed value. Otherwise, returns `nil`. 25 | 26 | - Note: This function is marked as `@discardableResult`, so you can 27 | choose to ignore the result if you don't need it. 28 | 29 | - Throws: An error of type `Error` if the transformation fails. 30 | 31 | - Example: 32 | ```swift 33 | enum MyError: Error { 34 | case invalidInput 35 | } 36 | 37 | func doAsyncTransformation(value: Int?) async throws -> String { 38 | guard let value = value else { 39 | throw MyError.invalidInput 40 | } 41 | 42 | await Task.sleep(1_000_000_000) // Simulate long-running task. 43 | 44 | return "Transformed value: \(value)" 45 | } 46 | 47 | let optionalValue: Int? = 42 48 | 49 | do { 50 | let transformedValue = try await optionalValue.tryAsyncMap { value in 51 | try doAsyncTransformation(value: value) 52 | } 53 | 54 | print(transformedValue) // Prints "Transformed value: 42". 55 | } catch { 56 | print(error) 57 | } 58 | ``` 59 | */ 60 | @discardableResult 61 | func tryAsyncMap(_ transform: (Wrapped) async throws -> T) async throws -> T? { 62 | switch self { 63 | case .some(let wrapped): return try await transform(wrapped) 64 | case .none : return .none 65 | } 66 | } 67 | 68 | @discardableResult 69 | func asyncFlatMap(_ transform: (Wrapped) async -> T?) async -> T? { 70 | 71 | switch self { 72 | case .some(let wrapped): return await transform(wrapped) 73 | case .none : return .none 74 | } 75 | } 76 | 77 | @discardableResult 78 | func tryAsyncFlatMap(_ transform: (Wrapped) async throws -> T?) async throws -> T? { 79 | 80 | switch self { 81 | case .some(let wrapped): return try await transform(wrapped) 82 | case .none : return .none 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/Cast.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Free Function 4 | 5 | 6 | /// Curried version of `cast` function. 7 | /// - Parameter otherType: Type to which to cast eg. `String.self` 8 | /// - Returns: Function that expects an instance that will be _try_ to cast to the `T` type. 9 | /// 10 | /// This form allows for easer composition. You provide configuration up 11 | /// front and pass the instance later. 12 | /// ```swift 13 | /// let casterToCustomVC: (Any) -> CustomVC? = cast(CustomVC.self) 14 | /// 15 | /// // later in code.. 16 | /// let someViewController: UIViewController? = ... 17 | /// someViewController 18 | /// .cast( CustomVC.self ) 19 | /// .andThen({ (vc: CustomVC) in 20 | /// // work with a non optional instance of CustomVC 21 | /// }) 22 | /// ``` 23 | public func cast(_ otherType: T.Type = T.self) -> (Any) -> T? { 24 | return { thing in 25 | cast(thing, to: otherType) 26 | } 27 | } 28 | 29 | 30 | /// Free function wrapping ```as``` keyword. 31 | /// - Parameters: 32 | /// - thing: Instance to be casted. 33 | /// - to: Type to which to cast eg. `String.self` 34 | /// - Returns: Some optional when cast succeeds or `none` otherwise. 35 | public func cast(_ thing: Any, to: T.Type = T.self) -> T? { 36 | thing as? T 37 | } 38 | 39 | // MARK: - Extension 40 | 41 | public extension Optional { 42 | 43 | /// Cast. 44 | /// - Parameter type: Type to which to cast eg. `String.self` 45 | /// - Returns: Some optional when cast succeeds or `none` otherwise. 46 | /// 47 | /// This form allows for easer composition. You provide configuration up 48 | /// front and pass the instance later. 49 | /// ```swift 50 | /// let someViewController: UIViewController? = ... 51 | /// someViewController 52 | /// .cast( CustomVC.self ) 53 | /// .andThen({ (vc: CustomVC) in 54 | /// // work with a non optional instance of CustomVC 55 | /// }) 56 | /// ``` 57 | func cast(_ type: T.Type = T.self) -> T? { 58 | flatMap( 59 | OptionalAPI.cast(type) 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/Filter.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | 5 | /// Given a predicate returns a function that can be used for checking if given 6 | /// optional wrapped value passes this predicate. 7 | /// 8 | /// - Parameter predicate: Predicate that is applied to wrapped value of optional. 9 | /// - Returns: Optional when wrapped value matches predicate or `.none`. 10 | @discardableResult 11 | public func filter(_ predicate: @escaping (W) -> Bool ) -> (W?) -> W? { 12 | return { (wrapped: W?) in 13 | wrapped.filter( predicate ) 14 | } 15 | } 16 | 17 | public extension Optional { 18 | 19 | /// Operator used to filter out optionals that do not pass a predicate. 20 | /// ```swift 21 | /// let someNumber: Int? = ... 22 | /// 23 | /// someNumber 24 | /// .filter{ $0 > 42 } 25 | /// .andThen{ ... } // <-- here you have int's that are grater than 42 26 | /// ``` 27 | /// - Parameter predicate: Predicate that should be applied to wrapped value. 28 | /// - Returns: Optional if it matches predicate or `.none` 29 | @discardableResult 30 | func filter(_ predicate: (Wrapped) -> Bool) -> Wrapped? { 31 | switch map(predicate) { 32 | case true?: return self 33 | default : return .none 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/Folds.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public extension Optional { 5 | 6 | func fold( 7 | _ noneCase: R, 8 | _ someCase: (Wrapped) -> R 9 | ) -> R { 10 | map( someCase ) ?? noneCase 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/Maps.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | import AliasWonderland 5 | 6 | public extension Optional { 7 | 8 | /// `mapNone` is the same thing for `none` case like `andThen` for `some` case. 9 | /// 10 | /// This functions allows you to `recover` from a computation that returned nil. It 11 | /// ignores the some case and run only for `none`. 12 | /// 13 | /// ```swift 14 | /// let host: String? = "www.host.com" 15 | /// let url: URL? = 16 | /// host 17 | /// .andThen{ $0 + "/" } 18 | /// .andThen{ (s: String) -> String? in nil } 19 | /// // try to recover with home page 20 | /// .mapNone("www.host.com") 21 | /// .andThen( URL.init ) 22 | /// 23 | /// url // www.host.com 24 | /// ```` 25 | /// 26 | /// If the failing function would produce a valid output then `mapNone` would not be called. 27 | /// 28 | /// ```swift 29 | /// let host: String? = "www.host.com" 30 | /// let url: URL? = 31 | /// host 32 | /// .andThen{ $0 + "/" } 33 | /// .andThen{ $0 + "page.html" } 34 | /// // computation did not fail so nothing to recover from 35 | /// .mapNone("www.host.com") 36 | /// .andThen( URL.init ) 37 | /// 38 | /// url // www.host.com/page.html 39 | /// ``` 40 | /// 41 | /// You can `mapNone` more than once and on any stage you want. 42 | @discardableResult 43 | func mapNone(_ producer: @autoclosure Producer) -> Wrapped? { 44 | or(producer) 45 | } 46 | 47 | /// `defaultSome` is just a better name than `mapNone`. Both work exactly the same. 48 | /// 49 | /// This functions allows you to `recover` from a computation that returned nil. It 50 | /// ignores the some case and run only for `none`. 51 | /// 52 | /// ```swift 53 | /// let host: String? = "www.host.com" 54 | /// let url: URL? = 55 | /// host 56 | /// .andThen{ $0 + "/" } 57 | /// .andThen{ (s: String) -> String? in nil } 58 | /// // try to recover with home page 59 | /// .defaultSome("www.host.com") 60 | /// .andThen( URL.init ) 61 | /// 62 | /// url // www.host.com 63 | /// ``` 64 | /// 65 | /// If the failing function would produce a valid output then `mapNone` would not be called. 66 | /// 67 | /// ```swift 68 | /// let host: String? = "www.host.com" 69 | /// let url: URL? = 70 | /// host 71 | /// .andThen{ $0 + "/" } 72 | /// .andThen{ $0 + "page.html" } 73 | /// // computation did not fail so nothing to recover from 74 | /// .defaultSome("www.host.com") 75 | /// .andThen( URL.init ) 76 | /// 77 | /// url // www.host.com/page.html 78 | /// ``` 79 | /// 80 | /// You can `defaultSome` more than once and on any stage you want. 81 | @discardableResult 82 | func defaultSome(_ producer: @autoclosure Producer) -> Wrapped? { 83 | or(producer) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/OptionalCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Extension 4 | 5 | public extension Optional where Wrapped == Data { 6 | 7 | /// Decodes wrapped data to desired Decodable type. 8 | /// - Parameter to: Type that is conforming to `Decodable`. 9 | /// - Returns: Instance of `T` if JSONDecoder decode succeeded or .none otherwise. 10 | /// 11 | /// ```swift 12 | /// let codableStructAsData: Data? = ... 13 | /// 14 | /// let result: CodableStruct? = codableStructAsData.decode() 15 | /// ``` 16 | /// Type can be inferred so it does not have to be added explicitly. Same can 17 | /// be written as: 18 | /// ```swift 19 | /// let result = codableStructAsData.decode(CodableStruct.self) 20 | /// ``` 21 | /// Either way is fine and you will have an optional to work with. 22 | /// ```swift 23 | /// codableStructAsData 24 | /// .decode(CodableStruct.self) 25 | /// .andThen({ instance in 26 | /// // work with not optional instance 27 | /// }) 28 | /// ``` 29 | func decode(_ to: T.Type = T.self) -> T? { 30 | flatMap { (wrapped) -> T? in 31 | try? JSONDecoder().decode(T.self, from: wrapped) 32 | } 33 | } 34 | } 35 | 36 | 37 | public extension Optional where Wrapped: Encodable { 38 | 39 | /// Encodes wrapped value to Data. 40 | /// - Returns: Data if JSONEncoder encode succeeded or .none otherwise. 41 | func encode() -> Data? { 42 | flatMap { (wrapped) -> Data? in 43 | try? JSONEncoder().encode(wrapped) 44 | } 45 | } 46 | } 47 | 48 | // MARK: - Free Functions 49 | // MARK: Decode 50 | 51 | 52 | /// Decodes wrapped data to desired Decodable type. 53 | /// - Parameters: 54 | /// - optional: Instance of `Data?` type. 55 | /// - to: Type that is conforming to `Decodable`. 56 | /// - Returns: Instance of `T` if JSONDecoder decode succeeded or `.none` otherwise. 57 | public func decode( 58 | _ optional: Data?, 59 | _ to: T.Type = T.self) 60 | -> T? 61 | { 62 | optional.decode(to) 63 | } 64 | 65 | 66 | /// Curried version of `decode` function 67 | /// - Parameter to: Type that is conforming to `Decodable`. 68 | /// - Returns: Function that given `Data?` will produce instance of `T` if JSONDecoder decode succeeded or `.none` otherwise. 69 | public func decode( 70 | _ to: T.Type = T.self) 71 | -> (Data?) -> T? 72 | { 73 | return { (optional: Data?) -> T? in 74 | optional.decode(to) 75 | } 76 | } 77 | 78 | 79 | // MARK: Encode 80 | 81 | /// Encodes wrapped value to Data. 82 | /// - Parameter optional: Instance to be encoded. 83 | /// - Returns: Data if JSONEncoder encode succeeded or `.none` otherwise. 84 | public func encode(_ optional: T?) -> Data? { 85 | optional.encode() 86 | } 87 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/OptionalCollection.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public extension Optional where Wrapped: Collection { 5 | 6 | /// **True** if optional instance is ````.some```` **and** collections 7 | /// **HAS** elements ````isEmpty == false```` 8 | /// 9 | /// When working with a Optional Collection the interesting _question_ is 10 | /// does it **hasElements**. Use this property to conveniently answer it. 11 | /// 12 | /// ```swift 13 | /// let noneString: String? = .none 14 | /// noneString.hasElements // false 15 | /// 16 | /// let emptySomeString: String? = "" // empty string 17 | /// emptySomeString.hasElements // false 18 | /// 19 | /// let someSomeString: String? = "some string" 20 | /// someSomeString.hasElements // true 21 | /// 22 | /// 23 | /// let noneIntArray: [Int]? = .none 24 | /// noneIntArray.hasElements // false 25 | /// 26 | /// let emptyIntArray: [Int]? = [] 27 | /// emptyIntArray.hasElements // false 28 | /// 29 | /// let someIntArray: [Int]? = [11, 22, 33] 30 | /// someIntArray.hasElements // true 31 | /// ```` 32 | var hasElements: Bool { 33 | map( \.isEmpty ) // get isEmpty value from the wrapped collection 34 | .map( ! ) // negation; if was empty then it `has NOT Elements` 35 | .or(false) // was none so definitely does not have elements 36 | } 37 | 38 | 39 | /// **True** if optional instance is ````.none```` **or** collections ````isEmpty````. 40 | /// 41 | /// Very often when working with a Optional Collection the absence of value and 42 | /// it being empty is handled in the same way. 43 | /// 44 | /// ```swift 45 | /// let noneString: String? = .none 46 | /// noneString.isNoneOrEmpty // true 47 | /// 48 | /// let emptySomeString: String? = "" 49 | /// emptySomeString.isNoneOrEmpty // true 50 | /// 51 | /// let someSomeString: String? = "some string" 52 | /// someSomeString.isNoneOrEmpty // false 53 | /// 54 | /// 55 | /// let noneIntArray: [Int]? = .none 56 | /// noneIntArray.isNoneOrEmpty // true 57 | /// 58 | /// let emptyIntArray: [Int]? = [] 59 | /// emptyIntArray.isNoneOrEmpty // true 60 | /// 61 | /// let someIntArray: [Int]? = [11, 22, 33] 62 | /// someIntArray.isNoneOrEmpty // false 63 | /// ```` 64 | var isNoneOrEmpty: Bool { map( \.isEmpty ) ?? true } 65 | 66 | 67 | /// - Parameters: 68 | /// - producer: Value ot type `Wrapped` to be used in case of wrapped collection `isEmpty`. 69 | /// 70 | /// This is called **only** if the underlying collection is empty. If optional is `nil` 71 | /// or has some value. Then this function will not be called. 72 | /// 73 | /// ```swift 74 | /// let noneIntArray : [Int]? = .none 75 | /// noneIntArray.recoverFromEmpty( [42] ) // nil ; is not a empty collection 76 | /// noneIntArray.defaultSome( [42] ) // [42]; use defaultSome for .none case 77 | /// 78 | /// let emptyIntArray: [Int]? = [] 79 | /// emptyIntArray.recoverFromEmpty( [42] ) // [42] ; was `some` and collection was empty 80 | /// emptyIntArray.defaultSome( [42] ) // [] ; was `some` case 81 | /// 82 | /// let someIntArray : [Int]? = [11, 22, 33] 83 | /// someIntArray.recoverFromEmpty( [42] ) // [11, 22, 33] ; was `some` and collection has elements 84 | /// someIntArray.defaultSome( [42] ) // [11, 22, 33] ; was `some` and collection has elements 85 | /// ``` 86 | @discardableResult 87 | func recoverFromEmpty(_ producer: @autoclosure () -> Wrapped) -> Wrapped? { 88 | map { collection in collection.isEmpty ? producer() : collection } 89 | } 90 | 91 | func recoverFromEmpty(_ producer: () -> Wrapped) -> Wrapped? { 92 | map { collection in collection.isEmpty ? producer() : collection } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/OptionalError.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public extension Optional { 5 | 6 | /// When self is `.none` calls error producing closure and throws produced error. 7 | /// When self is `.some` unwraps and returns it. 8 | /// 9 | /// - Parameter error: Closure producing an error. 10 | /// - Returns: Unwrapped value. 11 | func throwOrGetValue(_ error: () -> Error) throws -> Wrapped { 12 | if self == nil { 13 | throw error() 14 | } 15 | 16 | return self! 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/OptionalFreeFunctions.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public func isNone(_ optional: T?) -> Bool { 5 | optional.isNone 6 | } 7 | 8 | public func isSome(_ optional: T?) -> Bool { 9 | optional.isSome 10 | } 11 | 12 | public func isNotNone(_ optional: T?) -> Bool { 13 | optional.isNotNone 14 | } 15 | 16 | public func isNotSome(_ optional: T?) -> Bool { 17 | optional.isNotSome 18 | } 19 | 20 | 21 | public func andThen( 22 | _ optional: Wrapped?, 23 | _ transform: (Wrapped) -> T?) 24 | -> T? 25 | { 26 | optional.andThen(transform) 27 | } 28 | 29 | public func andThen( 30 | _ transform: @escaping (Wrapped) -> T?) 31 | -> (Wrapped?) 32 | -> T? 33 | { 34 | return { optional in 35 | optional.andThen(transform) 36 | } 37 | } 38 | 39 | // MARK: - or 40 | @discardableResult 41 | public func or( 42 | _ optional: T?, 43 | _ producer: @autoclosure () -> T) 44 | -> T 45 | { 46 | optional.or(producer) 47 | } 48 | 49 | @discardableResult 50 | public func or( 51 | _ producer: @autoclosure @escaping () -> T) 52 | -> (T?) -> T 53 | { 54 | return { optional in 55 | optional.or(producer) 56 | } 57 | } 58 | 59 | @discardableResult 60 | public func or( 61 | _ optional: T?, 62 | _ producer: () -> T) 63 | -> T 64 | { 65 | optional.or(producer) 66 | } 67 | 68 | @discardableResult 69 | public func or( 70 | _ producer: @escaping () -> T) 71 | -> (T?) -> T 72 | { 73 | return { optional in 74 | optional.or(producer) 75 | } 76 | } 77 | 78 | 79 | // MARK: - mapNone 80 | @discardableResult 81 | public func mapNone( 82 | _ optional: T?, 83 | _ producer: @autoclosure () -> T) 84 | -> T? 85 | { 86 | optional.or(producer) 87 | } 88 | 89 | @discardableResult 90 | public func mapNone( 91 | _ optional: T?, 92 | _ producer: () -> T) 93 | -> T? 94 | { 95 | optional.or(producer) 96 | } 97 | 98 | @discardableResult 99 | public func mapNone( 100 | _ producer: @escaping () -> T) 101 | -> (T?) -> T? 102 | { 103 | return { optional in 104 | optional.or(producer) 105 | } 106 | } 107 | 108 | @discardableResult 109 | public func mapNone( 110 | _ producer: @escaping @autoclosure () -> T) 111 | -> (T?) -> T? 112 | { 113 | return { optional in 114 | optional.or(producer) 115 | } 116 | } 117 | 118 | 119 | // MARK: - Default Some 120 | public func defaultSome( 121 | _ optional: T?, 122 | _ producer: @autoclosure () -> T) 123 | -> T? 124 | { 125 | optional.or(producer) 126 | } 127 | 128 | public func defaultSome( 129 | _ producer: @autoclosure @escaping () -> T) 130 | -> (T?) -> T? 131 | { 132 | return { optional in 133 | optional.or(producer) 134 | } 135 | } 136 | 137 | public func defaultSome( 138 | _ optional: T?, 139 | _ producer: () -> T) 140 | -> T? { 141 | optional.or(producer) 142 | } 143 | 144 | public func defaultSome( 145 | _ producer: @escaping () -> T) 146 | -> (T?) -> T? 147 | { 148 | return { optional in 149 | optional.or(producer) 150 | } 151 | } 152 | 153 | // MARK: - Collections 154 | 155 | public func hasElements(_ optional: T?) -> Bool { 156 | optional.hasElements 157 | } 158 | 159 | public func isNoneOrEmpty(_ optional: T?) -> Bool { 160 | optional.isNoneOrEmpty 161 | } 162 | 163 | @discardableResult 164 | public func recoverFromEmpty( 165 | _ optional: T?, 166 | _ producer: @autoclosure () -> T) 167 | -> T? 168 | { 169 | optional.recoverFromEmpty(producer) 170 | } 171 | 172 | @discardableResult 173 | public func recoverFromEmpty( 174 | _ producer: @autoclosure @escaping () -> T) 175 | -> (T?) -> T? 176 | { 177 | return { optional in 178 | optional.recoverFromEmpty(producer) 179 | } 180 | } 181 | 182 | @discardableResult 183 | public func recoverFromEmpty( 184 | _ producer: @escaping () -> T) 185 | -> (T?) -> T? 186 | { 187 | return { optional in 188 | optional.recoverFromEmpty(producer) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/OptionalProperties.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public extension Optional { 5 | 6 | /// **True** if optional instance is ````.none````. 7 | /// 8 | /// Replaces a check for nil: 9 | /// 10 | /// ```swift 11 | /// let number: Int? = nil 12 | /// number == nil // true 13 | /// ```swift 14 | /// 15 | /// with more friendly: 16 | /// 17 | /// ```swift 18 | /// number.isNone // true 19 | /// ```` 20 | var isNone: Bool { 21 | switch self { 22 | case .none: return true 23 | case .some: return false 24 | } 25 | } 26 | 27 | 28 | /// **True** if optional instance is ````.some(Wrapped)````. 29 | /// 30 | /// Replaces a check for nil: 31 | /// 32 | /// ```swift 33 | /// let number: Int? = .some(42) 34 | /// number != nil // true 35 | /// ```` 36 | /// 37 | /// with more friendly: 38 | /// 39 | /// ```swift 40 | /// number.isSome // true 41 | /// ```` 42 | var isSome: Bool { isNone == false } 43 | 44 | 45 | /// **True** if optional instance is ````.some(Wrapped)````. 46 | /// You can also read it as **isSome**. 47 | /// 48 | /// Replaces a check for nil: 49 | /// 50 | /// ```swift 51 | /// let number: Int? = .some(42) 52 | /// number != nil // true 53 | /// ```` 54 | /// 55 | /// with more friendly: 56 | /// 57 | /// ```swift 58 | /// number.isNotNone // true 59 | /// ```` 60 | var isNotNone: Bool { isNone == false } 61 | 62 | 63 | /// **True** if optional instance is ````.none````. 64 | /// You can also read it as **isNone**. 65 | /// 66 | /// Replaces a check for nil: 67 | /// 68 | /// ```swift 69 | /// let number: Int? = nil 70 | /// number == nil // true 71 | /// ```` 72 | /// 73 | /// with more friendly: 74 | /// 75 | /// ```swift 76 | /// number.isNotSome // true 77 | /// ```` 78 | var isNotSome: Bool { isSome == false } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/Or.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | import AliasWonderland 5 | 6 | public extension Optional { 7 | /// - Parameters: 8 | /// - producer: Value ot type `Wrapped` to be used in case of `.none`. 9 | /// 10 | /// `or` is a handy unwrapper of the wrapped value inside of optional **but** you must provide 11 | /// a `default` value in case the Optional is `nil`. That way it will always return 12 | /// a **not optional** instance to work with. 13 | /// 14 | /// ```swift 15 | /// let missingAge: Int? = nil 16 | /// let underAge : Int? = 17 17 | /// let overAge : Int? = 42 18 | /// 19 | /// func canBuyBeer(_ age: Int) -> Bool { age > 18 } 20 | /// 21 | /// missingAge.andThen(canBuyBeer).or(false) // false 22 | /// underAge .andThen(canBuyBeer).or(false) // false 23 | /// overAge .andThen(canBuyBeer).or(false) // true 24 | /// ```` 25 | /// 26 | /// Each time the final result was a true `Bool` not an `Bool?`. 27 | /// 28 | /// - Note: You can use `.init` and static method available on type to: 29 | /// 30 | /// ```swift 31 | /// let noneInt: Int? = nil 32 | /// noneInt.or( .init() ) // 0 33 | /// noneInt.or( .zero ) // 0 34 | /// 35 | /// let noneDouble: Double? = nil 36 | /// noneDouble.or( .init() ) // 0 37 | /// 38 | /// let defaults: UserDefaults? = nil 39 | /// defaults.or( .standard ) // custom or "standard" 40 | /// 41 | /// let view: UIView? = nil 42 | /// view.or( .init() ) 43 | /// 44 | /// // or any other init ;) 45 | /// view.or( .init(frame: .zero) ) 46 | /// 47 | /// // Collections 48 | /// let noneIntArray : [Int]? = .none 49 | /// noneIntArray.or( .init() ) // [] 50 | /// 51 | /// let emptySomeString: String? = "" 52 | /// noneString.or( .init() ) // "" 53 | /// 54 | /// // Enums 55 | /// enum Either { 56 | /// case left, right 57 | /// } 58 | /// let noneEither: Either? = nil 59 | /// noneEither.or(.right) 60 | /// ```` 61 | @discardableResult 62 | func or(_ producer: @autoclosure Producer) -> Wrapped { 63 | self.or(producer) 64 | } 65 | 66 | func or(_ producer: Producer) -> Wrapped { 67 | self ?? producer() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | NSPrivacyCollectedDataTypes 8 | 9 | NSPrivacyTrackingDomains 10 | 11 | NSPrivacyTracking 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/OptionalAPI/RunWhen.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public extension Optional { 5 | 6 | /// Sometime you want to just run some code when optional has _any_ wrapped value. This function gives you 7 | /// a nice API to do that. 8 | /// 9 | /// ````swift 10 | /// let life: Int? = 42 11 | /// 12 | /// life 13 | /// .whenSome { i in print(i) } // prints: 42 14 | /// .whenSome { print("I do not know the value but I run!") } 15 | /// .whenNone { print("This won't run") } 16 | /// ```` 17 | /// 18 | /// - Parameter block: Side effect that you want to trigger when optional has _any_ value of type `Wrapped` 19 | /// - Returns: Same optional without altering it. 20 | @discardableResult 21 | func whenSome(_ block: () -> Void) -> Wrapped? { 22 | if isSome { block() } 23 | return self 24 | } 25 | 26 | 27 | /// More explicit name for `run` function. Under the hood it just calls it. 28 | /// 29 | /// ````swift 30 | /// let life: Int? = 42 31 | /// 32 | /// life 33 | /// .whenSome { i in print(i) } // prints: 42 34 | /// .whenSome { print("I do not know the value but I run!") } 35 | /// .whenNone { print("This won't run") } 36 | /// ```` 37 | /// 38 | /// - Parameter block: Function to be called for side effects when optional `isSome`. 39 | /// - Returns: Same optional without altering it. 40 | @discardableResult 41 | func whenSome(_ block: (Wrapped) -> Void) -> Wrapped? { 42 | _ = self.map(block) 43 | return self 44 | } 45 | 46 | /// Sometime you want to just run some code when optional has _any_ wrapped value. 47 | /// This function gives you a nice API to do that. 48 | @discardableResult 49 | func tryWhenSome(_ block: (Wrapped) throws -> Void) throws -> Wrapped? { 50 | _ = try self.map(block) 51 | return self 52 | } 53 | 54 | /// Sometimes you want to run some logic if optional does not contain any wrapped value. 55 | /// 56 | /// ````swift 57 | /// let life: Int? = 42 58 | /// 59 | /// life 60 | /// .whenSome { print("This won't run") } 61 | /// .whenSome { print("This won't run") } 62 | /// .whenNone { print("Only this block will run") } 63 | /// ```` 64 | /// 65 | /// - Parameter block: Side effect that you want to trigger when optional `isNone` 66 | /// - Returns: Same optional without altering it. 67 | @discardableResult 68 | func whenNone(_ block: () -> Void) -> Wrapped? { 69 | if isNone { block() } 70 | return self 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import OptionalAPITests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += OptionalAPITests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/AndThenTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | import XCTest 5 | import OptionalAPI 6 | 7 | final class AndThenTests: XCTestCase { 8 | 9 | func test_andThenTry_whenSomeCase_transformsThrowsAnError_should_returnOptional() { 10 | XCTAssertNoThrow( 11 | someSomeString.andThenTry( alwaysThrowing ), 12 | "Error in throwing function should be handled by the operator!" 13 | ) 14 | 15 | XCTAssertNil( 16 | someSomeString.andThenTry( alwaysThrowing ), 17 | "Error in throwing function should make operator return `none`!" 18 | ) 19 | } 20 | 21 | func test_andThenTry_whenSomeCase_transformsDoesNotThrowsAnError_should_returnOptionalWithTransformedValue() { 22 | XCTAssertNoThrow( 23 | someSomeString.andThenTry( alwaysReturningString ), 24 | "Error in throwing function should be handled by the operator!" 25 | ) 26 | 27 | XCTAssertEqual( 28 | someSomeString.andThenTry( alwaysReturningString ), 29 | "It works fine", 30 | "When throwing transform does not throw returned value should be returned!" 31 | ) 32 | 33 | codableStructAsData 34 | .andThenTry{ data in try JSONDecoder().decode(CodableStruct.self, from: data) } 35 | .andThen { (instance: CodableStruct) in 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/AsyncMapsTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | import XCTest 5 | import OptionalAPI 6 | 7 | 8 | final class AsyncMapsTests: XCTestCase { 9 | 10 | // MARK: Map 11 | 12 | func test_asyncMap_whenNone_shouldNotCallTransform_andReturn_none() async { 13 | let none: Int? = .none 14 | 15 | let result: Void? = await none.asyncMap { _ in XCTFail( "Should not call this closure!" ) } 16 | 17 | XCTAssertNil( result ) 18 | } 19 | 20 | func test_asyncMap_whenSome_shouldCallTransform_andReturn_expectedValue() async { 21 | let some: Int? = 42 22 | 23 | let result: Int? = await some.asyncMap { wrapped in 24 | try! await Task.sleep(nanoseconds: 42) 25 | return wrapped * 2 26 | } 27 | 28 | XCTAssertEqual(result, 84) 29 | } 30 | 31 | // MARK: Flat Map 32 | 33 | func test_asyncFlatMap_whenNone_shouldNotCallTransform_andReturn_none() async { 34 | let none: Int? = .none 35 | 36 | let result: Void? = await none.asyncFlatMap { _ in XCTFail( "Should not call this closure!" ) } 37 | 38 | XCTAssertNil( result ) 39 | } 40 | 41 | func test_asyncFlatMap_whenSome_shouldCallTransform_andReturn_expectedValue() async { 42 | let some: Int? = 42 43 | 44 | let result: Int? = await some.asyncFlatMap { wrapped in 45 | try! await Task.sleep(nanoseconds: 42) 46 | return wrapped * 2 47 | } 48 | 49 | XCTAssertEqual(result, 84) 50 | } 51 | 52 | func test_asyncFlatMap_whenSome_whenTransformReturns_optionalValue_shouldReturnExpectedResult() async { 53 | let some: String? = "42" 54 | 55 | let result: Int? = await some.asyncFlatMap { (w: String) -> Int? in 56 | try! await Task.sleep(nanoseconds: 42) 57 | return Int(w) 58 | } 59 | 60 | XCTAssertEqual(result, 42) 61 | } 62 | 63 | func test_asyncFlatMap_longerPipeline() async { 64 | let some: Int? = 42 65 | 66 | let result: Int? = await some 67 | .asyncFlatMap { 68 | try! await Task.sleep(nanoseconds: 42) 69 | return $0 + 1 70 | } 71 | .flatMap { fromAsync in 72 | fromAsync * 10 73 | } 74 | 75 | XCTAssertEqual(result, 430) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/CastTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import OptionalAPI 3 | 4 | 5 | class CastTests: XCTestCase { 6 | 7 | func test_cast_should_returnCastedOptional() { 8 | // Arrange 9 | var didCast = false 10 | 11 | // Act 12 | let result: String? = anyString.cast(String.self) 13 | 14 | // Assert 15 | result 16 | .mapNone({ () -> String in 17 | XCTFail("Should be able to cast!") 18 | return "fail" 19 | }()) 20 | .andThen ({ (string: String) -> String in 21 | didCast = true 22 | 23 | return string 24 | }) 25 | 26 | XCTAssertTrue( 27 | didCast, 28 | "Should cast \(String(describing: anyString)) to String" 29 | ) 30 | XCTAssertEqual( 31 | result, "any string" 32 | ) 33 | } 34 | 35 | func test_cast_shouldNot_requireTypeParameterWhenItCanBeInferred() { 36 | let result: String? = anyString.cast() 37 | 38 | XCTAssertNotNil(result) 39 | } 40 | 41 | func test_cast_shouldReturn_NoneForFailedCast() { 42 | // Arrange && Act 43 | let result: Int? = anyString.cast(Int.self) 44 | 45 | // Assert 46 | result 47 | .andThen ({ (number: Int) -> Int in 48 | XCTFail("Should NOT cast!") 49 | 50 | return number 51 | }) 52 | 53 | XCTAssertNil(result) 54 | } 55 | 56 | func test_cast_toDifferentType_shouldReturn_NoneForNone() { 57 | // Arrange && Act 58 | let result: Int? = anyNoneString.cast(Int.self) 59 | 60 | // Assert 61 | result 62 | .andThen ({ (number: Int) -> Int in 63 | XCTFail("Should NOT cast!") 64 | 65 | return number 66 | }) 67 | 68 | XCTAssertNil(result) 69 | } 70 | 71 | func test_cast_toSameType_shouldReturn_NoneForNone() { 72 | // Arrange && Act 73 | let result: String? = anyNoneString.cast(String.self) 74 | 75 | // Assert 76 | result 77 | .andThen ({ (string: String) -> String in 78 | XCTFail("Should NOT cast!") 79 | 80 | return string 81 | }) 82 | 83 | XCTAssertNil(result) 84 | } 85 | 86 | func test_cast_shouldProduce_sameResultAsInlinedCast() { 87 | XCTAssertEqual( 88 | noneString.andThen({ $0 as? String }), 89 | noneString.cast(String.self) 90 | ) 91 | XCTAssertEqual( 92 | noneString.andThen({ $0 as? Int }), 93 | noneString.cast(Int.self) 94 | ) 95 | 96 | XCTAssertEqual( 97 | anyString.andThen({ $0 as? String }), 98 | anyString.cast(String.self) 99 | ) 100 | XCTAssertEqual( 101 | anyString.andThen({ $0 as? Int }), 102 | anyString.cast(Int.self) 103 | ) 104 | 105 | XCTAssertEqual( 106 | anyNoneString.andThen({ $0 as? String }), 107 | anyNoneString.cast(String.self) 108 | ) 109 | XCTAssertEqual( 110 | anyNoneString.andThen({ $0 as? Int }), 111 | anyNoneString.cast(Int.self) 112 | ) 113 | 114 | XCTAssertEqual( 115 | anyInt.andThen({ $0 as? String }), 116 | anyInt.cast(String.self) 117 | ) 118 | XCTAssertEqual( 119 | anyInt.andThen({ $0 as? Int }), 120 | anyInt.cast(Int.self) 121 | ) 122 | 123 | XCTAssertEqual( 124 | anyNoneInt.andThen({ $0 as? String }), 125 | anyNoneInt.cast(String.self) 126 | ) 127 | XCTAssertEqual( 128 | anyNoneInt.andThen({ $0 as? Int }), 129 | anyNoneInt.cast(Int.self) 130 | ) 131 | } 132 | 133 | func test_randomCombinators() { 134 | XCTAssertEqual( 135 | anyInt 136 | .cast(Int.self) 137 | .andThen({ $0 + 1 }), 138 | 43 139 | ) 140 | 141 | XCTAssertEqual( 142 | anyString 143 | .cast(String.self) 144 | .andThen({ $0.uppercased() }), 145 | "ANY STRING" 146 | ) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/FilterTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import OptionalAPI 3 | 4 | 5 | class FilterTests: XCTestCase { 6 | 7 | let sutTrue: (Int?) -> Int? = OptionalAPI.filter( alwaysTruePredicate ) 8 | let sutFalse: (Int?) -> Int? = OptionalAPI.filter( alwaysFalsePredicate ) 9 | 10 | func test_filteringSomeOptional_withSuccessPredicate_shouldBeSome() { 11 | XCTAssertNotNil( 12 | sutTrue( 42 ) 13 | ) 14 | } 15 | 16 | func test_filteringNoneOptional_withSuccessPredicate_shouldBeNone() { 17 | XCTAssertNil( 18 | sutTrue( .none ) 19 | ) 20 | } 21 | 22 | func test_filteringSomeOptional_withFailurePredicate_shouldBeNone() { 23 | XCTAssertNil( 24 | sutFalse( 42 ) 25 | ) 26 | } 27 | 28 | func test_filteringNoneOptional_withFailurePredicate_shouldBeNone() { 29 | XCTAssertNil( 30 | sutFalse( .none ) 31 | ) 32 | } 33 | 34 | func test_api() { 35 | let arrayWithTwoElements: [Int]? = [42, 69] 36 | 37 | XCTAssertNotNil( 38 | arrayWithTwoElements 39 | .filter { array in array.count > 1 } 40 | ) 41 | 42 | XCTAssertNil( 43 | arrayWithTwoElements 44 | .filter { array in array.isEmpty } 45 | ) 46 | } 47 | } 48 | 49 | func alwaysTruePredicate(_ value: T) -> Bool { true } 50 | func alwaysFalsePredicate(_ value: T) -> Bool { false } 51 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/FreeFunctionsTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import OptionalAPI 3 | 4 | class FreeFunctionsTests: XCTestCase { 5 | 6 | func test_isNone() throws { 7 | // Act & Assert 8 | XCTAssertEqual(noneInt |> isNone, 9 | noneInt == nil) 10 | 11 | XCTAssertEqual(someInt |> isNone, 12 | someInt == nil) 13 | 14 | XCTAssertEqual(noneString |> isNone, 15 | noneString == nil) 16 | 17 | XCTAssertEqual(emptySomeString |> isNone, 18 | emptySomeString == nil) 19 | 20 | XCTAssertEqual(someSomeString |> isNone, 21 | someSomeString == nil) 22 | 23 | XCTAssertEqual(noneIntArray |> isNone, 24 | noneIntArray == nil) 25 | 26 | XCTAssertEqual(emptyIntArray |> isNone, 27 | emptyIntArray == nil) 28 | 29 | XCTAssertEqual(someIntArray |> isNone, 30 | someIntArray == nil) 31 | } 32 | 33 | 34 | func test_isSome() throws { 35 | // Act & Assert 36 | XCTAssertEqual(noneInt |> isSome, 37 | noneInt != nil) 38 | 39 | XCTAssertEqual(someInt |> isSome, 40 | someInt != nil) 41 | 42 | XCTAssertEqual(noneString |> isSome, 43 | noneString != nil) 44 | 45 | XCTAssertEqual(emptySomeString |> isSome, 46 | emptySomeString != nil) 47 | 48 | XCTAssertEqual(someSomeString |> isSome, 49 | someSomeString != nil) 50 | 51 | XCTAssertEqual(noneIntArray |> isSome, 52 | noneIntArray != nil) 53 | 54 | XCTAssertEqual(emptyIntArray |> isSome, 55 | emptyIntArray != nil) 56 | 57 | XCTAssertEqual(someIntArray |> isSome, 58 | someIntArray != nil) 59 | } 60 | 61 | 62 | func test_isNotNone() throws { 63 | // Act & Assert 64 | XCTAssertFalse(noneInt |> isNotNone) 65 | XCTAssertEqual(noneInt |> isNotNone, 66 | noneInt != nil) 67 | 68 | XCTAssertTrue (someInt |> isNotNone) 69 | XCTAssertEqual(someInt |> isNotNone, 70 | someInt != nil) 71 | } 72 | 73 | 74 | // MARK: - or 75 | 76 | func test_or_shouldNotChangeSome() { 77 | XCTAssertEqual( 78 | or(someInt, 69), 79 | someInt 80 | ) 81 | 82 | XCTAssertEqual( 83 | or(someInt, { print(#function) ; return 69 }), 84 | someInt 85 | ) 86 | } 87 | 88 | 89 | func test_or_shouldGiveDefaultValue() { 90 | XCTAssertEqual( 91 | noneInt.or(69), 92 | 69 93 | ) 94 | 95 | XCTAssertEqual( 96 | or(noneInt, { print(#function) ; return 69 }), 97 | 69 98 | ) 99 | } 100 | 101 | 102 | // MARK: - andThen 103 | func test_andThen_should_operateOnWrappedValue() { 104 | // Arrange 105 | let didCallTransform = expectation(description: "transform was called") 106 | 107 | // Act 108 | andThen( 109 | someInt, 110 | { _ in didCallTransform.fulfill() } 111 | ) 112 | 113 | // Assert 114 | waitForExpectations(timeout: 0.5) 115 | } 116 | 117 | 118 | func test_andThen_should_callEachAndThenBlockForSomeCases() { 119 | // Arrange 120 | let didCallTransform = expectation(description: "transform was called") 121 | didCallTransform.expectedFulfillmentCount = 3 122 | 123 | // Act 124 | _ = someInt 125 | |> andThen({ (wrapped: Int) -> Int in 126 | didCallTransform.fulfill() 127 | return wrapped 128 | }) 129 | |> andThen({ (wrapped: Int) -> Int in 130 | didCallTransform.fulfill() 131 | return wrapped 132 | }) 133 | |> andThen({ (wrapped: Int) -> Int in 134 | didCallTransform.fulfill() 135 | return wrapped 136 | }) 137 | 138 | // Assert 139 | waitForExpectations(timeout: 0.5) 140 | } 141 | 142 | func test_andThen_should_passInTheWrappedValue() { 143 | // Arrange 144 | var accumulator: [Int] = [] 145 | 146 | // Act 147 | andThen( 148 | someInt, 149 | { wrapped in accumulator.append(wrapped) } 150 | ) 151 | 152 | // Assert 153 | XCTAssertEqual(accumulator, [42]) 154 | } 155 | 156 | func test_andThenCurried_should_passInTheWrappedValue() { 157 | // Arrange 158 | var accumulator: [Int] = [] 159 | 160 | // Act 161 | someInt 162 | |> andThen({ wrapped in accumulator.append(wrapped) }) 163 | 164 | // Assert 165 | XCTAssertEqual(accumulator, [42]) 166 | } 167 | 168 | 169 | func test_andThen_should_returnedTransformedValue() { 170 | // Act & Assert 171 | XCTAssertEqual( 172 | someInt 173 | |> andThen({ wrapped in wrapped + 1 }), 174 | .some(42 + 1) 175 | ) 176 | } 177 | 178 | func test_andThen_shouldNot_operateOnNoneCase() { 179 | // Arrange 180 | let didCallTransform = expectation(description: "transform was called") 181 | didCallTransform.isInverted = true 182 | 183 | // Act 184 | noneInt 185 | |> andThen({ _ in didCallTransform.fulfill() }) 186 | 187 | // Assert 188 | waitForExpectations(timeout: 0.5) 189 | } 190 | 191 | 192 | // MARK: - mapNone 193 | func test_mapNone_should_operateOnNoneValue() { 194 | // Arrange 195 | let didCallTransform = expectation(description: "transform was called") 196 | 197 | // Act 198 | mapNone( 199 | noneInt, 200 | { 201 | didCallTransform.fulfill() 202 | return 24 } 203 | ) 204 | 205 | // Assert 206 | waitForExpectations(timeout: 0.5) 207 | } 208 | 209 | func test_mapNoneCurried_should_operateOnNoneValue() { 210 | // Arrange 211 | let didCallTransform = expectation(description: "transform was called") 212 | 213 | // Act 214 | noneInt 215 | |> mapNone({ 216 | didCallTransform.fulfill() 217 | return 24 }) 218 | 219 | // Assert 220 | waitForExpectations(timeout: 0.5) 221 | } 222 | 223 | func test_mapNone_shouldNot_operateOnSomeValue() { 224 | // Arrange 225 | let didCallTransform = expectation(description: "transform was called") 226 | didCallTransform.isInverted = true 227 | 228 | // Act 229 | someInt 230 | |> mapNone({ 231 | didCallTransform.fulfill() 232 | return 24}) 233 | 234 | // Assert 235 | waitForExpectations(timeout: 0.5) 236 | } 237 | 238 | func test_mapNone_should_returnedSameSomeForSomeValue() { 239 | // Act 240 | let result = someInt 241 | |> mapNone(24) 242 | 243 | // Assert 244 | XCTAssertEqual(someInt, result) 245 | } 246 | 247 | func test_default_should_returnDefaultValueForNoneCase() { 248 | // Arrange 249 | let defaultValue = 24 250 | 251 | // Act 252 | let result = 253 | defaultSome(noneInt, defaultValue) 254 | 255 | // Assert 256 | XCTAssertNotNil(result) 257 | XCTAssertEqual(defaultValue, result) 258 | } 259 | 260 | func test_defaultCurried_should_returnDefaultValueForNoneCase() { 261 | // Arrange 262 | let defaultValue = 24 263 | 264 | // Act 265 | let result = 266 | noneInt |> defaultSome(defaultValue) 267 | 268 | // Assert 269 | XCTAssertNotNil(result) 270 | XCTAssertEqual(defaultValue, result) 271 | } 272 | 273 | 274 | func test_default_should_returnedSameSomeForSomeValue() { 275 | // Arrange 276 | let defaultValue = 24 277 | 278 | // Act 279 | let result = someInt 280 | |> defaultSome(defaultValue) 281 | 282 | // Assert 283 | XCTAssertNotNil(result) 284 | XCTAssertEqual(someInt, result) 285 | } 286 | 287 | func test_or_typeShouldBe_wrapped() { 288 | var intExpected: Int = someInt |> or(69) 289 | intExpected = noneInt |> or(69) 290 | 291 | // Redundant test but show how type system 292 | // is checking that's correct. 293 | XCTAssertTrue( 294 | intExpected is Int 295 | ) 296 | } 297 | 298 | func test_or_forNone_shouldReturn_providedDefault() { 299 | XCTAssertEqual( 300 | noneInt |> or(69), 301 | 69 302 | ) 303 | 304 | XCTAssertEqual( 305 | noneString |> or("default string"), 306 | "default string" 307 | ) 308 | } 309 | 310 | func test_or_forSome_shouldReturn_wrappedValue() { 311 | XCTAssertEqual( 312 | someInt |> or(69), 313 | 42 314 | ) 315 | 316 | XCTAssertEqual( 317 | someSomeString |> or("default string"), 318 | "some string" 319 | ) 320 | } 321 | 322 | // MARK: - Collections Properties 323 | 324 | func test_isNoneOrEmpty() { 325 | XCTAssertEqual( 326 | noneString |> isNoneOrEmpty, 327 | noneString == nil || noneString!.isEmpty 328 | ) 329 | XCTAssertTrue(noneString |> isNoneOrEmpty) 330 | 331 | XCTAssertEqual( 332 | emptySomeString |> isNoneOrEmpty, 333 | emptySomeString == nil || emptySomeString!.isEmpty 334 | ) 335 | XCTAssertTrue(emptySomeString |> isNoneOrEmpty) 336 | 337 | XCTAssertEqual( 338 | someSomeString |> isNoneOrEmpty, 339 | someSomeString == nil || someSomeString!.isEmpty 340 | ) 341 | XCTAssertFalse(someSomeString |> isNoneOrEmpty) 342 | 343 | XCTAssertEqual( 344 | noneIntArray |> isNoneOrEmpty, 345 | noneIntArray == nil || noneIntArray!.isEmpty 346 | ) 347 | XCTAssertTrue(noneIntArray |> isNoneOrEmpty) 348 | 349 | XCTAssertEqual( 350 | emptyIntArray |> isNoneOrEmpty, 351 | emptyIntArray == nil || emptyIntArray!.isEmpty 352 | ) 353 | XCTAssertTrue(emptyIntArray |> isNoneOrEmpty) 354 | 355 | XCTAssertEqual( 356 | someIntArray |> isNoneOrEmpty, 357 | someIntArray == nil || someIntArray!.isEmpty 358 | ) 359 | XCTAssertFalse(someIntArray |> isNoneOrEmpty) 360 | } 361 | 362 | func test_hasElements() { 363 | 364 | XCTAssertEqual( 365 | noneString |> hasElements, 366 | noneString != nil && noneString!.isEmpty == false 367 | ) 368 | XCTAssertFalse(noneString |> hasElements) 369 | 370 | XCTAssertEqual( 371 | emptySomeString |> hasElements, 372 | emptySomeString != nil && emptySomeString!.isEmpty == false 373 | ) 374 | XCTAssertFalse(emptySomeString |> hasElements) 375 | 376 | XCTAssertEqual( 377 | someSomeString |> hasElements, 378 | someSomeString != nil && someSomeString!.isEmpty == false 379 | ) 380 | XCTAssertTrue (someSomeString |> hasElements) 381 | 382 | XCTAssertEqual( 383 | noneIntArray |> hasElements, 384 | noneIntArray != nil && noneIntArray!.isEmpty == false 385 | ) 386 | XCTAssertFalse(noneIntArray |> hasElements) 387 | 388 | XCTAssertEqual( 389 | emptyIntArray |> hasElements, 390 | emptyIntArray != nil && emptyIntArray!.isEmpty == false 391 | ) 392 | XCTAssertFalse(emptyIntArray |> hasElements) 393 | 394 | 395 | XCTAssertEqual( 396 | someIntArray |> hasElements, 397 | someIntArray != nil && someIntArray!.isEmpty == false 398 | ) 399 | XCTAssertTrue (someIntArray |> hasElements) 400 | } 401 | 402 | func test_recoverFromEmpty_shouldNotBeCalledForNoneCase(){ 403 | // Arrange 404 | let didCallTransform = expectation(description: "recover was called") 405 | didCallTransform.isInverted = true 406 | 407 | // Act 408 | noneIntArray 409 | |> recoverFromEmpty({ () -> [Int] in 410 | didCallTransform.fulfill() 411 | return [24]}) 412 | 413 | // Assert 414 | waitForExpectations(timeout: 0.5) 415 | } 416 | 417 | func test_recoverFromEmpty_shouldNotBeCalledFor_Not_EmptyCollection(){ 418 | // Arrange 419 | let didCallTransform = expectation(description: "recover was called") 420 | didCallTransform.isInverted = true 421 | 422 | // Act 423 | let stringResult = 424 | someSomeString 425 | |> recoverFromEmpty({ () -> String in 426 | didCallTransform.fulfill() 427 | return "srting was empty"}()) 428 | 429 | XCTAssertEqual(stringResult, someSomeString) 430 | 431 | let intArrayResult = 432 | someIntArray 433 | .recoverFromEmpty({ 434 | didCallTransform.fulfill() 435 | return [5,10,15]}()) 436 | 437 | XCTAssertEqual(intArrayResult, someIntArray) 438 | 439 | // Assert 440 | waitForExpectations(timeout: 0.5) 441 | } 442 | 443 | func test_default_shouldNotBeCalledFor_NotEmptyCollectionsOrSomeValues(){ 444 | // Arrange 445 | let didCallTransform = expectation(description: "recover was not called") 446 | didCallTransform.isInverted = true 447 | 448 | let stringDefault = "default string" 449 | let intArrayDefault = [5,10,15] 450 | let intDefault = 24 451 | 452 | // Act 453 | XCTAssertEqual( 454 | someSomeString 455 | |> defaultSome({ () -> String in 456 | didCallTransform.fulfill() 457 | return stringDefault}()), 458 | 459 | someSomeString 460 | ) 461 | 462 | XCTAssertEqual( 463 | someIntArray 464 | |> defaultSome({ () -> [Int] in 465 | didCallTransform.fulfill() 466 | return intArrayDefault}()), 467 | 468 | someIntArray 469 | ) 470 | 471 | XCTAssertEqual( 472 | someInt 473 | |> defaultSome({ () -> Int in 474 | didCallTransform.fulfill() 475 | return intDefault}), 476 | 477 | someInt 478 | ) 479 | 480 | // Assert 481 | waitForExpectations(timeout: 0.5) 482 | } 483 | 484 | func test_randomChaining_stuff() { 485 | XCTAssertEqual( 486 | someInt 487 | |> defaultSome(5) 488 | |> andThen({ (i: Int) -> Int in i + 1 }) 489 | |> andThen({ (_) -> Int? in .none }) 490 | |> defaultSome(42), 491 | 42, 492 | "Final result should equal to the last default value" 493 | ) 494 | 495 | XCTAssertEqual( 496 | noneInt 497 | |> defaultSome(5) 498 | |> andThen({ (i: Int) -> Int in i + 1 }) 499 | |> andThen({ (_) -> Int? in .none }) 500 | |> defaultSome(42), 501 | 42, 502 | "Final result should equal to the last default value" 503 | ) 504 | 505 | XCTAssertEqual( 506 | noneInt 507 | |> andThen({ $0 + 1 }) 508 | |> defaultSome(42), 509 | 42, 510 | "Final result should equal to the last default value" 511 | ) 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/OptionalAPITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import OptionalAPI 3 | 4 | final class OptionalAPITests: XCTestCase { 5 | 6 | func test_isNone_property() throws { 7 | // Act & Assert 8 | XCTAssertEqual(noneInt.isNone, 9 | noneInt == nil) 10 | 11 | XCTAssertEqual(someInt.isNone, 12 | someInt == nil) 13 | 14 | XCTAssertEqual(noneString.isNone, 15 | noneString == nil) 16 | 17 | XCTAssertEqual(emptySomeString.isNone, 18 | emptySomeString == nil) 19 | 20 | XCTAssertEqual(someSomeString.isNone, 21 | someSomeString == nil) 22 | 23 | XCTAssertEqual(noneIntArray.isNone, 24 | noneIntArray == nil) 25 | 26 | XCTAssertEqual(emptyIntArray.isNone, 27 | emptyIntArray == nil) 28 | 29 | XCTAssertEqual(someIntArray.isNone, 30 | someIntArray == nil) 31 | } 32 | 33 | func test_isSome_property() throws { 34 | // Act & Assert 35 | XCTAssertEqual(noneInt.isSome, 36 | noneInt != nil) 37 | 38 | XCTAssertEqual(someInt.isSome, 39 | someInt != nil) 40 | 41 | XCTAssertEqual(noneString.isSome, 42 | noneString != nil) 43 | 44 | XCTAssertEqual(emptySomeString.isSome, 45 | emptySomeString != nil) 46 | 47 | XCTAssertEqual(someSomeString.isSome, 48 | someSomeString != nil) 49 | 50 | XCTAssertEqual(noneIntArray.isSome, 51 | noneIntArray != nil) 52 | 53 | XCTAssertEqual(emptyIntArray.isSome, 54 | emptyIntArray != nil) 55 | 56 | XCTAssertEqual(someIntArray.isSome, 57 | someIntArray != nil) 58 | } 59 | 60 | func test_isNotNone_property() throws { 61 | // Act & Assert 62 | XCTAssertFalse(noneInt.isNotNone) 63 | XCTAssertEqual(noneInt.isNotNone, 64 | noneInt != nil) 65 | 66 | XCTAssertTrue (someInt.isNotNone) 67 | XCTAssertEqual(someInt.isNotNone, 68 | someInt != nil) 69 | } 70 | 71 | func test_isNotSome_property() throws { 72 | // Act & Assert 73 | XCTAssertTrue(noneInt.isNotSome) 74 | XCTAssertEqual(noneInt.isNotSome, 75 | noneInt == nil) 76 | 77 | XCTAssertEqual(someInt.isNotSome, 78 | someInt == nil) 79 | } 80 | 81 | // MARK: - or 82 | 83 | func test_or_shouldNotChangeSome() { 84 | XCTAssertEqual( 85 | someInt.or(69), 86 | someInt 87 | ) 88 | } 89 | 90 | func test_or_shouldGiveDefaultValue() { 91 | XCTAssertEqual( 92 | noneInt.or(69), 93 | 69 94 | ) 95 | } 96 | 97 | // MARK: - andThen 98 | func test_andThen_should_operateOnWrappedValue() { 99 | // Arrange 100 | let didCallTransform = expectation(description: "transform was called") 101 | 102 | // Act 103 | someInt 104 | .andThen({ _ in didCallTransform.fulfill() }) 105 | 106 | // Assert 107 | waitForExpectations(timeout: 0.5) 108 | } 109 | 110 | func test_andThen_should_callEachAndThenBlockForSomeCases() { 111 | // Arrange 112 | let didCallTransform = expectation(description: "transform was called") 113 | didCallTransform.expectedFulfillmentCount = 3 114 | 115 | // Act 116 | someInt 117 | .andThen({ (wrapped: Int) -> Int in 118 | didCallTransform.fulfill() 119 | return wrapped 120 | }) 121 | .andThen({ (wrapped: Int) -> Int in 122 | didCallTransform.fulfill() 123 | return wrapped 124 | }) 125 | .andThen({ (wrapped: Int) -> Int in 126 | didCallTransform.fulfill() 127 | return wrapped 128 | }) 129 | 130 | // Assert 131 | waitForExpectations(timeout: 0.5) 132 | } 133 | 134 | func test_andThen_should_passInTheWrappedValue() { 135 | // Arrange 136 | var accumulator: [Int] = [] 137 | 138 | // Act 139 | someInt 140 | .andThen({ wrapped in accumulator.append(wrapped) }) 141 | 142 | // Assert 143 | XCTAssertEqual(accumulator, [42]) 144 | } 145 | 146 | func test_andThen_should_returnedTransformedValue() { 147 | // Act 148 | let result = someInt 149 | .andThen({ wrapped in wrapped + 1 }) 150 | 151 | // Assert 152 | XCTAssertEqual(result, .some(42 + 1)) 153 | } 154 | 155 | func test_andThen_shouldNot_operateOnNoneCase() { 156 | // Arrange 157 | let didCallTransform = expectation(description: "transform was called") 158 | didCallTransform.isInverted = true 159 | 160 | // Act 161 | noneInt 162 | .andThen({ _ in didCallTransform.fulfill() }) 163 | 164 | // Assert 165 | waitForExpectations(timeout: 0.5) 166 | } 167 | 168 | // MARK: - mapNone 169 | func test_mapNone_should_operateOnNoneValue() { 170 | // Arrange 171 | let didCallTransform = expectation(description: "transform was called") 172 | 173 | // Act 174 | noneInt 175 | .mapNone({ 176 | didCallTransform.fulfill() 177 | return 24}() 178 | ) 179 | 180 | // Assert 181 | waitForExpectations(timeout: 0.5) 182 | } 183 | 184 | func test_mapNone_shouldNot_operateOnSomeValue() { 185 | // Arrange 186 | let didCallTransform = expectation(description: "transform was called") 187 | didCallTransform.isInverted = true 188 | 189 | // Act 190 | someInt 191 | .mapNone({ 192 | didCallTransform.fulfill() 193 | return 24}() 194 | ) 195 | 196 | // Assert 197 | waitForExpectations(timeout: 0.5) 198 | } 199 | 200 | func test_mapNone_should_returnedSameSomeForSomeValue() { 201 | // Act 202 | let result = someInt 203 | .mapNone(24) 204 | 205 | // Assert 206 | XCTAssertEqual(someInt, result) 207 | } 208 | 209 | // MARK: - Regular Default 210 | 211 | func test_default_should_returnDefaultValueForNoneCase() { 212 | // Arrange 213 | let defaultValue = 24 214 | 215 | // Act 216 | let result = noneInt 217 | .defaultSome(defaultValue) 218 | 219 | // Assert 220 | XCTAssertNotNil(result) 221 | XCTAssertEqual(defaultValue, result) 222 | } 223 | 224 | func test_default_should_returnedSameSomeForSomeValue() { 225 | // Arrange 226 | let defaultValue = 24 227 | 228 | // Act 229 | let result = someInt 230 | .defaultSome(defaultValue) 231 | 232 | // Assert 233 | XCTAssertNotNil(result) 234 | XCTAssertEqual(someInt, result) 235 | } 236 | 237 | 238 | func test_or_typeShouldBe_wrapped() { 239 | var intExpected: Int = someInt.or(69) 240 | intExpected = noneInt.or(69) 241 | 242 | // Redundant test but show how type system 243 | // is checking that's correct. 244 | XCTAssertTrue( 245 | intExpected is Int 246 | ) 247 | } 248 | 249 | func test_or_forNone_shouldReturn_providedDefault() { 250 | XCTAssertEqual( 251 | noneInt.or(69), 252 | 69 253 | ) 254 | 255 | XCTAssertEqual( 256 | noneString.or("default string"), 257 | "default string" 258 | ) 259 | } 260 | 261 | func test_or_forSome_shouldReturn_wrappedValue() { 262 | XCTAssertEqual( 263 | someInt.or(69), 264 | 42 265 | ) 266 | 267 | XCTAssertEqual( 268 | someSomeString.or("default string"), 269 | "some string" 270 | ) 271 | } 272 | 273 | 274 | // MARK: - Collections Properties 275 | 276 | func test_isNoneOrEmpty_property() { 277 | XCTAssertEqual( 278 | noneString.isNoneOrEmpty, 279 | noneString == nil || noneString!.isEmpty 280 | ) 281 | XCTAssertTrue(noneString.isNoneOrEmpty) 282 | 283 | XCTAssertEqual( 284 | emptySomeString.isNoneOrEmpty, 285 | emptySomeString == nil || emptySomeString!.isEmpty 286 | ) 287 | XCTAssertTrue(emptySomeString.isNoneOrEmpty) 288 | 289 | XCTAssertEqual( 290 | someSomeString.isNoneOrEmpty, 291 | someSomeString == nil || someSomeString!.isEmpty 292 | ) 293 | XCTAssertFalse(someSomeString.isNoneOrEmpty) 294 | 295 | XCTAssertEqual( 296 | noneIntArray.isNoneOrEmpty, 297 | noneIntArray == nil || noneIntArray!.isEmpty 298 | ) 299 | XCTAssertTrue(noneIntArray.isNoneOrEmpty) 300 | 301 | XCTAssertEqual( 302 | emptyIntArray.isNoneOrEmpty, 303 | emptyIntArray == nil || emptyIntArray!.isEmpty 304 | ) 305 | XCTAssertTrue(emptyIntArray.isNoneOrEmpty) 306 | 307 | XCTAssertEqual( 308 | someIntArray.isNoneOrEmpty, 309 | someIntArray == nil || someIntArray!.isEmpty 310 | ) 311 | XCTAssertFalse(someIntArray.isNoneOrEmpty) 312 | } 313 | 314 | func test_hasElements_property() { 315 | 316 | XCTAssertEqual( 317 | noneString.hasElements, 318 | noneString != nil && noneString!.isEmpty == false 319 | ) 320 | XCTAssertFalse(noneString.hasElements) 321 | 322 | XCTAssertEqual( 323 | emptySomeString.hasElements, 324 | emptySomeString != nil && emptySomeString!.isEmpty == false 325 | ) 326 | XCTAssertFalse(emptySomeString.hasElements) 327 | 328 | XCTAssertEqual( 329 | someSomeString.hasElements, 330 | someSomeString != nil && someSomeString!.isEmpty == false 331 | ) 332 | XCTAssertTrue (someSomeString.hasElements) 333 | 334 | XCTAssertEqual( 335 | noneIntArray.hasElements, 336 | noneIntArray != nil && noneIntArray!.isEmpty == false 337 | ) 338 | XCTAssertFalse(noneIntArray.hasElements) 339 | 340 | XCTAssertEqual( 341 | emptyIntArray.hasElements, 342 | emptyIntArray != nil && emptyIntArray!.isEmpty == false 343 | ) 344 | XCTAssertFalse(emptyIntArray.hasElements) 345 | 346 | 347 | XCTAssertEqual( 348 | someIntArray.hasElements, 349 | someIntArray != nil && someIntArray!.isEmpty == false 350 | ) 351 | XCTAssertTrue (someIntArray.hasElements) 352 | } 353 | 354 | func test_recoverFromEmpty_shouldNotBeCalledForNoneCase(){ 355 | // Arrange 356 | let didCallTransform = expectation(description: "recover was called") 357 | didCallTransform.isInverted = true 358 | 359 | // Act 360 | noneIntArray 361 | .recoverFromEmpty({ 362 | didCallTransform.fulfill() 363 | return [24]}() 364 | ) 365 | 366 | // Assert 367 | waitForExpectations(timeout: 0.5) 368 | } 369 | 370 | func test_recoverFromEmpty_shouldNotBeCalledFor_Not_EmptyCollection(){ 371 | // Arrange 372 | let didCallTransform = expectation(description: "recover was called") 373 | didCallTransform.isInverted = true 374 | 375 | // Act 376 | let stringResult = 377 | someSomeString 378 | .recoverFromEmpty({ 379 | didCallTransform.fulfill() 380 | return "srting was empty"}()) 381 | 382 | XCTAssertEqual(stringResult, someSomeString) 383 | 384 | let intArrayResult = 385 | someIntArray 386 | .recoverFromEmpty({ 387 | didCallTransform.fulfill() 388 | return [5,10,15]}()) 389 | 390 | XCTAssertEqual(intArrayResult, someIntArray) 391 | 392 | // Assert 393 | waitForExpectations(timeout: 0.5) 394 | } 395 | 396 | func test_recoverFromEmpty_shouldBeCalledFor_EmptyCollection(){ 397 | // Arrange 398 | let didCallTransform = expectation(description: "recover was not called") 399 | didCallTransform.expectedFulfillmentCount = 2 400 | 401 | // Act 402 | let stringResult = 403 | emptySomeString 404 | .recoverFromEmpty({ 405 | didCallTransform.fulfill() 406 | return "string was empty"}()) 407 | 408 | XCTAssertEqual(stringResult, "string was empty") 409 | 410 | let intArrayResult = 411 | emptyIntArray 412 | .recoverFromEmpty({ 413 | didCallTransform.fulfill() 414 | return [5,10,15]}()) 415 | 416 | XCTAssertEqual(intArrayResult, [5,10,15]) 417 | 418 | // Assert 419 | waitForExpectations(timeout: 0.5) 420 | } 421 | 422 | func test_default_shouldNotBeCalledFor_NotEmptyCollectionsOrSomeValues(){ 423 | // Arrange 424 | let didCallTransform = expectation(description: "recover was not called") 425 | didCallTransform.isInverted = true 426 | 427 | let stringDefault = "default string" 428 | let intArrayDefault = [5,10,15] 429 | let intDefault = 24 430 | 431 | // Act 432 | XCTAssertEqual( 433 | someSomeString 434 | .defaultSome({ 435 | didCallTransform.fulfill() 436 | return stringDefault}()), 437 | 438 | someSomeString 439 | ) 440 | 441 | XCTAssertEqual( 442 | someIntArray 443 | .defaultSome({ 444 | didCallTransform.fulfill() 445 | return intArrayDefault}()), 446 | 447 | someIntArray 448 | ) 449 | 450 | XCTAssertEqual( 451 | someInt 452 | .defaultSome({ 453 | didCallTransform.fulfill() 454 | return intDefault}()), 455 | 456 | someInt 457 | ) 458 | 459 | // Assert 460 | waitForExpectations(timeout: 0.5) 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/OptionalCodableTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import OptionalAPI 3 | 4 | let codableStruct: CodableStruct = .init(number: 69, message: "codable message") 5 | 6 | let sut : CodableStruct? = codableStruct 7 | let noneCase: CodableStruct? = .none 8 | 9 | let codableStructAsData: Data? = 10 | """ 11 | { 12 | "number": 55, 13 | "message": "data message" 14 | } 15 | """.data(using: .utf8)! 16 | let noneData: Data? = .none 17 | 18 | 19 | final class OptionalCodableTests: XCTestCase { 20 | 21 | func test_shouldEncode() { 22 | // Arrange & Act 23 | let result = sut.encode() 24 | 25 | // Assert 26 | XCTAssertNotNil(result) 27 | } 28 | 29 | func test_shouldDecode() { 30 | // Arrange 31 | let sut: Data? = codableStructAsData 32 | 33 | // Act 34 | let result: CodableStruct? = sut.decode(CodableStruct.self) 35 | 36 | // Assert 37 | XCTAssertNotNil(result) 38 | 39 | XCTAssertEqual( 40 | result, 41 | CodableStruct(number: 55, message: "data message") 42 | ) 43 | } 44 | 45 | func test_shouldDecode_withDefaultType() { 46 | // Arrange 47 | let sut: Data? = codableStructAsData 48 | 49 | // Act 50 | let result: CodableStruct? = sut.decode() 51 | 52 | // Assert 53 | XCTAssertNotNil(result) 54 | 55 | XCTAssertEqual( 56 | result, 57 | CodableStruct(number: 55, message: "data message") 58 | ) 59 | } 60 | 61 | func test_decode_shouldReturn_noneForInvalidData() { 62 | // Arrange 63 | let sut: Data? = Data() 64 | 65 | // Act 66 | let result: CodableStruct? = sut.decode() 67 | 68 | // Assert 69 | XCTAssertNil(result) 70 | } 71 | 72 | func test_decode_shouldReturn_noneForNoneData() { 73 | // Arrange 74 | let sut: Data? = .none 75 | 76 | // Act 77 | let result: CodableStruct? = sut.decode() 78 | 79 | // Assert 80 | XCTAssertNil(result) 81 | } 82 | 83 | func test_freeFunctions_should_yieldSameResultsAsExtension() { 84 | // MARK: Encode 85 | XCTAssertEqual( 86 | sut.encode()!, 87 | encode(sut)! 88 | ) 89 | 90 | XCTAssertEqual( 91 | noneCase.encode(), 92 | encode(noneCase) 93 | ) 94 | 95 | // MARK: Decode 96 | XCTAssertEqual( 97 | codableStructAsData.decode(CodableStruct.self), 98 | decode(codableStructAsData, CodableStruct.self) 99 | ) 100 | XCTAssertEqual( 101 | codableStructAsData.decode(CodableStruct.self), 102 | decode(CodableStruct.self)(codableStructAsData) // curried 103 | ) 104 | 105 | XCTAssertEqual( 106 | noneData.decode(CodableStruct.self), 107 | decode(noneData, CodableStruct.self) 108 | ) 109 | XCTAssertEqual( 110 | noneData.decode(CodableStruct.self), 111 | decode(CodableStruct.self)(noneData) // curried 112 | ) 113 | } 114 | 115 | func test_encodeDecode_randomStuff() { 116 | XCTAssertEqual( 117 | sut, 118 | sut 119 | .encode() 120 | .decode(CodableStruct.self) 121 | .encode() 122 | .decode(CodableStruct.self) 123 | .encode() 124 | .decode(CodableStruct.self), 125 | "Should not loose any information!" 126 | ) 127 | 128 | XCTAssertEqual( 129 | CodableStruct(number: 55, message: "data message"), 130 | codableStructAsData 131 | .decode(CodableStruct.self) 132 | .encode() 133 | .decode(CodableStruct.self) 134 | .encode() 135 | .decode(CodableStruct.self) 136 | ) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/RunWhenTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import OptionalAPI 3 | 4 | class RunWhenTests: XCTestCase { 5 | 6 | func test_whenSome_shouldCallBlock_onlyWhenIsSome() { 7 | // Arrange 8 | let sut: Int? = 42 9 | 10 | let shouldCallBlock = expectation(description: "Block should have been called!") 11 | shouldCallBlock.assertForOverFulfill = true 12 | 13 | // Act 14 | sut.whenSome { wrapped in 15 | XCTAssertEqual(wrapped, 42, "Should not modify value!") 16 | shouldCallBlock.fulfill() 17 | } 18 | 19 | // Assert 20 | waitForExpectations(timeout: 2) 21 | } 22 | 23 | func test_whenSome_withNoArguments_shouldCallBlock_onlyWhenIsSome() { 24 | // Arrange 25 | let sut: Int? = 42 26 | 27 | let shouldCallBlock = expectation(description: "Block should have been called!") 28 | shouldCallBlock.assertForOverFulfill = true 29 | 30 | // Act 31 | sut.whenSome { shouldCallBlock.fulfill() } 32 | 33 | // Assert 34 | waitForExpectations(timeout: 2) 35 | } 36 | 37 | func test_whenNone_shouldCallBlock_onlyWhenIsSome() { 38 | // Arrange 39 | let sut: Int? = .none 40 | 41 | let shouldCallBlock = expectation(description: "Block should have been called!") 42 | shouldCallBlock.assertForOverFulfill = true 43 | 44 | // Act 45 | sut.whenNone { shouldCallBlock.fulfill() } 46 | 47 | // Assert 48 | waitForExpectations(timeout: 2) 49 | } 50 | 51 | func test_tryWhenSome_withArgument_shouldCallBlock_onlyWhenIsSome() throws { 52 | // Arrange 53 | let sut: Int? = 42 54 | 55 | let shouldCallBlock = expectation(description: "Block should have been called!") 56 | shouldCallBlock.assertForOverFulfill = true 57 | 58 | // Act 59 | try sut.tryWhenSome { wrapped in 60 | XCTAssertEqual(wrapped, 42, "Should not modify value!") 61 | shouldCallBlock.fulfill() 62 | } 63 | 64 | // Assert 65 | waitForExpectations(timeout: 2) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/SUTs.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Systems Under Test 4 | 5 | /// `.none` 6 | let noneString: String? = .none 7 | 8 | /// "" 9 | let emptySomeString: String? = "" 10 | 11 | /// "some string" 12 | let someSomeString: String? = "some string" 13 | 14 | /// `.none` 15 | let noneIntArray : [Int]? = .none 16 | 17 | /// Empty array `[]` 18 | let emptyIntArray: [Int]? = [] 19 | 20 | /// Array with elements `[11, 22, 33]`. 21 | let someIntArray : [Int]? = [11, 22, 33] 22 | 23 | /// .none 24 | let noneInt: Int? = .none 25 | 26 | /// `.some( 42 )` 27 | let someInt: Int? = .some(42) 28 | 29 | /// `String?.some("any string")` 30 | let anyString: Any? = String?.some("any string") 31 | 32 | /// `String?.none` 33 | let anyNoneString: Any? = String?.none 34 | 35 | let anyInt: Any? = someInt 36 | let anyNoneInt: Any? = noneInt 37 | 38 | // MARK: - Codable 39 | 40 | struct CodableStruct: Codable, Equatable { 41 | let number: Int 42 | let message: String 43 | } 44 | 45 | // MARK: - Throwing 46 | 47 | enum DummyError: Error { 48 | case boom 49 | } 50 | 51 | /// Always throws `DummyError.boom`. 52 | func alwaysThrowing(_ anything: T) throws -> String { 53 | throw DummyError.boom 54 | } 55 | 56 | /// Always returns string `"It works fine"`. 57 | func alwaysReturningString(_ anything: T) throws -> String { 58 | "It works fine" 59 | } 60 | 61 | // MARK: - Application 62 | 63 | precedencegroup ForwardApplication { 64 | associativity: left 65 | higherThan: AssignmentPrecedence 66 | } 67 | 68 | infix operator |>: ForwardApplication 69 | 70 | /// Applys function `f` to value `x`. 71 | @discardableResult 72 | public func |> (x: A, f: (A) -> B) -> B { 73 | f(x) 74 | } 75 | -------------------------------------------------------------------------------- /Tests/OptionalAPITests/TryAsyncMapsTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | import XCTest 5 | import OptionalAPI 6 | 7 | final class TryAsyncMapsTests: XCTestCase { 8 | 9 | // MARK: Map 10 | 11 | func test_tryAsyncMap_whenNone_shouldNotCallTransform_andReturn_none() async throws { 12 | let none: Int? = .none 13 | 14 | let result: Void? = try await none.tryAsyncMap { _ in XCTFail( "Should not call this closure!" ) } 15 | 16 | XCTAssertNil( result ) 17 | } 18 | 19 | func test_tryAsyncMap_whenSome_shouldCallTransform_andReturn_expectedValue() async throws { 20 | let some: Int? = 42 21 | 22 | let result: Int? = try await some.tryAsyncMap { wrapped in 23 | try! await Task.sleep(nanoseconds: 42) 24 | return wrapped * 2 25 | } 26 | 27 | XCTAssertEqual(result, 84) 28 | } 29 | 30 | func test_tryAsyncMap_whenSome_whenTransformThrows_shouldThrow() async throws { 31 | 32 | let some: Int? = 42 33 | 34 | enum E: Error { case e } 35 | 36 | do { 37 | try await some.tryAsyncMap { wrapped in 38 | try! await Task.sleep(nanoseconds: 42) 39 | throw E.e 40 | } 41 | 42 | XCTFail("Should not reach this point!") 43 | } catch { 44 | XCTAssert(error is E) 45 | } 46 | } 47 | 48 | 49 | // MARK: Flat Map 50 | 51 | func test_tryAsyncFlatMap_whenNone_shouldNotCallTransform_andReturn_none() async throws { 52 | let none: Int? = .none 53 | 54 | let result: Void? = try await none.tryAsyncFlatMap { _ in XCTFail( "Should not call this closure!" ) } 55 | 56 | XCTAssertNil( result ) 57 | } 58 | 59 | func test_tryAsyncFlatMap_whenSome_shouldCallTransform_andReturn_expectedValue() async throws { 60 | let some: Int? = 42 61 | 62 | let result: Int? = try await some.tryAsyncFlatMap { wrapped in 63 | try! await Task.sleep(nanoseconds: 42) 64 | return wrapped * 2 65 | } 66 | 67 | XCTAssertEqual(result, 84) 68 | } 69 | 70 | func test_tryAsyncFlatMap_whenSome_whenTransformReturns_optionalValue_shouldReturnExpectedResult() async throws { 71 | let some: String? = "42" 72 | 73 | let result: Int? = try await some.tryAsyncFlatMap { (w: String) -> Int? in 74 | try! await Task.sleep(nanoseconds: 42) 75 | return Int(w) 76 | } 77 | 78 | XCTAssertEqual(result, 42) 79 | } 80 | 81 | func test_tryAsyncFlatMap_longerPipeline() async throws { 82 | let some: Int? = 42 83 | 84 | let result: Int? = try await some 85 | .tryAsyncFlatMap { 86 | try await Task.sleep(nanoseconds: 42) 87 | return $0 + 1 88 | } 89 | .flatMap { fromAsync in 90 | fromAsync * 10 91 | } 92 | 93 | XCTAssertEqual(result, 430) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | --------------------------------------------------------------------------------