Slather logo

Coverage for "RoutingInterceptor.swift" : 100.00%

(37 of 37 relevant lines covered)

RouteComposer/Classes/RoutingInterceptor.swift

1
//
2
// RouteComposer
3
// RoutingInterceptor.swift
4
// https://github.com/ekazaev/route-composer
5
//
6
// Created by Eugene Kazaev in 2018-2022.
7
// Distributed under the MIT license.
8
//
9
// Become a sponsor:
10
// https://github.com/sponsors/ekazaev
11
//
12
13
import Foundation
14
15
/// `RoutingInterceptor` is called before the actual navigation process happens.
16
/// e.g. user should be logged in.
17
///
18
/// ### NB
19
/// Interceptor is an asynchronous action. For the `Router` to continue the navigation process, the `completion` block of the interceptor's
20
/// execute method **MUST** be called.
21
/// Otherwise, the `Router` will stay in a limbo state waiting for the interceptor to finish its action.
22
public protocol RoutingInterceptor {
23
24
    // MARK: Associated types
25
26
    /// `Context` type associated with `RoutingInterceptor`
27
    associatedtype Context
28
29
    // MARK: Methods to implement
30
31
    /// The `Router` will call this method before the navigation process. If `RoutingInterceptor` is not able to allow
32
    /// the navigation process to start it can stop `Router` by throwing an exception.
33
    ///
34
    /// - Parameters:
35
    ///   - context: The `Context` instance that is provided to the `Router`.
36
    /// - Throws: The `RoutingError` if the `RoutingInterceptor` cannot prepare itself or if the navigation process cannot start
37
    ///   with the `Context` instance provided.
38
    mutating func prepare(with context: Context) throws
39
40
    /// Method that will be called by `Router` to start interceptor.
41
    ///
42
    /// - Parameters:
43
    ///   - context: `Context` instance provided to the `Router`
44
    ///   - completion: Completion block with a result.
45
    ///
46
    /// ###NB
47
    /// For the `Router` to continue the navigation process, the `completion` block of interceptor **MUST** be called
48
    /// by the implementation of this method.
49
    /// Otherwise `Router` will stay in limbo waiting for `RoutingInterceptor` to finish its action.
50
    func perform(with context: Context, completion: @escaping (_: RoutingResult) -> Void)
51
52
}
53
54
// MARK: Default implementation
55
56
public extension RoutingInterceptor {
57
58
    /// Default implementation does nothing.
59
    func prepare(with context: Context) throws {}
315x
60
61
}
62
63
// MARK: Helper methods
64
65
public extension RoutingInterceptor {
66
67
    /// Prepares the `RoutingInterceptor` and executes it
68
    func execute(with context: Context, completion: @escaping (_: RoutingResult) -> Void) throws {
7x
69
        var interceptor = self
7x
70
        try interceptor.prepare(with: context)
7x
71
        interceptor.perform(with: context, completion: completion)
7x
72
    }
7x
73
74
    /// Prepares the `RoutingInterceptor` and performs it. Does not throw an exception.
75
    func commit(with context: Context, completion: @escaping (_: RoutingResult) -> Void) {
4x
76
        do {
4x
77
            try execute(with: context, completion: completion)
4x
78
        } catch {
4x
79
            completion(.failure(error))
1x
80
        }
4x
81
    }
4x
82
83
}
84
85
// MARK: Helper methods where the Context is Any?
86
87
public extension RoutingInterceptor where Context == Any? {
88
89
    /// The `Router` will call this method before the navigation process. If `RoutingInterceptor` is not able to allow
90
    /// the navigation process to start it can stop `Router` by throwing an exception.
91
    ///
92
    /// - Throws: The `RoutingError` if the `RoutingInterceptor` cannot prepare itself or if the navigation process cannot start
93
    ///   with the `Context` instance provided.
94
    mutating func prepare() throws {
1x
95
        try prepare(with: nil)
1x
96
    }
1x
97
98
    /// Method that will be called by `Router` to start interceptor.
99
    ///
100
    /// - Parameters:
101
    ///   - completion: Completion block with a result.
102
    ///
103
    /// ###NB
104
    /// For the `Router` to continue the navigation process, the `completion` block of interceptor **MUST** be called
105
    /// by the implementation of this method.
106
    /// Otherwise `Router` will stay in limbo waiting for `RoutingInterceptor` to finish its action.
107
    func perform(completion: @escaping (_: RoutingResult) -> Void) {
1x
108
        perform(with: nil, completion: completion)
1x
109
    }
1x
110
111
    /// Prepares the `RoutingInterceptor` and executes it
112
    func execute(completion: @escaping (_: RoutingResult) -> Void) throws {
1x
113
        try execute(with: nil, completion: completion)
1x
114
    }
1x
115
116
    /// Prepares the `RoutingInterceptor` and performs it. Does not throw an exception.
117
    func commit(completion: @escaping (_: RoutingResult) -> Void) {
1x
118
        commit(with: nil, completion: completion)
1x
119
    }
1x
120
121
}
122
123
// MARK: Helper methods where the Context is Void
124
125
public extension RoutingInterceptor where Context == Void {
126
127
    /// The `Router` will call this method before the navigation process. If `RoutingInterceptor` is not able to allow
128
    /// the navigation process to start it can stop `Router` by throwing an exception.
129
    ///
130
    /// - Throws: The `RoutingError` if the `RoutingInterceptor` cannot prepare itself or if the navigation process cannot start
131
    ///   with the `Context` instance provided.
132
    mutating func prepare() throws {
1x
133
        try prepare(with: ())
1x
134
    }
1x
135
136
    /// Method that will be called by `Router` to start interceptor.
137
    ///
138
    /// - Parameters:
139
    ///   - completion: Completion block with a result.
140
    ///
141
    /// ###NB
142
    /// For the `Router` to continue the navigation process, the `completion` block of interceptor **MUST** be called
143
    /// by the implementation of this method.
144
    /// Otherwise `Router` will stay in limbo waiting for `RoutingInterceptor` to finish its action.
145
    func perform(completion: @escaping (_: RoutingResult) -> Void) {
1x
146
        perform(with: (), completion: completion)
1x
147
    }
1x
148
149
    /// Prepares the `RoutingInterceptor` and executes it
150
    func execute(completion: @escaping (_: RoutingResult) -> Void) throws {
1x
151
        try execute(with: (), completion: completion)
1x
152
    }
1x
153
154
    /// Prepares the `RoutingInterceptor` and performs it. Does not throw an exception.
155
    func commit(completion: @escaping (_: RoutingResult) -> Void) {
2x
156
        commit(with: (), completion: completion)
2x
157
    }
2x
158
159
}