2using System.Collections;
3using System.Collections.Generic;
7using System.Reflection;
9using Microsoft.Extensions.DependencyInjection;
10using Microsoft.Net.Http.Headers;
11using Microsoft.OpenApi.Any;
12using Microsoft.OpenApi.Models;
14using Swashbuckle.AspNetCore.SwaggerGen;
61 public static void Configure(SwaggerGenOptions swaggerGenOptions,
string assemblyDocumentationPath,
string apiDocumentationPath)
63 swaggerGenOptions.SwaggerDoc(
69 License =
new OpenApiLicense
72 Url =
new Uri(
"https://github.com/tgstation/tgstation-server/blob/dev/LICENSE"),
74 Contact =
new OpenApiContact
76 Name =
"/tg/station 13",
77 Url =
new Uri(
"https://github.com/tgstation"),
79 Description =
"A production scale tool for DreamMaker server management",
84 swaggerGenOptions.IncludeXmlComments(assemblyDocumentationPath);
85 swaggerGenOptions.IncludeXmlComments(apiDocumentationPath);
88 swaggerGenOptions.UseAllOfToExtendReferenceSchemas();
99 In = ParameterLocation.Header,
100 Type = SecuritySchemeType.Http,
101 Name = HeaderNames.Authorization,
107 In = ParameterLocation.Header,
108 Type = SecuritySchemeType.Http,
109 Name = HeaderNames.Authorization,
115 BearerFormat =
"JWT",
116 In = ParameterLocation.Header,
117 Type = SecuritySchemeType.Http,
118 Name = HeaderNames.Authorization,
129 var errorMessageContent =
new Dictionary<string, OpenApiMediaType>
132 MediaTypeNames.Application.Json,
135 Schema =
new OpenApiSchema
137 Reference =
new OpenApiReference
140 Type = ReferenceType.Schema,
147 void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete)
149 string responseKey = $
"{(int)code}";
151 document.Components.Responses.Add(responseKey, concrete);
153 var referenceResponse =
new OpenApiResponse
155 Reference =
new OpenApiReference
157 Type = ReferenceType.Response,
162 foreach (var operation
in document.Paths.SelectMany(path => path.Value.Operations))
163 operation.Value.Responses.TryAdd(responseKey, referenceResponse);
166 AddDefaultResponse(HttpStatusCode.BadRequest,
new OpenApiResponse
168 Description =
"A badly formatted request was made. See error message for details.",
169 Content = errorMessageContent,
172 AddDefaultResponse(HttpStatusCode.Unauthorized,
new OpenApiResponse
174 Description =
"Invalid Authentication header.",
177 AddDefaultResponse(HttpStatusCode.Forbidden,
new OpenApiResponse
179 Description =
"User lacks sufficient permissions for the operation.",
182 AddDefaultResponse(HttpStatusCode.Conflict,
new OpenApiResponse
184 Description =
"A data integrity check failed while performing the operation. See error message for details.",
185 Content = errorMessageContent,
188 AddDefaultResponse(HttpStatusCode.NotAcceptable,
new OpenApiResponse
190 Description = $
"Invalid Accept header, TGS requires `{HeaderNames.Accept}: {MediaTypeNames.Application.Json}`.",
191 Content = errorMessageContent,
194 AddDefaultResponse(HttpStatusCode.InternalServerError,
new OpenApiResponse
196 Description = ErrorCode.InternalServerError.Describe(),
197 Content = errorMessageContent,
200 AddDefaultResponse(HttpStatusCode.ServiceUnavailable,
new OpenApiResponse
202 Description =
"The server may be starting up or shutting down.",
205 AddDefaultResponse(HttpStatusCode.NotImplemented,
new OpenApiResponse
207 Description = ErrorCode.RequiresPosixSystemIdentity.Describe(),
208 Content = errorMessageContent,
220 rootSchema.Nullable =
false;
222 var rootRequestSchema = rootSchemaId.EndsWith(
"Request", StringComparison.Ordinal);
223 var rootResponseSchema = rootSchemaId.EndsWith(
"Response", StringComparison.Ordinal);
224 var isPutRequest = rootSchemaId.EndsWith(
"CreateRequest", StringComparison.Ordinal);
226 Tuple<PropertyInfo, string, OpenApiSchema, IDictionary<string, OpenApiSchema>> GetTypeFromKvp(Type currentType, KeyValuePair<string, OpenApiSchema> kvp, IDictionary<string, OpenApiSchema> schemaDictionary)
228 var propertyInfo = currentType
230 .Single(x => x.Name.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase));
239 var subSchemaStack =
new Stack<Tuple<PropertyInfo, string, OpenApiSchema, IDictionary<string, OpenApiSchema>>>(
243 x => GetTypeFromKvp(context.Type, x, rootSchema.Properties))
244 .Where(x => x.Item3.Reference ==
null));
246 while (subSchemaStack.Count > 0)
248 var tuple = subSchemaStack.Pop();
249 var subSchema = tuple.Item3;
251 var subSchemaPropertyInfo = tuple.Item1;
253 if (subSchema.Properties !=
null
254 && !subSchemaPropertyInfo
257 .Any(x => x == typeof(IEnumerable)))
258 foreach (var kvp in subSchema.Properties.Where(x => x.Value.Reference ==
null))
259 subSchemaStack.Push(GetTypeFromKvp(subSchemaPropertyInfo.PropertyType, kvp, subSchema.Properties));
261 var attributes = subSchemaPropertyInfo
262 .GetCustomAttributes();
263 var responsePresence = attributes
268 var requestOptions = attributes
270 .OrderBy(x => x.PutOnly)
273 if (requestOptions.Count == 0 && requestOptions.All(x => x.Presence ==
FieldPresence.Ignored && !x.PutOnly))
274 subSchema.ReadOnly =
true;
276 var subSchemaId = tuple.Item2;
277 var subSchemaOwningDictionary = tuple.Item4;
278 if (rootResponseSchema)
280 subSchema.Nullable = responsePresence ==
FieldPresence.Optional;
282 subSchemaOwningDictionary.Remove(subSchemaId);
284 else if (rootRequestSchema)
286 subSchema.Nullable =
true;
287 var lastOptionWasIgnored =
false;
288 foreach (var requestOption
in requestOptions)
290 var validForThisRequest = !requestOption.PutOnly || isPutRequest;
291 if (!validForThisRequest)
294 lastOptionWasIgnored =
false;
295 switch (requestOption.Presence)
298 lastOptionWasIgnored =
true;
301 subSchema.Nullable =
true;
304 subSchema.Nullable =
false;
307 throw new InvalidOperationException($
"Invalid FieldPresence: {requestOption.Presence}!");
311 if (lastOptionWasIgnored)
312 subSchemaOwningDictionary.Remove(subSchemaId);
315 && requestOptions.All(x => x.Presence ==
FieldPresence.Required && !x.PutOnly))
316 subSchema.Nullable = subSchemaId.Equals(
318 StringComparison.OrdinalIgnoreCase)
334 return "ShallowUserResponse";
337 return $
"Paginated{type.GenericTypeArguments.First().Name}";
343 public void Apply(OpenApiOperation operation, OperationFilterContext context)
345 ArgumentNullException.ThrowIfNull(operation);
346 ArgumentNullException.ThrowIfNull(context);
348 operation.OperationId = $
"{context.MethodInfo.DeclaringType!.Name}.{context.MethodInfo.Name}";
350 var authAttributes = context
353 .GetCustomAttributes(
true)
357 .GetCustomAttributes(
true))
360 if (authAttributes.Any())
362 var tokenScheme =
new OpenApiSecurityScheme
364 Reference =
new OpenApiReference
366 Type = ReferenceType.SecurityScheme,
371 operation.Security =
new List<OpenApiSecurityRequirement>
383 operation.Parameters.Insert(0,
new OpenApiParameter
385 Reference =
new OpenApiReference
387 Type = ReferenceType.Parameter,
391 else if (typeof(
TransferController).IsAssignableFrom(context.MethodInfo.DeclaringType))
393 operation.RequestBody =
new OpenApiRequestBody
395 Content =
new Dictionary<string, OpenApiMediaType>
398 MediaTypeNames.Application.Octet,
401 Schema =
new OpenApiSchema
412 var twoHundredResponseContents = operation.Responses[
"200"].Content;
413 var fileContent = twoHundredResponseContents[MediaTypeNames.Application.Json];
414 twoHundredResponseContents.Remove(MediaTypeNames.Application.Json);
415 twoHundredResponseContents.Add(MediaTypeNames.Application.Octet, fileContent);
420 var passwordScheme =
new OpenApiSecurityScheme
422 Reference =
new OpenApiReference
424 Type = ReferenceType.SecurityScheme,
429 var oAuthScheme =
new OpenApiSecurityScheme
431 Reference =
new OpenApiReference
433 Type = ReferenceType.SecurityScheme,
438 operation.Parameters.Add(
new OpenApiParameter
440 In = ParameterLocation.Header,
442 Description =
"The external OAuth service provider.",
443 Style = ParameterStyle.Simple,
444 Example =
new OpenApiString(
"Discord"),
445 Schema =
new OpenApiSchema
451 operation.Security =
new List<OpenApiSecurityRequirement>
469 public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
471 ArgumentNullException.ThrowIfNull(swaggerDoc);
472 ArgumentNullException.ThrowIfNull(context);
474 swaggerDoc.ExternalDocs =
new OpenApiExternalDocs
476 Description =
"API Usage Documentation",
477 Url =
new Uri(
"https://tgstation.github.io/tgstation-server/api.html"),
482 In = ParameterLocation.Header,
483 Name = ApiHeaders.InstanceIdHeader,
484 Description =
"The instance ID being accessed",
486 Style = ParameterStyle.Simple,
487 Schema = new OpenApiSchema
493 var productHeaderSchema =
new OpenApiSchema
496 Format =
"productheader",
501 In = ParameterLocation.Header,
502 Name = ApiHeaders.ApiVersionHeader,
503 Description =
"The API version being used in the form \"Tgstation.Server.Api/[API version]\"",
505 Style = ParameterStyle.Simple,
506 Example = new OpenApiString($
"Tgstation.Server.Api/{ApiHeaders.Version}"),
507 Schema = productHeaderSchema,
510 swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent,
new OpenApiParameter
512 In = ParameterLocation.Header,
513 Name = HeaderNames.UserAgent,
514 Description =
"The user agent of the calling client.",
516 Style = ParameterStyle.Simple,
517 Example = new OpenApiString(
"Your-user-agent/1.0.0.0"),
518 Schema = productHeaderSchema,
521 var allSchemas = context
524 foreach (var path
in swaggerDoc.Paths)
525 foreach (var operation
in path.Value.Operations.Select(x => x.Value))
527 operation.Parameters.Insert(0,
new OpenApiParameter
529 Reference =
new OpenApiReference
531 Type = ReferenceType.Parameter,
536 operation.Parameters.Insert(1,
new OpenApiParameter
538 Reference =
new OpenApiReference
540 Type = ReferenceType.Parameter,
541 Id = HeaderNames.UserAgent,
550 public void Apply(OpenApiSchema schema, SchemaFilterContext context)
552 ArgumentNullException.ThrowIfNull(schema);
553 ArgumentNullException.ThrowIfNull(context);
556 schema.Required.Clear();
558 if (context.MemberInfo ==
null)
559 ApplyAttributesForRootSchema(schema, context);
561 if (!schema.Enum?.Any() ??
false)
565 Type firstGenericArgumentOrType = context.Type.IsConstructedGenericType
566 ? context.Type.GenericTypeArguments.First()
573 public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
575 ArgumentNullException.ThrowIfNull(requestBody);
576 ArgumentNullException.ThrowIfNull(context);
578 requestBody.Required =
true;
Indicates the FieldPresence for fields in models.
Represents an error message returned by the server.
Represents a paginated set of models.
Indicates the response FieldPresence of API fields. Changes it from FieldPresence....
FieldPresence Presence
The FieldPresence.
Parameters for creating a TestMerge.
virtual ? string TargetCommitSha
The sha of the test merge revision to merge. If not specified, the latest commit from the source will...
Base class for user names.
Root ApiController for the Application.
ValueTask< IActionResult > CreateToken(CancellationToken cancellationToken)
Attempt to authenticate a User using ApiController.ApiHeaders.
ComponentInterfacingController for operations that require an instance.
ApiController for file streaming.
ValueTask< IActionResult > Download([Required, FromQuery] string ticket, CancellationToken cancellationToken)
Downloads a file with a given ticket .
async ValueTask< IActionResult > Upload([Required, FromQuery] string ticket, CancellationToken cancellationToken)
Uploads a file with a given ticket .
Helper for using the AuthorizeAttribute with the Api.Rights system.
Implements the "x-enum-varnames" OpenAPI 3.0 extension.
static void Apply(OpenApiSchema openApiSchema, Type enumType)
Applies the extension to a give openApiSchema .
Implements various filters for Swashbuckle.
static string GenerateSchemaId(Type type)
Generates the OpenAPI schema ID for a given type .
void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
const string OAuthSecuritySchemeId
The OpenApiSecurityScheme name for OAuth 2.0 authentication.
const string DocumentationSiteRouteExtension
The path to the hosted documentation site.
void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
void Apply(OpenApiSchema schema, SchemaFilterContext context)
static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
Configure the swagger settings.
const string PasswordSecuritySchemeId
The OpenApiSecurityScheme name for password authentication.
const string TokenSecuritySchemeId
The OpenApiSecurityScheme name for token authentication.
const string DocumentName
The name of the swagger document.
static void AddDefaultResponses(OpenApiDocument document)
Add the default error responses to a given document .
void Apply(OpenApiOperation operation, OperationFilterContext context)
static void ApplyAttributesForRootSchema(OpenApiSchema rootSchema, SchemaFilterContext context)
Applies the OpenApiSchema.Nullable, OpenApiSchema.ReadOnly, and OpenApiSchema.WriteOnly to OpenApiSch...
FieldPresence
Indicates whether a request field is Required or Ignored.