├── .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://swiftpackageindex.com/sloik/OptionalAPI)
2 | [](https://swiftpackageindex.com/sloik/OptionalAPI)
3 | 
4 | 
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 |
--------------------------------------------------------------------------------