• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rwjdk / TrelloDotNet / 16290700419

15 Jul 2025 10:23AM UTC coverage: 79.517% (-0.07%) from 79.585%
16290700419

push

github

rwjdk
Assistant moved to its own private Repo to avoid confusion

2537 of 3464 branches covered (73.24%)

Branch coverage included in aggregate %.

4478 of 5358 relevant lines covered (83.58%)

118.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.64
/src/TrelloDotNet/Control/ApiRequestController.cs
1
using System;
2
using System.Net;
3
using System.Net.Http;
4
using System.Net.Http.Headers;
5
using System.Text;
6
using System.Text.Json;
7
using System.Threading;
8
using System.Threading.Tasks;
9
using TrelloDotNet.Model;
10

11
namespace TrelloDotNet.Control
12
{
13
    internal class ApiRequestController
14
    {
15
        private const string BaseUrl = "https://api.trello.com/1/";
16
        private readonly HttpClient _httpClient;
17
        private readonly string _apiKey;
18
        private readonly string _token;
19
        private readonly TrelloClient _client;
20

21
        internal HttpClient HttpClient => _httpClient;
3✔
22

23
        internal ApiRequestController(HttpClient httpClient, string apiKey, string token, TrelloClient client)
1,291✔
24
        {
25
            _httpClient = httpClient;
1,291✔
26
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
1,291✔
27
            _apiKey = apiKey;
1,291✔
28
            _token = token;
1,291✔
29
            _client = client;
1,291✔
30
        }
1,291✔
31

32
        public string Token => _token;
35✔
33

34
        public string ApiKey => _apiKey;
1✔
35

36
        internal async Task<T> Get<T>(string suffix, CancellationToken cancellationToken, params QueryParameter[] parameters)
37
        {
38
            string json = await Get(suffix, cancellationToken, 0, parameters);
858✔
39
            var @object = JsonSerializer.Deserialize<T>(json);
858✔
40
            return @object;
858✔
41
        }
858✔
42

43
        internal async Task<string> Get(string suffix, CancellationToken cancellationToken, int retryCount, params QueryParameter[] parameters)
44
        {
45
            var uri = BuildUri(suffix, parameters);
878✔
46
            var response = await _httpClient.GetAsync(uri, cancellationToken);
878✔
47
            var responseContent = await response.Content.ReadAsStringAsync();
878✔
48

49
            if (response.StatusCode != HttpStatusCode.OK)
878✔
50
            {
51
                return await PreformRetryIfNeededOrThrow(uri, response.StatusCode, responseContent, retry => Get(suffix, cancellationToken, retry, parameters), retryCount, cancellationToken);
4✔
52
            }
53

54
            return responseContent; //Content is assumed JSON
874✔
55
        }
874✔
56

57
        internal async Task<T> Post<T>(string suffix, CancellationToken cancellationToken, params QueryParameter[] parameters)
58
        {
59
            string json = await Post(suffix, cancellationToken, 0, parameters);
453✔
60
            var @object = JsonSerializer.Deserialize<T>(json);
453✔
61
            return @object;
453✔
62
        }
453✔
63

64
        internal async Task<T> PostWithAttachmentFileUpload<T>(string suffix, AttachmentFileUpload attachmentFile, CancellationToken cancellationToken, params QueryParameter[] parameters)
65
        {
66
            string json = await PostWithAttachmentFileUpload(suffix, attachmentFile, cancellationToken, 0, parameters);
2✔
67
            var @object = JsonSerializer.Deserialize<T>(json);
2✔
68
            return @object;
2✔
69
        }
2✔
70

71
        internal async Task<string> PostWithAttachmentFileUpload(string suffix, AttachmentFileUpload attachmentFile, CancellationToken cancellationToken, int retryCount, params QueryParameter[] parameters)
72
        {
73
            var uri = BuildUri(suffix, parameters);
2✔
74
            using (var multipartFormContent = new MultipartFormDataContent())
2✔
75
            {
76
                multipartFormContent.Add(new StreamContent(attachmentFile.Stream), name: "file", fileName: attachmentFile.Filename);
2✔
77
                var response = await _httpClient.PostAsync(uri, multipartFormContent, cancellationToken);
2✔
78
                var responseContent = await response.Content.ReadAsStringAsync();
2✔
79

80
                if (response.StatusCode != HttpStatusCode.OK)
2!
81
                {
82
                    return await PreformRetryIfNeededOrThrow(uri, response.StatusCode, responseContent, retry => PostWithAttachmentFileUpload(suffix, attachmentFile, cancellationToken, retry, parameters), retryCount, cancellationToken);
×
83
                }
84

85
                return responseContent; //Content is assumed JSON
2✔
86
            }
87
        }
2✔
88

89
        internal async Task<string> Post(string suffix, CancellationToken cancellationToken, int retryCount, params QueryParameter[] parameters)
90
        {
91
            var uri = BuildUri(suffix, parameters);
457✔
92
            var response = await _httpClient.PostAsync(uri, null, cancellationToken);
457✔
93
            var content = await response.Content.ReadAsStringAsync();
457✔
94

95
            if (response.StatusCode != HttpStatusCode.OK)
457✔
96
            {
97
                return await PreformRetryIfNeededOrThrow(uri, response.StatusCode, content, retry => Post(suffix, cancellationToken, retry, parameters), retryCount, cancellationToken);
1✔
98
            }
99

100
            return content; //Content is assumed JSON
456✔
101
        }
456✔
102

103
        internal async Task<T> Put<T>(string suffix, CancellationToken cancellationToken, params QueryParameter[] parameters)
104
        {
105
            string json = await Put(suffix, cancellationToken, 0, parameters);
139✔
106
            var @object = JsonSerializer.Deserialize<T>(json);
139✔
107
            return @object;
139✔
108
        }
139✔
109

110
        internal async Task<string> Put(string suffix, CancellationToken cancellationToken, int retryCount, params QueryParameter[] parameters)
111
        {
112
            var uri = BuildUri(suffix, parameters);
145✔
113
            var response = await _httpClient.PutAsync(uri, null, cancellationToken);
145✔
114
            var responseContent = await response.Content.ReadAsStringAsync();
145✔
115

116
            if (response.StatusCode != HttpStatusCode.OK)
145✔
117
            {
118
                return await PreformRetryIfNeededOrThrow(uri, response.StatusCode, responseContent, retry => Put(suffix, cancellationToken, retry, parameters), retryCount, cancellationToken);
1✔
119
            }
120

121
            return responseContent; //Content is assumed JSON
144✔
122
        }
144✔
123

124
        internal async Task<T> PutWithJsonPayload<T>(string suffix, CancellationToken cancellationToken, string payload, params QueryParameter[] parameters)
125
        {
126
            string json = await PutWithJsonPayload(suffix, cancellationToken, payload, 0, parameters);
13✔
127
            var @object = JsonSerializer.Deserialize<T>(json);
13✔
128
            return @object;
13✔
129
        }
13✔
130

131
        internal async Task<string> PutWithJsonPayload(string suffix, CancellationToken cancellationToken, string payload, int retryCount, params QueryParameter[] parameters)
132
        {
133
            var uri = BuildUri(suffix, parameters);
31✔
134
            var response = await _httpClient.PutAsync(uri, new StringContent(payload, Encoding.UTF8, "application/json"), cancellationToken);
31✔
135
            var responseContent = await response.Content.ReadAsStringAsync();
31✔
136

137
            if (response.StatusCode != HttpStatusCode.OK)
31!
138
            {
139
                return await PreformRetryIfNeededOrThrow(uri, response.StatusCode, responseContent, retry => PutWithJsonPayload(suffix, cancellationToken, payload, retry, parameters), retryCount, cancellationToken);
×
140
            }
141

142
            return responseContent; //Content is assumed JSON
31✔
143
        }
31✔
144

145
        internal async Task<T> PostWithJsonPayload<T>(string suffix, CancellationToken cancellationToken, string payload, params QueryParameter[] parameters)
146
        {
147
            string json = await PostWithJsonPayload(suffix, cancellationToken, payload, 0, parameters);
1✔
148
            var @object = JsonSerializer.Deserialize<T>(json);
1✔
149
            return @object;
1✔
150
        }
1✔
151

152
        internal async Task<string> PostWithJsonPayload(string suffix, CancellationToken cancellationToken, string payload, int retryCount, params QueryParameter[] parameters)
153
        {
154
            var uri = BuildUri(suffix, parameters);
1✔
155
            var response = await _httpClient.PostAsync(uri, new StringContent(payload, Encoding.UTF8, "application/json"), cancellationToken);
1✔
156
            var responseContent = await response.Content.ReadAsStringAsync();
1✔
157

158
            if (response.StatusCode != HttpStatusCode.OK)
1!
159
            {
160
                return await PreformRetryIfNeededOrThrow(uri, response.StatusCode, responseContent, retry => PostWithJsonPayload(suffix, cancellationToken, payload, retry, parameters), retryCount, cancellationToken);
×
161
            }
162

163
            return responseContent; //Content is assumed JSON
1✔
164
        }
1✔
165

166
        private string FormatExceptionUrlAccordingToClientOptions(string fullUrl)
167
        {
168
            switch (_client.Options.ApiCallExceptionOption)
7!
169
            {
170
                case ApiCallExceptionOption.IncludeUrlAndCredentials:
171
                    return fullUrl;
1✔
172
                case ApiCallExceptionOption.IncludeUrlButMaskCredentials:
173
                    // ReSharper disable StringLiteralTypo
174
                    return fullUrl.Replace($"?key={_apiKey}&token={_token}", "?key=XXXXX&token=XXXXXXXXXX");
5✔
175
                case ApiCallExceptionOption.DoNotIncludeTheUrl:
176
                    return string.Empty.PadLeft(5, 'X');
1✔
177
                default:
178
                    throw new ArgumentOutOfRangeException();
×
179
            }
180
        }
181

182
        internal static StringBuilder GetParametersAsString(QueryParameter[] parameters)
183
        {
184
            StringBuilder parameterString = new StringBuilder();
1,607✔
185
            foreach (var parameter in parameters)
19,530✔
186
            {
187
                parameterString.Append($"&{parameter.Name}={parameter.GetValueAsApiFormattedString()}");
8,158✔
188
            }
189

190
            return parameterString;
1,607✔
191
        }
192

193
        private Uri BuildUri(string suffix, params QueryParameter[] parameters)
194
        {
195
            string separator = suffix.Contains("?") ? "&" : "?";
1,596✔
196
            return new Uri($"{BaseUrl}{suffix}{separator}key={_apiKey}&token={_token}" + GetParametersAsString(parameters));
1,596!
197
        }
198

199
        internal async Task<string> Delete(string suffix, CancellationToken cancellationToken, int retryCount)
200
        {
201
            var uri = BuildUri(suffix);
82✔
202
            var response = await _httpClient.DeleteAsync(uri, cancellationToken);
82✔
203
            var responseContent = await response.Content.ReadAsStringAsync();
82✔
204

205
            if (response.StatusCode != HttpStatusCode.OK)
82✔
206
            {
207
                return await PreformRetryIfNeededOrThrow(uri, response.StatusCode, responseContent, retry => Delete(suffix, cancellationToken, retry), retryCount, cancellationToken);
1✔
208
            }
209

210
            return null;
81✔
211
        }
81✔
212

213
        private async Task<string> PreformRetryIfNeededOrThrow(Uri uri, HttpStatusCode statusCode, string responseContent, Func<int, Task<string>> toRetry, int retryCount, CancellationToken cancellationToken)
214
        {
215
            int statusCodeAsInteger = (int)statusCode;
7✔
216
            bool isTooManyRequestsStatusCode = statusCodeAsInteger == 429;
7✔
217
            if ((responseContent.Contains("API_TOKEN_LIMIT_EXCEEDED") || isTooManyRequestsStatusCode) && retryCount <= _client.Options.MaxRetryCountForTokenLimitExceeded)
7!
218
            {
219
                await Task.Delay(TimeSpan.FromSeconds(_client.Options.DelayInSecondsToWaitInTokenLimitExceededRetry), cancellationToken);
×
220
                retryCount++;
×
221
                return await toRetry(retryCount);
×
222
            }
223

224
            throw new TrelloApiException($"{responseContent} [{statusCodeAsInteger}: {statusCode}]", FormatExceptionUrlAccordingToClientOptions(uri.AbsoluteUri)); //Content is assumed Error Message       
7✔
225
        }
×
226
    }
227
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc