1
+ //! [Chain of responsibility pattern][pattern] implementation.
2
+ //!
3
+ //! lychee is based on a chain of responsibility, where each handler can modify
4
+ //! a request and decide if it should be passed to the next element or not.
5
+ //!
6
+ //! The chain is implemented as a vector of [`Chainable`] handlers. It is
7
+ //! traversed by calling [`Chain::traverse`], which will call
8
+ //! [`Chainable::chain`] on each handler in the chain consecutively.
9
+ //!
10
+ //! To add external handlers, you can implement the [`Chainable`] trait and add
11
+ //! the handler to the chain.
12
+ //!
13
+ //! [pattern]: https://github.com/lpxxn/rust-design-pattern/blob/master/behavioral/chain_of_responsibility.rs
1
14
use crate :: Status ;
2
15
use async_trait:: async_trait;
3
16
use core:: fmt:: Debug ;
4
17
use std:: sync:: Arc ;
5
18
use tokio:: sync:: Mutex ;
6
19
20
+ /// Result of a handler.
21
+ ///
22
+ /// This is used to decide if the chain should continue to the next handler or
23
+ /// stop and return the result:
24
+ ///
25
+ /// - If the chain should continue, the handler should return
26
+ /// [`ChainResult::Next`]. This will traverse the next handler in the chain.
27
+ /// - If the chain should stop, the handler should return [`ChainResult::Done`].
28
+ /// All subsequent chain elements are skipped and the result is returned.
7
29
#[ derive( Debug , PartialEq ) ]
8
- pub ( crate ) enum ChainResult < T , R > {
30
+ pub enum ChainResult < T , R > {
31
+ /// Continue to the next handler in the chain.
9
32
Next ( T ) ,
33
+ /// Stop the chain and return the result.
10
34
Done ( R ) ,
11
35
}
12
36
37
+ /// Request chain type
38
+ ///
39
+ /// This takes a request and returns a status.
13
40
pub ( crate ) type RequestChain = Chain < reqwest:: Request , Status > ;
14
41
42
+ /// Inner chain type.
43
+ ///
44
+ /// This holds all handlers, which were chained together.
45
+ /// Handlers are traversed in order.
46
+ ///
47
+ /// Each handler needs to implement the `Chainable` trait and be `Send`, because
48
+ /// the chain is traversed concurrently and the handlers can be sent between
49
+ /// threads.
15
50
pub ( crate ) type InnerChain < T , R > = Vec < Box < dyn Chainable < T , R > + Send > > ;
16
51
52
+ /// The outer chain type.
53
+ ///
54
+ /// This is a wrapper around the inner chain type and allows for
55
+ /// concurrent access to the chain.
17
56
#[ derive( Debug ) ]
18
57
pub struct Chain < T , R > ( Arc < Mutex < InnerChain < T , R > > > ) ;
19
58
20
59
impl < T , R > Default for Chain < T , R > {
21
60
fn default ( ) -> Self {
22
- Self ( Arc :: new ( Mutex :: new ( vec ! [ ] ) ) )
61
+ Self ( Arc :: new ( Mutex :: new ( InnerChain :: default ( ) ) ) )
23
62
}
24
63
}
25
64
26
65
impl < T , R > Clone for Chain < T , R > {
27
66
fn clone ( & self ) -> Self {
67
+ // Cloning the chain is a cheap operation, because the inner chain is
68
+ // wrapped in an `Arc` and `Mutex`.
28
69
Self ( self . 0 . clone ( ) )
29
70
}
30
71
}
31
72
32
73
impl < T , R > Chain < T , R > {
74
+ /// Create a new chain from a vector of chainable handlers
33
75
pub ( crate ) fn new ( values : InnerChain < T , R > ) -> Self {
34
76
Self ( Arc :: new ( Mutex :: new ( values) ) )
35
77
}
36
78
79
+ /// Traverse the chain with the given input.
80
+ ///
81
+ /// This will call `chain` on each handler in the chain and return
82
+ /// the result. If a handler returns `ChainResult::Done`, the chain
83
+ /// will stop and return.
84
+ ///
85
+ /// If no handler returns `ChainResult::Done`, the chain will return
86
+ /// `ChainResult::Next` with the input.
37
87
pub ( crate ) async fn traverse ( & self , mut input : T ) -> ChainResult < T , R > {
38
88
use ChainResult :: { Done , Next } ;
39
89
for e in self . 0 . lock ( ) . await . iter_mut ( ) {
@@ -49,23 +99,71 @@ impl<T, R> Chain<T, R> {
49
99
}
50
100
}
51
101
102
+ /// Chainable trait for implementing request handlers
103
+ ///
104
+ /// This trait needs to be implemented by all chainable handlers.
105
+ /// It is the only requirement to handle requests in lychee.
106
+ ///
107
+ /// It takes an input request and returns a [`ChainResult`], which can be either
108
+ /// [`ChainResult::Next`] to continue to the next handler or
109
+ /// [`ChainResult::Done`] to stop the chain.
110
+ ///
111
+ /// The request can be modified by the handler before it is passed to the next
112
+ /// handler. This allows for modifying the request, such as adding headers or
113
+ /// changing the URL (e.g. for remapping or filtering).
52
114
#[ async_trait]
53
- pub ( crate ) trait Chainable < T , R > : Debug {
115
+ pub trait Chainable < T , R > : Debug {
116
+ /// Given an input request, return a [`ChainResult`] to continue or stop the
117
+ /// chain.
118
+ ///
119
+ /// The input request can be modified by the handler before it is passed to
120
+ /// the next handler.
121
+ ///
122
+ /// # Example
123
+ ///
124
+ /// ```
125
+ /// use lychee_lib::{Chainable, ChainResult, Status};
126
+ /// use reqwest::Request;
127
+ /// use async_trait::async_trait;
128
+ ///
129
+ /// #[derive(Debug)]
130
+ /// struct AddHeader;
131
+ ///
132
+ /// #[async_trait]
133
+ /// impl Chainable<Request, Status> for AddHeader {
134
+ /// async fn chain(&mut self, mut request: Request) -> ChainResult<Request, Status> {
135
+ /// // You can modify the request however you like here
136
+ /// request.headers_mut().append("X-Header", "value".parse().unwrap());
137
+ ///
138
+ /// // Pass the request to the next handler
139
+ /// ChainResult::Next(request)
140
+ /// }
141
+ /// }
142
+ /// ```
54
143
async fn chain ( & mut self , input : T ) -> ChainResult < T , R > ;
55
144
}
56
145
146
+ /// Client request chains
147
+ ///
148
+ /// This struct holds all request chains.
149
+ ///
150
+ /// Usually, this is used to hold the default request chain and the external
151
+ /// plugin request chain.
57
152
#[ derive( Debug ) ]
58
- pub ( crate ) struct ClientRequestChain < ' a > {
153
+ pub ( crate ) struct ClientRequestChains < ' a > {
59
154
chains : Vec < & ' a RequestChain > ,
60
155
}
61
156
62
- impl < ' a > ClientRequestChain < ' a > {
157
+ impl < ' a > ClientRequestChains < ' a > {
158
+ /// Create a new chain of request chains.
63
159
pub ( crate ) fn new ( chains : Vec < & ' a RequestChain > ) -> Self {
64
160
Self { chains }
65
161
}
66
162
163
+ /// Traverse all request chains and resolve to a status.
67
164
pub ( crate ) async fn traverse ( & self , mut input : reqwest:: Request ) -> Status {
68
165
use ChainResult :: { Done , Next } ;
166
+
69
167
for e in & self . chains {
70
168
match e. traverse ( input) . await {
71
169
Next ( r) => input = r,
@@ -75,7 +173,8 @@ impl<'a> ClientRequestChain<'a> {
75
173
}
76
174
}
77
175
78
- // consider as excluded if no chain element has converted it to a done
176
+ // Consider the request to be excluded if no chain element has converted
177
+ // it to a `ChainResult::Done`
79
178
Status :: Excluded
80
179
}
81
180
}
0 commit comments