diff --git a/build/analyzers.ruleset b/build/analyzers.ruleset
index c3d0f539a2..79627376f3 100644
--- a/build/analyzers.ruleset
+++ b/build/analyzers.ruleset
@@ -1071,7 +1071,7 @@
-
+
@@ -1091,7 +1091,7 @@
-
+
@@ -1130,7 +1130,7 @@
-
+
diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs
index 0349ce9fce..e7afce2eaa 100644
--- a/src/Tgstation.Server.Api/Models/Instance.cs
+++ b/src/Tgstation.Server.Api/Models/Instance.cs
@@ -50,7 +50,10 @@ namespace Tgstation.Server.Api.Models
[NotMapped]
public Job MoveJob { get; set; }
- ///
+ ///
+ /// Create a clone of the essential metadata
+ ///
+ /// A clone of the essential metadata
public Instance CloneMetadata() => new Instance
{
Id = Id,
diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs
index b5d3b5f9a3..51d8025f73 100644
--- a/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs
@@ -13,7 +13,7 @@
///
/// Gets the associated with the
///
- ///
+ /// The associated with the
public abstract override string ToString();
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs b/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs
index b0bbb0db23..76dbe54ce2 100644
--- a/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs
@@ -42,7 +42,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// Construct a from a
///
- ///
+ /// The to copy data from
protected TestMergeBase(TestMergeBase copy)
{
if (copy == null)
diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs
index 452bcf1e23..3d01a67871 100644
--- a/src/Tgstation.Server.Client/ApiClient.cs
+++ b/src/Tgstation.Server.Client/ApiClient.cs
@@ -49,6 +49,79 @@ namespace Tgstation.Server.Client
///
ApiHeaders headers;
+ static JsonSerializerSettings GetSerializerSettings() => new JsonSerializerSettings
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver(),
+ Converters = new[] { new VersionConverter() }
+ };
+
+ static void HandleBadResponse(HttpResponseMessage response, string json)
+ {
+ ErrorMessage errorMessage = null;
+ try
+ {
+ // check if json serializes to an error message
+ errorMessage = JsonConvert.DeserializeObject(json, GetSerializerSettings());
+ }
+ catch (JsonException) { }
+
+ const string BadSpecExtension = " This is not part of TGS4 communication specification and should be reported if it was returned from a TGS4 server!";
+
+#pragma warning disable IDE0010 // Add missing cases
+ switch (response.StatusCode)
+#pragma warning restore IDE0010 // Add missing cases
+ {
+ case HttpStatusCode.UpgradeRequired:
+ throw new ApiMismatchException(errorMessage ?? new ErrorMessage
+ {
+ Message = "API Mismatch but no current API version provided!" + BadSpecExtension,
+ SeverApiVersion = null
+ });
+ case HttpStatusCode.Unauthorized:
+ throw new UnauthorizedException();
+ case HttpStatusCode.RequestTimeout:
+ throw new RequestTimeoutException();
+ case HttpStatusCode.Forbidden:
+ throw new InsufficientPermissionsException();
+ case HttpStatusCode.ServiceUnavailable:
+ throw new ServiceUnavailableException();
+ case HttpStatusCode.Gone:
+ errorMessage = errorMessage ?? new ErrorMessage
+ {
+ Message = "The requested resource could not be found!",
+ SeverApiVersion = null
+ };
+ goto case HttpStatusCode.Conflict;
+ case HttpStatusCode.NotFound:
+ // our fault somehow
+ errorMessage = errorMessage ?? new ErrorMessage
+ {
+ Message = "This is not a valid route!" + BadSpecExtension,
+ SeverApiVersion = null
+ };
+ goto case HttpStatusCode.Conflict;
+ case HttpStatusCode.Conflict:
+ throw new ConflictException(errorMessage ?? new ErrorMessage
+ {
+ Message = "An undescribed conflict occurred!" + BadSpecExtension,
+ SeverApiVersion = null
+ }, response.StatusCode);
+ case HttpStatusCode.NotImplemented:
+ // unprocessable entity
+ case (HttpStatusCode)422:
+ throw new MethodNotSupportedException();
+ case HttpStatusCode.InternalServerError:
+ // response json is html
+ throw new ServerErrorException(json);
+ case (HttpStatusCode)429:
+ // rate limited
+ response.Headers.TryGetValues("Retry-After", out var values);
+ throw new RateLimitException(values?.FirstOrDefault());
+ default:
+ throw new ApiConflictException(errorMessage, response.StatusCode);
+ }
+ }
+
///
/// Construct an
///
@@ -60,7 +133,7 @@ namespace Tgstation.Server.Client
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
Url = url ?? throw new ArgumentNullException(nameof(url));
headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
-
+
requestLoggers = new List();
}
@@ -70,10 +143,11 @@ namespace Tgstation.Server.Client
///
/// Main request method
///
+ /// The resulting POCO type
/// The route to run
/// The body of the request
/// The method of the request
- /// The optional for the request
+ /// The optional for the request
/// The for the operation
/// A resulting in the response on success
async Task RunRequest(string route, object body, HttpMethod method, long? instanceId, CancellationToken cancellationToken)
@@ -85,102 +159,42 @@ namespace Tgstation.Server.Client
if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
throw new InvalidOperationException("Body cannot be null for POST or PUT!");
+ HttpResponseMessage response;
var fullUri = new Uri(Url, route);
-
- var message = new HttpRequestMessage(method, fullUri);
-
- var serializerSettings = new JsonSerializerSettings
+ var serializerSettings = GetSerializerSettings();
+ using (var request = new HttpRequestMessage(method, fullUri))
{
- ContractResolver = new CamelCasePropertyNamesContractResolver(),
- Converters = new[] { new VersionConverter() }
- };
+ if (body != null)
+ request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
- if (body != null)
- message.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
+ headers.SetRequestHeaders(request.Headers, instanceId);
- headers.SetRequestHeaders(message.Headers, instanceId);
+ await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
- await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(message, cancellationToken))).ConfigureAwait(false);
+ response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ }
- var response = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false);
-
- await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
-
- var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
-
- if (!response.IsSuccessStatusCode)
+ using (response)
{
- ErrorMessage errorMessage = null;
+ await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
+
+ var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ if (!response.IsSuccessStatusCode)
+ HandleBadResponse(response, json);
+
+ if (String.IsNullOrWhiteSpace(json))
+ json = JsonConvert.SerializeObject(new object());
+
try
{
- //check if json serializes to an error message
- errorMessage = JsonConvert.DeserializeObject(json, serializerSettings);
+ return JsonConvert.DeserializeObject(json, serializerSettings);
}
- catch (JsonException) { }
-
- const string BadSpecExtension = " This is not part of TGS4 communication specification and should be reported if it was returned from a TGS4 server!";
-
- switch (response.StatusCode)
+ catch (JsonException)
{
- case HttpStatusCode.UpgradeRequired:
- throw new ApiMismatchException(errorMessage ?? new ErrorMessage
- {
- Message = "API Mismatch but no current API version provided!" + BadSpecExtension,
- SeverApiVersion = null
- });
- case HttpStatusCode.Unauthorized:
- throw new UnauthorizedException();
- case HttpStatusCode.RequestTimeout:
- throw new RequestTimeoutException();
- case HttpStatusCode.Forbidden:
- throw new InsufficientPermissionsException();
- case HttpStatusCode.ServiceUnavailable:
- throw new ServiceUnavailableException();
- case HttpStatusCode.Gone:
- errorMessage = errorMessage ?? new ErrorMessage
- {
- Message = "The requested resource could not be found!",
- SeverApiVersion = null
- };
- goto case HttpStatusCode.Conflict;
- case HttpStatusCode.NotFound: //our fault somehow
- errorMessage = errorMessage ?? new ErrorMessage
- {
- Message = "This is not a valid route!" + BadSpecExtension,
- SeverApiVersion = null
- };
- goto case HttpStatusCode.Conflict;
- case HttpStatusCode.Conflict:
- throw new ConflictException(errorMessage ?? new ErrorMessage
- {
- Message = "An undescribed conflict occurred!" + BadSpecExtension,
- SeverApiVersion = null
- }, response.StatusCode);
- case HttpStatusCode.NotImplemented:
- case (HttpStatusCode)422: //unprocessable entity
- throw new MethodNotSupportedException();
- case HttpStatusCode.InternalServerError:
- //response
- throw new ServerErrorException(json); //json is html
- case (HttpStatusCode)429: //rate limited
- response.Headers.TryGetValues("Retry-After", out var values);
- throw new RateLimitException(values?.FirstOrDefault());
- default:
- throw new ApiConflictException(errorMessage, response.StatusCode);
+ throw new UnrecognizedResponseException(json, response.StatusCode);
}
}
-
- if (String.IsNullOrWhiteSpace(json))
- json = JsonConvert.SerializeObject(new object());
-
- try
- {
- return JsonConvert.DeserializeObject(json, serializerSettings);
- }
- catch (JsonException)
- {
- throw new UnrecognizedResponseException(json, response.StatusCode);
- }
}
///
diff --git a/src/Tgstation.Server.Client/Components/ByondClient.cs b/src/Tgstation.Server.Client/Components/ByondClient.cs
index 92c2f8aba5..f3675dc265 100644
--- a/src/Tgstation.Server.Client/Components/ByondClient.cs
+++ b/src/Tgstation.Server.Client/Components/ByondClient.cs
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
/// The for the
///
readonly IApiClient apiClient;
+
///
/// The for the
///
diff --git a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs
index 5c303f5f77..f7890f1f22 100644
--- a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs
+++ b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
/// The for the
///
readonly IApiClient apiClient;
+
///
/// The for the
///
diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs
index 1f1af74469..54dc6acf39 100644
--- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs
+++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
/// The for the
///
readonly IApiClient apiClient;
+
///
/// The for the
///
diff --git a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs
index e077ea142d..6c2d5869d2 100644
--- a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs
+++ b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs
@@ -13,6 +13,7 @@ namespace Tgstation.Server.Client.Components
/// The for the
///
readonly IApiClient apiClient;
+
///
/// The for the
///
diff --git a/src/Tgstation.Server.Client/Components/InstanceUserClient.cs b/src/Tgstation.Server.Client/Components/InstanceUserClient.cs
index c3ceeca108..440340b6c4 100644
--- a/src/Tgstation.Server.Client/Components/InstanceUserClient.cs
+++ b/src/Tgstation.Server.Client/Components/InstanceUserClient.cs
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
/// The for the
///
readonly IApiClient apiClient;
+
///
/// The for the
///
@@ -33,6 +34,7 @@ namespace Tgstation.Server.Client.Components
///
public Task Create(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Create(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
+ ///
public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceUser, instanceUser.UserId.Value), instance.Id, cancellationToken);
///
diff --git a/src/Tgstation.Server.Client/Components/JobsClient.cs b/src/Tgstation.Server.Client/Components/JobsClient.cs
index 59cdbc22ba..d6c421e65d 100644
--- a/src/Tgstation.Server.Client/Components/JobsClient.cs
+++ b/src/Tgstation.Server.Client/Components/JobsClient.cs
@@ -14,6 +14,7 @@ namespace Tgstation.Server.Client.Components
/// The for the
///
readonly IApiClient apiClient;
+
///
/// The for the
///
diff --git a/src/Tgstation.Server.Client/Components/RepositoryClient.cs b/src/Tgstation.Server.Client/Components/RepositoryClient.cs
index 18e332fd09..97d4cdfec5 100644
--- a/src/Tgstation.Server.Client/Components/RepositoryClient.cs
+++ b/src/Tgstation.Server.Client/Components/RepositoryClient.cs
@@ -13,6 +13,7 @@ namespace Tgstation.Server.Client.Components
/// The for the
///
readonly IApiClient apiClient;
+
///
/// The for the
///
@@ -22,7 +23,7 @@ namespace Tgstation.Server.Client.Components
/// Construct a
///
/// The value of
- ///
+ /// The value of
public RepositoryClient(IApiClient apiClient, Instance instance)
{
this.apiClient = apiClient;
diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs
index 7eb8538156..ab833b1731 100644
--- a/src/Tgstation.Server.Client/IApiClient.cs
+++ b/src/Tgstation.Server.Client/IApiClient.cs
@@ -10,10 +10,19 @@ namespace Tgstation.Server.Client
///
interface IApiClient : IDisposable
{
+ ///
+ /// The the uses
+ ///
ApiHeaders Headers { get; set; }
+ ///
+ /// The pointing the tgstation-server
+ ///
Uri Url { get; }
+ ///
+ /// The request timeout
+ ///
TimeSpan Timeout { get; set; }
///
@@ -22,21 +31,155 @@ namespace Tgstation.Server.Client
/// The to add
void AddRequestLogger(IRequestLogger requestLogger);
+ ///
+ /// Run an HTTP PUT request
+ ///
+ /// The type to of the request body
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The request body
+ /// The for the operation
+ /// A resulting in the response body as a
Task Create(string route, TBody body, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP PUT request
+ ///
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Create(string route, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP GET request
+ ///
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Read(string route, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP POST request
+ ///
+ /// The type to of the request body
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The request body
+ /// The for the operation
+ /// A resulting in the response body as a
Task Update(string route, TBody body, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP POST request
+ ///
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Update(string route, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP POST request
+ ///
+ /// The type to of the request body
+ /// The server route to make the request to
+ /// The request body
+ /// The for the operation
+ /// A representing the running operation
Task Update(string route, TBody body, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP DELETE request
+ ///
+ /// The server route to make the request to
+ /// The for the operation
+ /// A representing the running operation
Task Delete(string route, CancellationToken cancellationToken);
+ ///
+ /// Run an HTTP PUT request
+ ///
+ /// The type to of the request body
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The request body
+ /// The to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Create(string route, TBody body, long instanceId, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP PUT request
+ ///
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Create(string route, long instanceId, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP PATCH request
+ ///
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Patch(string route, long instanceId, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP GET request
+ ///
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Read(string route, long instanceId, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP POST request
+ ///
+ /// The type to of the request body
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The request body
+ /// The to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Update(string route, TBody body, long instanceId, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP DELETE request
+ ///
+ /// The server route to make the request to
+ /// The to make the request to
+ /// The for the operation
+ /// A representing the running operation
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP DELETE request
+ ///
+ /// The type to of the request body
+ /// The server route to make the request to
+ /// The request body
+ /// The to make the request to
+ /// The for the operation
+ /// A representing the running operation
Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken);
+
+ ///
+ /// Run an HTTP DELETE request
+ ///
+ /// The type of the response body
+ /// The server route to make the request to
+ /// The to make the request to
+ /// The for the operation
+ /// A resulting in the response body as a
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs
index 9432ea9afd..685606df7d 100644
--- a/src/Tgstation.Server.Client/IServerClient.cs
+++ b/src/Tgstation.Server.Client/IServerClient.cs
@@ -43,6 +43,8 @@ namespace Tgstation.Server.Client
///
/// The of the
///
+ /// The for the operation
+ /// A resulting in the of the target server
Task Version(CancellationToken cancellationToken);
///
diff --git a/src/Tgstation.Server.Client/IServerClientFactory.cs b/src/Tgstation.Server.Client/IServerClientFactory.cs
index df0427efef..32816de376 100644
--- a/src/Tgstation.Server.Client/IServerClientFactory.cs
+++ b/src/Tgstation.Server.Client/IServerClientFactory.cs
@@ -16,8 +16,8 @@ namespace Tgstation.Server.Client
/// The URL to access TGS
/// The username to for the
/// The password for the
- /// The for the operation
/// The representing timeout for the connection
+ /// The for the operation
/// A resulting in a new
Task CreateServerClient(Uri host, string username, string password, TimeSpan timeout = default, CancellationToken cancellationToken = default);
diff --git a/src/Tgstation.Server.Client/InstanceManagerClient.cs b/src/Tgstation.Server.Client/InstanceManagerClient.cs
index 0aad027e0b..62d99de98d 100644
--- a/src/Tgstation.Server.Client/InstanceManagerClient.cs
+++ b/src/Tgstation.Server.Client/InstanceManagerClient.cs
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Client
///
/// Construct an
///
- ///
+ /// The value of
public InstanceManagerClient(IApiClient apiClient)
{
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
@@ -55,6 +55,7 @@ namespace Tgstation.Server.Client
client = new InstanceClient(apiClient, instance);
cachedClients.Add(instance.Id, client);
}
+
return client;
}
}
diff --git a/src/Tgstation.Server.Client/ServerClientFactory.cs b/src/Tgstation.Server.Client/ServerClientFactory.cs
index b0a7d3b547..62eec5644c 100644
--- a/src/Tgstation.Server.Client/ServerClientFactory.cs
+++ b/src/Tgstation.Server.Client/ServerClientFactory.cs
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Client
///
/// The for the
///
- static readonly IApiClientFactory apiClientFactory = new ApiClientFactory();
+ static readonly IApiClientFactory ApiClientFactory = new ApiClientFactory();
///
/// The for the
@@ -40,12 +40,13 @@ namespace Tgstation.Server.Client
throw new ArgumentNullException(nameof(password));
Token token;
- using (var api = apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
+ using (var api = ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
{
if (timeout != default)
api.Timeout = timeout;
token = await api.Update(Routes.Root, cancellationToken).ConfigureAwait(false);
}
+
return CreateServerClient(host, token, timeout);
}
@@ -56,7 +57,7 @@ namespace Tgstation.Server.Client
throw new ArgumentNullException(nameof(host));
if (token == null)
throw new ArgumentNullException(nameof(token));
- var result = new ServerClient(apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
+ var result = new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
if (timeout != default)
result.Timeout = timeout;
return result;