diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index 981d207959..4c4807d337 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -233,10 +233,18 @@ namespace Tgstation.Server.Host.Controllers
/// The model containing the to update to.
/// The for the operation.
/// A resulting in the for the operation.
+ /// Update has been started successfully.
+ /// The requested version could not be found.
/// Upgrade operations are unavailable due to the launch configuration of TGS.
+ /// A GitHub rate limit was encountered.
+ /// A GitHub API error occurred.
[HttpPost]
[TgsAuthorize(AdministrationRights.ChangeVersion)]
+ [ProducesResponseType(202)]
+ [ProducesResponseType(410)]
[ProducesResponseType(typeof(ErrorMessage), 422)]
+ [ProducesResponseType(424)]
+ [ProducesResponseType(typeof(ErrorMessage), 429)]
public async Task Update([FromBody] Administration model, CancellationToken cancellationToken)
{
if (model == null)
@@ -258,12 +266,12 @@ namespace Tgstation.Server.Host.Controllers
}
///
- /// Attempts to restart the server
+ /// Attempts to restart the server.
///
/// A resulting in the of the request
/// Restart begun successfully.
/// Restart operations are unavailable due to the launch configuration of TGS.
- [HttpDelete("{id}")]
+ [HttpDelete]
[TgsAuthorize(AdministrationRights.RestartHost)]
[ProducesResponseType(200)]
[ProducesResponseType(typeof(ErrorMessage), 422)]
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index a160198a49..ec2573ed4c 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -151,7 +151,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the operation.
/// A resulting in the of the operation.
/// Watchdog terminated.
- [HttpDelete("{id}")]
+ [HttpDelete]
[TgsAuthorize(DreamDaemonRights.Shutdown)]
[ProducesResponseType(200)]
public async Task Delete(CancellationToken cancellationToken)
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index aba60c5dbd..765388fb4c 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -232,7 +232,7 @@ namespace Tgstation.Server.Host.Controllers
/// A resulting in the of the operation
/// Job to delete the repository created successfully.
/// Instance no longer available.
- [HttpDelete("{id}")]
+ [HttpDelete]
[TgsAuthorize(RepositoryRights.Delete)]
[ProducesResponseType(typeof(Repository), 202)]
[ProducesResponseType(410)]
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index ad03584087..40c58d0ff6 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -12,8 +12,6 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
-using Microsoft.Net.Http.Headers;
-using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Serilog;
@@ -261,43 +259,12 @@ namespace Tgstation.Server.Host.Core
});
if (hostingEnvironment.IsDevelopment())
- services.AddSwaggerGen(
- c =>
- {
- c.SwaggerDoc(
- "v1",
- new OpenApiInfo
- {
- Title = "TGS API",
- Version = "v4"
- });
-
- // Important to do this before applying our own filters
- // Otherwise we'll get NullReferenceExceptions on parameters to be setup in our document filter
- var assemblyLocation = assemblyInformationProvider.Path;
- var filePath = ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml"));
- c.IncludeXmlComments(filePath);
-
- c.OperationFilter();
- c.DocumentFilter();
-
- c.AddSecurityDefinition(SwaggerConfiguration.PasswordSecuritySchemeId, new OpenApiSecurityScheme
- {
- In = ParameterLocation.Header,
- Type = SecuritySchemeType.Http,
- Name = HeaderNames.Authorization,
- Scheme = ApiHeaders.BasicAuthenticationScheme
- });
-
- c.AddSecurityDefinition(SwaggerConfiguration.TokenSecuritySchemeId, new OpenApiSecurityScheme
- {
- BearerFormat = "JWT",
- In = ParameterLocation.Header,
- Type = SecuritySchemeType.Http,
- Name = HeaderNames.Authorization,
- Scheme = ApiHeaders.JwtAuthenticationScheme
- });
- });
+ {
+ string GetDocumentationFilePath(string assemblyLocation) => ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml"));
+ var assemblyDocumentationPath = GetDocumentationFilePath(assemblyInformationProvider.Path);
+ var apiDocumentationPath = GetDocumentationFilePath(typeof(ApiHeaders).Assembly.Location);
+ services.AddSwaggerGen(genOptions => SwaggerConfiguration.Configure(genOptions, assemblyDocumentationPath, apiDocumentationPath));
+ }
// enable browser detection
services.AddDetectionCore().AddBrowser();
diff --git a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs
new file mode 100644
index 0000000000..2955147706
--- /dev/null
+++ b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs
@@ -0,0 +1,57 @@
+using Microsoft.OpenApi;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Writers;
+using System;
+
+namespace Tgstation.Server.Host.Core
+{
+ ///
+ /// Implements the "x-enum-varnames" OpenAPI 3.0 extension.
+ ///
+ sealed class OpenApiEnumVarNamesExtension : IOpenApiExtension
+ {
+ ///
+ /// The of the being described.
+ ///
+ readonly Type enumType;
+
+ ///
+ /// Initializes a new instance of the .
+ ///
+ /// The value of ,
+ private OpenApiEnumVarNamesExtension(Type enumType)
+ {
+ this.enumType = enumType ?? throw new ArgumentNullException(nameof(enumType));
+ }
+
+ ///
+ /// Applies the extension to a give .
+ ///
+ /// The to apply to.
+ /// The of the being described.
+ public static void Apply(OpenApiSchema openApiSchema, Type enumType)
+ {
+ if (openApiSchema == null)
+ throw new ArgumentNullException(nameof(openApiSchema));
+
+ openApiSchema.Extensions.Add("x-enum-varnames", new OpenApiEnumVarNamesExtension(enumType));
+ }
+
+ ///
+ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
+ {
+ if (writer == null)
+ throw new ArgumentNullException(nameof(writer));
+
+ if (specVersion != OpenApiSpecVersion.OpenApi3_0)
+ throw new InvalidOperationException("This extension only applies to OpenAPI 3.0!");
+
+ writer.WriteStartArray();
+ foreach (var enumValue in Enum.GetValues(enumType))
+ writer.WriteValue(enumValue.ToString());
+
+ writer.WriteEndArray();
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
index 650e56903a..de3e3d9f02 100644
--- a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
+++ b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
@@ -1,4 +1,5 @@
-using Microsoft.Net.Http.Headers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
@@ -21,87 +22,12 @@ namespace Tgstation.Server.Host.Core
///
/// The name for password authentication.
///
- public const string PasswordSecuritySchemeId = "Password_Login_Scheme";
+ const string PasswordSecuritySchemeId = "Password_Login_Scheme";
///
/// The name for token authentication.
///
- public const string TokenSecuritySchemeId = "Token_Authorization_Scheme";
-
- ///
- public void Apply(OpenApiOperation operation, OperationFilterContext context)
- {
- if (operation == null)
- throw new ArgumentNullException(nameof(operation));
- if (context == null)
- throw new ArgumentNullException(nameof(context));
-
- var authAttributes = context
- .MethodInfo
- .DeclaringType
- .GetCustomAttributes(true)
- .Union(
- context
- .MethodInfo
- .GetCustomAttributes(true))
- .OfType();
-
- if (authAttributes.Any())
- {
- var tokenScheme = new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = TokenSecuritySchemeId
- }
- };
-
- operation.Security = new List
- {
- new OpenApiSecurityRequirement
- {
- {
- tokenScheme,
- new List()
- }
- }
- };
-
- if (authAttributes.Any(attr => attr.RightsType.HasValue && RightsHelper.IsInstanceRight(attr.RightsType.Value)))
- operation.Parameters.Add(new OpenApiParameter
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.Parameter,
- Id = ApiHeaders.InstanceIdHeader
- }
- });
- }
- else
- {
- // HomeController.CreateToken
- var passwordScheme = new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = PasswordSecuritySchemeId
- }
- };
-
- operation.Security = new List
- {
- new OpenApiSecurityRequirement
- {
- {
- passwordScheme,
- new List()
- }
- }
- };
- }
- }
+ const string TokenSecuritySchemeId = "Token_Authorization_Scheme";
static void AddDefaultResponses(OpenApiDocument document)
{
@@ -176,6 +102,126 @@ namespace Tgstation.Server.Host.Core
});
}
+ ///
+ /// Configure the swagger settings.
+ ///
+ /// The to use.
+ /// The path to the XML documentation file for the assembly.
+ /// The path to the XML documentation file for the assembly.
+ public static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
+ {
+ swaggerGenOptions.SwaggerDoc(
+ "v1",
+ new OpenApiInfo
+ {
+ Title = "TGS API",
+ Version = "v4"
+ });
+
+ // Important to do this before applying our own filters
+ // Otherwise we'll get NullReferenceExceptions on parameters to be setup in our document filter
+ swaggerGenOptions.IncludeXmlComments(assemblyDocumentationPath);
+ swaggerGenOptions.IncludeXmlComments(apiDocumentationPath);
+
+ swaggerGenOptions.OperationFilter();
+ swaggerGenOptions.DocumentFilter();
+ swaggerGenOptions.SchemaFilter();
+
+ swaggerGenOptions.AddSecurityDefinition(PasswordSecuritySchemeId, new OpenApiSecurityScheme
+ {
+ In = ParameterLocation.Header,
+ Type = SecuritySchemeType.Http,
+ Name = HeaderNames.Authorization,
+ Scheme = ApiHeaders.BasicAuthenticationScheme
+ });
+
+ swaggerGenOptions.AddSecurityDefinition(TokenSecuritySchemeId, new OpenApiSecurityScheme
+ {
+ BearerFormat = "JWT",
+ In = ParameterLocation.Header,
+ Type = SecuritySchemeType.Http,
+ Name = HeaderNames.Authorization,
+ Scheme = ApiHeaders.JwtAuthenticationScheme
+ });
+ }
+
+ ///
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ if (operation == null)
+ throw new ArgumentNullException(nameof(operation));
+ if (context == null)
+ throw new ArgumentNullException(nameof(context));
+
+ operation.OperationId = $"{context.MethodInfo.DeclaringType.Name}.{context.MethodInfo.Name}";
+
+ var authAttributes = context
+ .MethodInfo
+ .DeclaringType
+ .GetCustomAttributes(true)
+ .Union(
+ context
+ .MethodInfo
+ .GetCustomAttributes(true))
+ .OfType();
+
+ if (authAttributes.Any())
+ {
+ var tokenScheme = new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = TokenSecuritySchemeId
+ }
+ };
+
+ operation.Security = new List
+ {
+ new OpenApiSecurityRequirement
+ {
+ {
+ tokenScheme,
+ new List()
+ }
+ }
+ };
+
+ if (authAttributes.Any(attr => attr.RightsType.HasValue && RightsHelper.IsInstanceRight(attr.RightsType.Value)))
+ operation.Parameters.Add(new OpenApiParameter
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.Parameter,
+ Id = ApiHeaders.InstanceIdHeader
+ }
+ });
+ }
+ else
+ {
+ // HomeController.CreateToken
+ var passwordScheme = new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = PasswordSecuritySchemeId
+ }
+ };
+
+ operation.Security = new List
+ {
+ new OpenApiSecurityRequirement
+ {
+ {
+ passwordScheme,
+ new List()
+ }
+ }
+ };
+ }
+ }
+
///
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
@@ -190,9 +236,19 @@ namespace Tgstation.Server.Host.Core
Name = ApiHeaders.InstanceIdHeader,
Description = "The instance ID being accessed",
Required = true,
- Style = ParameterStyle.Simple
+ Style = ParameterStyle.Simple,
+ Schema = new OpenApiSchema
+ {
+ Type = "integer"
+ }
});
+ var productHeaderSchema = new OpenApiSchema
+ {
+ Type = "string",
+ Format = "productheader"
+ };
+
swaggerDoc.Components.Parameters.Add(ApiHeaders.ApiVersionHeader, new OpenApiParameter
{
In = ParameterLocation.Header,
@@ -200,7 +256,8 @@ namespace Tgstation.Server.Host.Core
Description = "The API version being used in the form \"Tgstation.Server.Api/[API version]\"",
Required = true,
Style = ParameterStyle.Simple,
- Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}")
+ Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}"),
+ Schema = productHeaderSchema
});
swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent, new OpenApiParameter
@@ -210,7 +267,8 @@ namespace Tgstation.Server.Host.Core
Description = "The user agent of the calling client.",
Required = true,
Style = ParameterStyle.Simple,
- Example = new OpenApiString("Your-user-agent/1.0.0.0")
+ Example = new OpenApiString("Your-user-agent/1.0.0.0"),
+ Schema = productHeaderSchema
});
foreach (var operation in swaggerDoc
@@ -247,6 +305,16 @@ namespace Tgstation.Server.Host.Core
throw new ArgumentNullException(nameof(schema));
if (context == null)
throw new ArgumentNullException(nameof(context));
+
+ if (!schema.Enum?.Any() ?? false)
+ return;
+
+ // Could be nullable type, make sure to get the right one
+ Type enumType = context.Type.IsConstructedGenericType
+ ? context.Type.GenericTypeArguments.First()
+ : context.Type;
+
+ OpenApiEnumVarNamesExtension.Apply(schema, enumType);
}
}
}