mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-30 08:33:19 +01:00
GraphQL API Initial Implementation
- Build API via annotations using HotChocolate. - Export to `artifacts` on build. - Repurpose REST API types where possible, but many have to be reimplemented. - Transfer controller and bridge controller will definitely have to stay forever. - Added `Tgstation.Server.Client.GraphQL` built from schema using StrawberryShake. - Added equivalent server information query. - Setup semver and u32 (de)serialization. - Begin transitioning to multi-client type live tests. - Add GraphQL route `/api/graphql` - Rename ServerClient likes to RestServerClient equivalent. - Major API/Client library version bumps.
This commit is contained in:
@@ -5,7 +5,7 @@ using System.Net.Http;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Exceptions thrown by <see cref="IServerClient"/>s.
|
||||
/// Exceptions thrown by <see cref="IRestServerClient"/>s.
|
||||
/// </summary>
|
||||
public abstract class ClientException : Exception
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -8,14 +7,13 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Response;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Web interface for the API.
|
||||
/// </summary>
|
||||
interface IApiClient : IAsyncDisposable
|
||||
interface IApiClient : ITransferClient, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ApiHeaders"/> the <see cref="IApiClient"/> uses.
|
||||
@@ -39,7 +37,7 @@ namespace Tgstation.Server.Client
|
||||
void AddRequestLogger(IRequestLogger requestLogger);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to all job updates available to the <see cref="IServerClient"/>.
|
||||
/// Subscribe to all job updates available to the <see cref="IRestServerClient"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="THubImplementation">The <see cref="Type"/> of the hub being implemented.</typeparam>
|
||||
/// <param name="hubImplementation">The <typeparamref name="THubImplementation"/> to use for proxying the methods of the hub connection.</param>
|
||||
@@ -239,22 +237,5 @@ namespace Tgstation.Server.Client
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
|
||||
ValueTask<TResult> Delete<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken)
|
||||
where TBody : class;
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a file <see cref="Stream"/> for a given <paramref name="ticket"/>.
|
||||
/// </summary>
|
||||
/// <param name="ticket">The <see cref="FileTicketResponse"/> to download.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the downloaded <see cref="Stream"/>.</returns>
|
||||
ValueTask<Stream> Download(FileTicketResponse ticket, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a given <paramref name="uploadStream"/> for a given <paramref name="ticket"/>.
|
||||
/// </summary>
|
||||
/// <param name="ticket">The <see cref="FileTicketResponse"/> to download.</param>
|
||||
/// <param name="uploadStream">The <see cref="Stream"/> to upload. <see langword="null"/> represents an empty file.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
|
||||
ValueTask Upload(FileTicketResponse ticket, Stream? uploadStream, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Main client for communicating with a server.
|
||||
/// </summary>
|
||||
public interface IServerClient : IAsyncDisposable
|
||||
public interface IRestServerClient : IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The connected server's root <see cref="Uri"/>.
|
||||
@@ -51,14 +51,20 @@ namespace Tgstation.Server.Client
|
||||
IUserGroupsClient Groups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ServerInformationResponse"/> of the <see cref="IServerClient"/>.
|
||||
/// Access the <see cref="ITransferClient"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Most client methods handle transfers in their invocations. There is rarely any reason to use the <see cref="ITransferClient"/> directly.</remarks>
|
||||
ITransferClient Transfer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ServerInformationResponse"/> of the <see cref="IRestServerClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ServerInformationResponse"/> of the target server.</returns>
|
||||
ValueTask<ServerInformationResponse> ServerInformation(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to all job updates available to the <see cref="IServerClient"/>.
|
||||
/// Subscribe to all job updates available to the <see cref="IRestServerClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="jobsReceiver">The <see cref="IJobsHub"/> to use to subscribe to updates.</param>
|
||||
/// <param name="retryPolicy">The optional <see cref="IRetryPolicy"/> to use for the backing connection. The default retry policy waits for 1, 2, 4, 8, and 16 seconds, then 30s repeatedly.</param>
|
||||
+15
-15
@@ -9,9 +9,9 @@ using Tgstation.Server.Api.Models.Response;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for creating <see cref="IServerClient"/>s.
|
||||
/// Factory for creating <see cref="IRestServerClient"/>s.
|
||||
/// </summary>
|
||||
public interface IServerClientFactory
|
||||
public interface IRestServerClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ServerInformationResponse"/> for a given <paramref name="host"/>.
|
||||
@@ -28,17 +28,17 @@ namespace Tgstation.Server.Client
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServerClient"/> using a password login.
|
||||
/// Create a <see cref="IRestServerClient"/> using a password login.
|
||||
/// </summary>
|
||||
/// <param name="host">The URL to access TGS.</param>
|
||||
/// <param name="username">The username to for the <see cref="IServerClient"/>.</param>
|
||||
/// <param name="password">The password for the <see cref="IServerClient"/>.</param>
|
||||
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IServerClient"/>.</param>
|
||||
/// <param name="username">The username to for the <see cref="IRestServerClient"/>.</param>
|
||||
/// <param name="password">The password for the <see cref="IRestServerClient"/>.</param>
|
||||
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IRestServerClient"/>.</param>
|
||||
/// <param name="timeout">Optional <see cref="TimeSpan"/> representing timeout for the connection.</param>
|
||||
/// <param name="attemptLoginRefresh">Attempt to refresh the received <see cref="TokenResponse"/> when it expires or becomes invalid. <paramref name="username"/> and <paramref name="password"/> will be stored in memory if this is <see langword="true"/>.</param>
|
||||
/// <param name="cancellationToken">Optional <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IServerClient"/>.</returns>
|
||||
ValueTask<IServerClient> CreateFromLogin(
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IRestServerClient"/>.</returns>
|
||||
ValueTask<IRestServerClient> CreateFromLogin(
|
||||
Uri host,
|
||||
string username,
|
||||
string password,
|
||||
@@ -48,16 +48,16 @@ namespace Tgstation.Server.Client
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServerClient"/> using am OAuth login.
|
||||
/// Create a <see cref="IRestServerClient"/> using an OAuth login.
|
||||
/// </summary>
|
||||
/// <param name="host">The URL to access TGS.</param>
|
||||
/// <param name="oAuthCode">The OAuth code used to complete the flow.</param>
|
||||
/// <param name="oAuthProvider">The <see cref="OAuthProvider"/>.</param>
|
||||
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IServerClient"/>.</param>
|
||||
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IRestServerClient"/>.</param>
|
||||
/// <param name="timeout">Optional <see cref="TimeSpan"/> representing timeout for the connection.</param>
|
||||
/// <param name="cancellationToken">Optional <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IServerClient"/>.</returns>
|
||||
ValueTask<IServerClient> CreateFromOAuth(
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IRestServerClient"/>.</returns>
|
||||
ValueTask<IRestServerClient> CreateFromOAuth(
|
||||
Uri host,
|
||||
string oAuthCode,
|
||||
OAuthProvider oAuthProvider,
|
||||
@@ -66,12 +66,12 @@ namespace Tgstation.Server.Client
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServerClient"/>.
|
||||
/// Create a <see cref="IRestServerClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="host">The URL to access TGS.</param>
|
||||
/// <param name="token">The <see cref="TokenResponse"/> to access the API with.</param>
|
||||
/// <returns>A new <see cref="IServerClient"/>.</returns>
|
||||
IServerClient CreateFromToken(
|
||||
/// <returns>A new <see cref="IRestServerClient"/>.</returns>
|
||||
IRestServerClient CreateFromToken(
|
||||
Uri host,
|
||||
TokenResponse token);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Api.Models.Response;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// For transferring data <see cref="Stream"/>s.
|
||||
/// </summary>
|
||||
public interface ITransferClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Downloads a file <see cref="Stream"/> for a given <paramref name="ticket"/>.
|
||||
/// </summary>
|
||||
/// <param name="ticket">The <see cref="FileTicketResponse"/> to download.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the downloaded <see cref="Stream"/>.</returns>
|
||||
ValueTask<Stream> Download(FileTicketResponse ticket, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a given <paramref name="uploadStream"/> for a given <paramref name="ticket"/>.
|
||||
/// </summary>
|
||||
/// <param name="ticket">The <see cref="FileTicketResponse"/> to download.</param>
|
||||
/// <param name="uploadStream">The <see cref="Stream"/> to upload. <see langword="null"/> represents an empty file.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
|
||||
ValueTask Upload(FileTicketResponse ticket, Stream? uploadStream, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -12,7 +12,7 @@ using Tgstation.Server.Api.Models.Response;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ServerClient : IServerClient
|
||||
sealed class RestServerClient : IRestServerClient
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Uri Url => apiClient.Url;
|
||||
@@ -43,16 +43,19 @@ namespace Tgstation.Server.Client
|
||||
/// <inheritdoc />
|
||||
public IUserGroupsClient Groups { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITransferClient Transfer => apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ServerClient"/>.
|
||||
/// The <see cref="IApiClient"/> for the <see cref="RestServerClient"/>.
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerClient"/> class.
|
||||
/// Initializes a new instance of the <see cref="RestServerClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/>.</param>
|
||||
public ServerClient(IApiClient apiClient)
|
||||
public RestServerClient(IApiClient apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
|
||||
+16
-16
@@ -12,37 +12,37 @@ using Tgstation.Server.Api.Models.Response;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ServerClientFactory : IServerClientFactory
|
||||
public sealed class RestServerClientFactory : IRestServerClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClientFactory"/> for the <see cref="ServerClientFactory"/>.
|
||||
/// The <see cref="IApiClientFactory"/> for the <see cref="RestServerClientFactory"/>.
|
||||
/// </summary>
|
||||
internal static IApiClientFactory ApiClientFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ProductHeaderValue"/> for the <see cref="ServerClientFactory"/>.
|
||||
/// The <see cref="ProductHeaderValue"/> for the <see cref="RestServerClientFactory"/>.
|
||||
/// </summary>
|
||||
readonly ProductHeaderValue productHeaderValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="ServerClientFactory"/> class.
|
||||
/// Initializes static members of the <see cref="RestServerClientFactory"/> class.
|
||||
/// </summary>
|
||||
static ServerClientFactory()
|
||||
static RestServerClientFactory()
|
||||
{
|
||||
ApiClientFactory = new ApiClientFactory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerClientFactory"/> class.
|
||||
/// Initializes a new instance of the <see cref="RestServerClientFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="productHeaderValue">The value of <see cref="productHeaderValue"/>.</param>
|
||||
public ServerClientFactory(ProductHeaderValue productHeaderValue)
|
||||
public RestServerClientFactory(ProductHeaderValue productHeaderValue)
|
||||
{
|
||||
this.productHeaderValue = productHeaderValue ?? throw new ArgumentNullException(nameof(productHeaderValue));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IServerClient> CreateFromLogin(
|
||||
public ValueTask<IRestServerClient> CreateFromLogin(
|
||||
Uri host,
|
||||
string username,
|
||||
string password,
|
||||
@@ -69,7 +69,7 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IServerClient> CreateFromOAuth(
|
||||
public ValueTask<IRestServerClient> CreateFromOAuth(
|
||||
Uri host,
|
||||
string oAuthCode,
|
||||
OAuthProvider oAuthProvider,
|
||||
@@ -93,7 +93,7 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IServerClient CreateFromToken(Uri host, TokenResponse token)
|
||||
public IRestServerClient CreateFromToken(Uri host, TokenResponse token)
|
||||
{
|
||||
if (host == null)
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
@@ -102,7 +102,7 @@ namespace Tgstation.Server.Client
|
||||
if (token.Bearer == null)
|
||||
throw new InvalidOperationException("token.Bearer should not be null!");
|
||||
|
||||
var serverClient = new ServerClient(
|
||||
var serverClient = new RestServerClient(
|
||||
ApiClientFactory.CreateApiClient(
|
||||
host,
|
||||
new ApiHeaders(
|
||||
@@ -142,16 +142,16 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IServerClient"/> from a login operation.
|
||||
/// Creates a <see cref="IRestServerClient"/> from a login operation.
|
||||
/// </summary>
|
||||
/// <param name="host">The URL to access TGS.</param>
|
||||
/// <param name="loginHeaders">The <see cref="ApiHeaders"/> to use for the login operation.</param>
|
||||
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IServerClient"/>.</param>
|
||||
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IRestServerClient"/>.</param>
|
||||
/// <param name="timeout">Optional <see cref="TimeSpan"/> representing timeout for the connection.</param>
|
||||
/// <param name="attemptLoginRefresh">If <paramref name="loginHeaders"/> may be used to re-login in the future.</param>
|
||||
/// <param name="cancellationToken">Optional <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IServerClient"/>.</returns>
|
||||
async ValueTask<IServerClient> CreateWithNewToken(
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IRestServerClient"/>.</returns>
|
||||
async ValueTask<IRestServerClient> CreateWithNewToken(
|
||||
Uri host,
|
||||
ApiHeaders loginHeaders,
|
||||
IEnumerable<IRequestLogger>? requestLoggers,
|
||||
@@ -173,7 +173,7 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
var apiHeaders = new ApiHeaders(productHeaderValue, token);
|
||||
var client = new ServerClient(
|
||||
var client = new RestServerClient(
|
||||
ApiClientFactory.CreateApiClient(
|
||||
host,
|
||||
apiHeaders,
|
||||
Reference in New Issue
Block a user