summaryrefslogtreecommitdiffstats
path: root/src/Cepis/CepisClientOptions.cs
blob: ea9234de1357034b2837f4c84e7faa86a23d91b4 (plain)
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
namespace Cepis;

/// <summary>
/// How a <see cref="CepisClient"/> behaves. Every setting has a default; the
/// one that usually changes between environments is <see cref="BaseUrl"/>.
/// </summary>
/// <example>
/// <code>
/// using var client = new CepisClient(new CepisClientOptions
/// {
///     BaseUrl = "http://localhost:8080",
///     TotalTimeout = TimeSpan.FromSeconds(15),
/// });
/// </code>
/// </example>
public sealed record CepisClientOptions
{
    /// <summary>
    /// Where the API lives. Defaults to <see cref="CepisClient.DefaultUrl"/>.
    /// </summary>
    /// <remarks>
    /// Point it at a local instance or at a test server. A trailing slash is
    /// stripped when the client is built, so it cannot produce doubled
    /// separators that some proxies normalise and others answer 404 to.
    /// </remarks>
    public string BaseUrl { get; init; } = CepisClient.DefaultUrl;

    /// <summary>
    /// How long a single attempt may take, connection included. Defaults to ten
    /// seconds.
    /// </summary>
    /// <remarks>
    /// It is not a cap on the whole call: with three attempts and the waits
    /// between them, the worst case is roughly three times this plus the
    /// backoffs. <see cref="TotalTimeout"/> is the cap on the whole call.
    /// </remarks>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(10);

    /// <summary>
    /// How many attempts in total, counting the first. Defaults to three.
    /// </summary>
    /// <remarks>
    /// <c>1</c> disables retrying. The default of <c>3</c> covers the common
    /// case, one transient failure, without turning a real outage into a long
    /// wait.
    /// </remarks>
    public int MaxAttempts { get; init; } = 3;

    /// <summary>
    /// The base of the exponential wait between attempts. Defaults to 500
    /// milliseconds.
    /// </summary>
    /// <remarks>
    /// The actual wait is drawn from <c>[0, base * 2^(n-1)]</c> — full jitter.
    /// The randomness is not decoration: without it, N clients that fail
    /// together come back together and reproduce the spike that took the
    /// service down.
    /// </remarks>
    public TimeSpan BackoffBase { get; init; } = TimeSpan.FromMilliseconds(500);

    /// <summary>
    /// The cap on each individual wait. Defaults to eight seconds.
    /// </summary>
    /// <remarks>
    /// It applies to a server-sent <c>Retry-After</c> too, or a
    /// <c>Retry-After: 3600</c> from a misconfigured proxy would park the
    /// caller for an hour.
    /// </remarks>
    public TimeSpan BackoffMax { get; init; } = TimeSpan.FromSeconds(8);

    /// <summary>
    /// The cap on the whole call, attempts and waits included. Unset by
    /// default.
    /// </summary>
    /// <remarks>
    /// <see langword="null"/> leaves <see cref="MaxAttempts"/> as the only
    /// bound. Set it in code serving a web request: without it the worst case
    /// is <see cref="MaxAttempts"/> times <see cref="Timeout"/> plus the sum of
    /// the backoffs.
    /// </remarks>
    public TimeSpan? TotalTimeout { get; init; }

    /// <summary>
    /// Overrides the <c>User-Agent</c>. <see langword="null"/> uses the
    /// default.
    /// </summary>
    /// <remarks>
    /// The default identifies the library and its version, which is what lets
    /// the server diagnose an old client misbehaving.
    /// </remarks>
    public string? UserAgent { get; init; }
}