1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::time::Duration;

/// Configuration for a [`Client`]'s reconnect behaviour.
///
/// ```
/// # use std::time::Duration;
/// # use eventsource_client::ReconnectOptions;
/// #
/// let reconnect_options = ReconnectOptions::reconnect(true)
///                             .retry_initial(false)
///                             .delay(Duration::from_secs(1))
///                             .backoff_factor(2)
///                             .delay_max(Duration::from_secs(60))
///                             .build();
/// ```
///
/// See [`default()`] for a description of the default behaviour. See
/// [`ReconnectOptionsBuilder`] for descriptions of each configurable parameter.
///
/// [`Client`]: struct.Client.html
/// [`default()`]: #method.default
/// [`ReconnectOptionsBuilder`]: struct.ReconnectOptionsBuilder.html
#[derive(Clone, Debug)]
pub struct ReconnectOptions {
    pub(crate) retry_initial: bool,
    pub(crate) reconnect: bool,
    pub(crate) delay: Duration,
    pub(crate) backoff_factor: u32,
    pub(crate) delay_max: Duration,
}

impl ReconnectOptions {
    /// Start building a `ReconnectOptions`, by enabling or disabling
    /// reconnection on stream error.
    ///
    /// If `reconnect` is `true` (the [default]), the client will automatically
    /// try to reconnect if the stream ends due to an error. If it is `false`,
    /// the client will stop receiving events after an error.
    ///
    /// [default]: #method.default
    pub fn reconnect(reconnect: bool) -> ReconnectOptionsBuilder {
        ReconnectOptionsBuilder::new(reconnect)
    }
}

impl Default for ReconnectOptions {
    /// The default reconnect behaviour is to automatically try to reconnect if
    /// the stream ends due to an error, but not to retry if the initial
    /// connection fails.
    ///
    /// The client will wait before each reconnect attempt, to allow time for
    /// the error condition to be resolved (e.g. for the SSE server to restart
    /// if it went down). It will wait 1 second before the first attempt, and
    /// then back off exponentially, up to a maximum wait of 1 minute.
    fn default() -> ReconnectOptions {
        ReconnectOptions {
            retry_initial: false,
            reconnect: true,
            delay: Duration::from_secs(1),
            backoff_factor: 2,
            delay_max: Duration::from_secs(60),
        }
    }
}

/// Builder for [`ReconnectOptions`].
///
/// [`ReconnectOptions`]: struct.ReconnectOptions.html
pub struct ReconnectOptionsBuilder {
    opts: ReconnectOptions,
}

impl ReconnectOptionsBuilder {
    pub fn new(reconnect: bool) -> Self {
        let opts = ReconnectOptions {
            reconnect,
            ..Default::default()
        };
        Self { opts }
    }

    /// Configure whether to retry if the initial connection to the server
    /// fails.
    ///
    /// If `true`, the client will automatically retry the connection, with the
    /// same delay and backoff behaviour as for reconnects due to stream error.
    /// If `false` (the [default]), the client will not retry the initial
    /// connection.
    ///
    /// [default]: struct.ReconnectOptions.html#method.default
    pub fn retry_initial(mut self, retry: bool) -> Self {
        self.opts.retry_initial = retry;
        self
    }

    /// Configure the initial delay before trying to reconnect (the [default] is
    /// 1 second).
    ///
    /// After an error, the client will wait this long before the first attempt
    /// to reconnect.  Subsequent reconnect attempts may wait longer, depending
    /// on the [`backoff_factor`].
    ///
    /// [default]: struct.ReconnectOptions.html#method.default
    /// [`backoff_factor`]: #method.backoff_factor
    pub fn delay(mut self, delay: Duration) -> Self {
        self.opts.delay = delay;
        self
    }

    /// Configure the factor by which delays between reconnect attempts will
    /// exponentially increase, up to [`delay_max`]. The [default] factor is 2,
    /// so each reconnect attempt will wait twice as long as the previous one.
    ///
    /// Set this to 1 to disable exponential backoff (i.e. to make reconnect
    /// attempts at regular intervals equal to the configured [`delay`]).
    ///
    /// [`delay_max`]: #method.delay_max
    /// [default]: struct.ReconnectOptions.html#method.default
    /// [`delay`]: #method.delay
    pub fn backoff_factor(mut self, factor: u32) -> Self {
        self.opts.backoff_factor = factor;
        self
    }

    /// Configure the maximum delay between reconnects (the [default] is 1
    /// minute). The exponential backoff configured by [`backoff_factor`] will
    /// not cause a delay greater than this value.
    ///
    /// [default]: struct.ReconnectOptions.html#method.default
    /// [`backoff_factor`]: #method.backoff_factor
    pub fn delay_max(mut self, max: Duration) -> Self {
        self.opts.delay_max = max;
        self
    }

    /// Finish building the `ReconnectOptions`.
    pub fn build(self) -> ReconnectOptions {
        self.opts
    }
}