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;
49 public static void Configure(SwaggerGenOptions swaggerGenOptions,
string assemblyDocumentationPath,
string apiDocumentationPath)
51 swaggerGenOptions.SwaggerDoc(
57 License =
new OpenApiLicense
60 Url =
new Uri(
"https://github.com/tgstation/tgstation-server/blob/dev/LICENSE"),
62 Contact =
new OpenApiContact
64 Name =
"/tg/station 13",
65 Url =
new Uri(
"https://github.com/tgstation"),
67 Description =
"A production scale tool for BYOND server management",
72 swaggerGenOptions.IncludeXmlComments(assemblyDocumentationPath);
73 swaggerGenOptions.IncludeXmlComments(apiDocumentationPath);
76 swaggerGenOptions.UseAllOfToExtendReferenceSchemas();
87 In = ParameterLocation.Header,
88 Type = SecuritySchemeType.Http,
89 Name = HeaderNames.Authorization,
95 In = ParameterLocation.Header,
96 Type = SecuritySchemeType.Http,
97 Name = HeaderNames.Authorization,
103 BearerFormat =
"JWT",
104 In = ParameterLocation.Header,
105 Type = SecuritySchemeType.Http,
106 Name = HeaderNames.Authorization,
117 var errorMessageContent =
new Dictionary<string, OpenApiMediaType>
120 MediaTypeNames.Application.Json,
123 Schema =
new OpenApiSchema
125 Reference =
new OpenApiReference
128 Type = ReferenceType.Schema,
135 void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete)
137 string responseKey = $
"{(int)code}";
139 document.Components.Responses.Add(responseKey, concrete);
141 var referenceResponse =
new OpenApiResponse
143 Reference =
new OpenApiReference
145 Type = ReferenceType.Response,
150 foreach (var operation
in document.Paths.SelectMany(path => path.Value.Operations))
151 operation.Value.Responses.TryAdd(responseKey, referenceResponse);
154 AddDefaultResponse(HttpStatusCode.BadRequest,
new OpenApiResponse
156 Description =
"A badly formatted request was made. See error message for details.",
157 Content = errorMessageContent,
160 AddDefaultResponse(HttpStatusCode.Unauthorized,
new OpenApiResponse
162 Description =
"Invalid Authentication header.",
165 AddDefaultResponse(HttpStatusCode.Forbidden,
new OpenApiResponse
167 Description =
"User lacks sufficient permissions for the operation.",
170 AddDefaultResponse(HttpStatusCode.Conflict,
new OpenApiResponse
172 Description =
"A data integrity check failed while performing the operation. See error message for details.",
173 Content = errorMessageContent,
176 AddDefaultResponse(HttpStatusCode.NotAcceptable,
new OpenApiResponse
178 Description = $
"Invalid Accept header, TGS requires `{HeaderNames.Accept}: {MediaTypeNames.Application.Json}`.",
179 Content = errorMessageContent,
182 AddDefaultResponse(HttpStatusCode.InternalServerError,
new OpenApiResponse
184 Description = ErrorCode.InternalServerError.Describe(),
185 Content = errorMessageContent,
188 AddDefaultResponse(HttpStatusCode.ServiceUnavailable,
new OpenApiResponse
190 Description =
"The server may be starting up or shutting down.",
193 AddDefaultResponse(HttpStatusCode.NotImplemented,
new OpenApiResponse
195 Description = ErrorCode.RequiresPosixSystemIdentity.Describe(),
196 Content = errorMessageContent,
208 rootSchema.Nullable =
false;
210 var rootRequestSchema = rootSchemaId.EndsWith(
"Request", StringComparison.Ordinal);
211 var rootResponseSchema = rootSchemaId.EndsWith(
"Response", StringComparison.Ordinal);
212 var isPutRequest = rootSchemaId.EndsWith(
"CreateRequest", StringComparison.Ordinal);
214 Tuple<PropertyInfo, string, OpenApiSchema, IDictionary<string, OpenApiSchema>> GetTypeFromKvp(Type currentType, KeyValuePair<string, OpenApiSchema> kvp, IDictionary<string, OpenApiSchema> schemaDictionary)
216 var propertyInfo = currentType
218 .Single(x => x.Name.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase));
227 var subSchemaStack =
new Stack<Tuple<PropertyInfo, string, OpenApiSchema, IDictionary<string, OpenApiSchema>>>(
231 x => GetTypeFromKvp(context.Type, x, rootSchema.Properties))
232 .Where(x => x.Item3.Reference ==
null));
234 while (subSchemaStack.Count > 0)
236 var tuple = subSchemaStack.Pop();
237 var subSchema = tuple.Item3;
239 var subSchemaPropertyInfo = tuple.Item1;
241 if (subSchema.Properties !=
null
242 && !subSchemaPropertyInfo
245 .Any(x => x == typeof(IEnumerable)))
246 foreach (var kvp in subSchema.Properties.Where(x => x.Value.Reference ==
null))
247 subSchemaStack.Push(GetTypeFromKvp(subSchemaPropertyInfo.PropertyType, kvp, subSchema.Properties));
249 var attributes = subSchemaPropertyInfo
250 .GetCustomAttributes();
251 var responsePresence = attributes
256 var requestOptions = attributes
258 .OrderBy(x => x.PutOnly)
261 if (requestOptions.Any() && requestOptions.All(x => x.Presence ==
FieldPresence.Ignored && !x.PutOnly))
262 subSchema.ReadOnly =
true;
264 var subSchemaId = tuple.Item2;
265 var subSchemaOwningDictionary = tuple.Item4;
266 if (rootResponseSchema)
268 subSchema.Nullable = responsePresence ==
FieldPresence.Optional;
270 subSchemaOwningDictionary.Remove(subSchemaId);
272 else if (rootRequestSchema)
274 subSchema.Nullable =
true;
275 var lastOptionWasIgnored =
false;
276 foreach (var requestOption
in requestOptions)
278 var validForThisRequest = !requestOption.PutOnly || isPutRequest;
279 if (!validForThisRequest)
282 lastOptionWasIgnored =
false;
283 switch (requestOption.Presence)
286 lastOptionWasIgnored =
true;
289 subSchema.Nullable =
true;
292 subSchema.Nullable =
false;
295 throw new InvalidOperationException($
"Invalid FieldPresence: {requestOption.Presence}!");
299 if (lastOptionWasIgnored)
300 subSchemaOwningDictionary.Remove(subSchemaId);
303 && requestOptions.All(x => x.Presence ==
FieldPresence.Required && !x.PutOnly))
304 subSchema.Nullable = subSchemaId.Equals(
306 StringComparison.OrdinalIgnoreCase)
322 return "ShallowUserResponse";
325 return $
"Paginated{type.GenericTypeArguments.First().Name}";
331 public void Apply(OpenApiOperation operation, OperationFilterContext context)
333 ArgumentNullException.ThrowIfNull(operation);
334 ArgumentNullException.ThrowIfNull(context);
336 operation.OperationId = $
"{context.MethodInfo.DeclaringType.Name}.{context.MethodInfo.Name}";
338 var authAttributes = context
341 .GetCustomAttributes(
true)
345 .GetCustomAttributes(
true))
348 if (authAttributes.Any())
350 var tokenScheme =
new OpenApiSecurityScheme
352 Reference =
new OpenApiReference
354 Type = ReferenceType.SecurityScheme,
359 operation.Security =
new List<OpenApiSecurityRequirement>
361 new OpenApiSecurityRequirement
371 operation.Parameters.Insert(0,
new OpenApiParameter
373 Reference =
new OpenApiReference
375 Type = ReferenceType.Parameter,
379 else if (typeof(
TransferController).IsAssignableFrom(context.MethodInfo.DeclaringType))
381 operation.RequestBody =
new OpenApiRequestBody
383 Content =
new Dictionary<string, OpenApiMediaType>
386 MediaTypeNames.Application.Octet,
389 Schema =
new OpenApiSchema
400 var twoHundredResponseContents = operation.Responses[
"200"].Content;
401 var fileContent = twoHundredResponseContents[MediaTypeNames.Application.Json];
402 twoHundredResponseContents.Remove(MediaTypeNames.Application.Json);
403 twoHundredResponseContents.Add(MediaTypeNames.Application.Octet, fileContent);
408 var passwordScheme =
new OpenApiSecurityScheme
410 Reference =
new OpenApiReference
412 Type = ReferenceType.SecurityScheme,
417 var oAuthScheme =
new OpenApiSecurityScheme
419 Reference =
new OpenApiReference
421 Type = ReferenceType.SecurityScheme,
426 operation.Parameters.Add(
new OpenApiParameter
428 In = ParameterLocation.Header,
430 Description =
"The external OAuth service provider.",
431 Style = ParameterStyle.Simple,
432 Example =
new OpenApiString(
"Discord"),
433 Schema =
new OpenApiSchema
439 operation.Security =
new List<OpenApiSecurityRequirement>
441 new OpenApiSecurityRequirement
457 public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
459 ArgumentNullException.ThrowIfNull(swaggerDoc);
460 ArgumentNullException.ThrowIfNull(context);
462 swaggerDoc.ExternalDocs =
new OpenApiExternalDocs
464 Description =
"API Usage Documentation",
465 Url =
new Uri(
"https://tgstation.github.io/tgstation-server/api.html"),
470 In = ParameterLocation.Header,
471 Name = ApiHeaders.InstanceIdHeader,
472 Description =
"The instance ID being accessed",
474 Style = ParameterStyle.Simple,
475 Schema = new OpenApiSchema
481 var productHeaderSchema =
new OpenApiSchema
484 Format =
"productheader",
489 In = ParameterLocation.Header,
490 Name = ApiHeaders.ApiVersionHeader,
491 Description =
"The API version being used in the form \"Tgstation.Server.Api/[API version]\"",
493 Style = ParameterStyle.Simple,
494 Example = new OpenApiString($
"Tgstation.Server.Api/{ApiHeaders.Version}"),
495 Schema = productHeaderSchema,
498 swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent,
new OpenApiParameter
500 In = ParameterLocation.Header,
501 Name = HeaderNames.UserAgent,
502 Description =
"The user agent of the calling client.",
504 Style = ParameterStyle.Simple,
505 Example = new OpenApiString(
"Your-user-agent/1.0.0.0"),
506 Schema = productHeaderSchema,
509 var allSchemas = context
512 foreach (var path
in swaggerDoc.Paths)
513 foreach (var operation
in path.Value.Operations.Select(x => x.Value))
515 operation.Parameters.Insert(0,
new OpenApiParameter
517 Reference =
new OpenApiReference
519 Type = ReferenceType.Parameter,
524 operation.Parameters.Insert(1,
new OpenApiParameter
526 Reference =
new OpenApiReference
528 Type = ReferenceType.Parameter,
529 Id = HeaderNames.UserAgent,
538 public void Apply(OpenApiSchema schema, SchemaFilterContext context)
540 ArgumentNullException.ThrowIfNull(schema);
541 ArgumentNullException.ThrowIfNull(context);
544 schema.Required.Clear();
546 if (context.MemberInfo ==
null)
547 ApplyAttributesForRootSchema(schema, context);
549 if (!schema.Enum?.Any() ??
false)
553 Type firstGenericArgumentOrType = context.Type.IsConstructedGenericType
554 ? context.Type.GenericTypeArguments.First()
561 public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
563 ArgumentNullException.ThrowIfNull(requestBody);
564 ArgumentNullException.ThrowIfNull(context);
566 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.
async Task< IActionResult > CreateToken(CancellationToken cancellationToken)
Attempt to authenticate a User using ApiController.ApiHeaders.
ComponentInterfacingController for operations that require an instance.
Helper for using the AuthorizeAttribute with the Api.Rights system.
ApiController for file streaming.
Task< IActionResult > Download([Required, FromQuery] string ticket, CancellationToken cancellationToken)
Downloads a file with a given ticket .
async Task< IActionResult > Upload([Required, FromQuery] string ticket, CancellationToken cancellationToken)
Uploads a file with a given ticket .
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.
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.
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.