mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-07 10:14:13 -04:00
Swagger: Using NSwag instead of Swackbuckles
This commit is contained in:
parent
e32dcd0f30
commit
561b8e81b2
57
src/Kyoo.Abstractions/Models/Utils/RequestError.cs
Normal file
57
src/Kyoo.Abstractions/Models/Utils/RequestError.cs
Normal file
@ -0,0 +1,57 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of errors that where made in the request.
|
||||
/// </summary>
|
||||
public class RequestError
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of errors that where made in the request.
|
||||
/// </summary>
|
||||
[NotNull] public string[] Errors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="RequestError"/> with one error.
|
||||
/// </summary>
|
||||
/// <param name="error">The error to specify in the response.</param>
|
||||
public RequestError([NotNull] string error)
|
||||
{
|
||||
if (error == null)
|
||||
throw new ArgumentNullException(nameof(error));
|
||||
Errors = new[] { error };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="RequestError"/> with multiple errors.
|
||||
/// </summary>
|
||||
/// <param name="errors">The errors to specify in the response.</param>
|
||||
public RequestError([NotNull] string[] errors)
|
||||
{
|
||||
if (errors == null || !errors.Any())
|
||||
throw new ArgumentException("Errors must be non null and not empty", nameof(errors));
|
||||
Errors = errors;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,18 +18,21 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Autofac;
|
||||
using Autofac.Core;
|
||||
using Autofac.Core.Registration;
|
||||
using Autofac.Extras.AttributeMetadata;
|
||||
using Kyoo.Abstractions;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Core.Api;
|
||||
using Kyoo.Core.Controllers;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Kyoo.Core.Tasks;
|
||||
using Kyoo.Database;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -139,8 +142,21 @@ namespace Kyoo.Core
|
||||
string publicUrl = _configuration.GetPublicUrl();
|
||||
|
||||
services.AddMvcCore()
|
||||
.AddDataAnnotations()
|
||||
.AddControllersAsServices()
|
||||
.AddApiExplorer();
|
||||
.AddApiExplorer()
|
||||
.ConfigureApiBehaviorOptions(options =>
|
||||
{
|
||||
options.SuppressMapClientErrors = true;
|
||||
options.InvalidModelStateResponseFactory = ctx =>
|
||||
{
|
||||
string[] errors = ctx.ModelState
|
||||
.SelectMany(x => x.Value.Errors)
|
||||
.Select(x => x.ErrorMessage)
|
||||
.ToArray();
|
||||
return new BadRequestObjectResult(new RequestError(errors));
|
||||
};
|
||||
});
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson(x =>
|
||||
{
|
||||
|
@ -77,7 +77,6 @@ namespace Kyoo.Core.Api
|
||||
/// </param>
|
||||
/// <param name="limit">The number of shows to return.</param>
|
||||
/// <returns>A page of shows.</returns>
|
||||
/// <response code="200">A page of shows.</response>
|
||||
/// <response code="400"><paramref name="sortBy"/> or <paramref name="where"/> is invalid.</response>
|
||||
/// <response code="404">No collection with the ID <paramref name="id"/> could be found.</response>
|
||||
[HttpGet("{id:int}/shows")]
|
||||
|
@ -24,6 +24,8 @@ using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
@ -63,15 +65,38 @@ namespace Kyoo.Core.Api
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <typeparamref name="T"/> by ID.
|
||||
/// Construct and return a page from an api.
|
||||
/// </summary>
|
||||
/// <param name="resources">The list of resources that should be included in the current page.</param>
|
||||
/// <param name="limit">
|
||||
/// The max number of items that should be present per page. This should be the same as in the request,
|
||||
/// it is used to calculate if this is the last page and so on.
|
||||
/// </param>
|
||||
/// <typeparam name="TResult">The type of items on the page.</typeparam>
|
||||
/// <returns>A Page representing the response.</returns>
|
||||
protected Page<TResult> Page<TResult>(ICollection<TResult> resources, int limit)
|
||||
where TResult : IResource
|
||||
{
|
||||
return new Page<TResult>(resources,
|
||||
new Uri(BaseURL, Request.Path),
|
||||
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
||||
limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get by ID
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get a specific resource via it's ID.
|
||||
/// </remarks>
|
||||
/// <param name="id">The ID of the resource to retrieve.</param>
|
||||
/// <returns>The retrieved <typeparamref name="T"/>.</returns>
|
||||
/// <response code="200">The <typeparamref name="T"/> exist and is returned.</response>
|
||||
/// <response code="404">A resource with the ID <paramref name="id"/> does not exist.</response>
|
||||
/// <returns>The retrieved resource.</returns>
|
||||
/// <response code="404">A resource with the given ID does not exist.</response>
|
||||
[HttpGet("{id:int}")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<T>> Get(int id)
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<T>> Get(int id)
|
||||
{
|
||||
T ret = await _repository.GetOrDefault(id);
|
||||
if (ret == null)
|
||||
@ -79,9 +104,20 @@ namespace Kyoo.Core.Api
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get by slug
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get a specific resource via it's slug (a unique, human readable identifier).
|
||||
/// </remarks>
|
||||
/// <param name="slug" example="1">The slug of the resource to retrieve.</param>
|
||||
/// <returns>The retrieved resource.</returns>
|
||||
/// <response code="404">A resource with the given ID does not exist.</response>
|
||||
[HttpGet("{slug}")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<T>> Get(string slug)
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<T>> Get(string slug)
|
||||
{
|
||||
T ret = await _repository.GetOrDefault(slug);
|
||||
if (ret == null)
|
||||
@ -89,9 +125,20 @@ namespace Kyoo.Core.Api
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get count
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get the number of resources that match the filters.
|
||||
/// </remarks>
|
||||
/// <param name="where">A list of filters to respect.</param>
|
||||
/// <returns>How many resources matched that filter.</returns>
|
||||
/// <response code="400">Invalid filters.</response>
|
||||
[HttpGet("count")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<int>> GetCount([FromQuery] Dictionary<string, string> where)
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<int>> GetCount([FromQuery] Dictionary<string, string> where)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -99,13 +146,27 @@ namespace Kyoo.Core.Api
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(new { Error = ex.Message });
|
||||
return BadRequest(new RequestError(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get all resources that match the given filter.
|
||||
/// </remarks>
|
||||
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||
/// <param name="afterID">Where the pagination should start.</param>
|
||||
/// <param name="where">Filter the returned items.</param>
|
||||
/// <param name="limit">How many items per page should be returned.</param>
|
||||
/// <returns>A list of resources that match every filters.</returns>
|
||||
/// <response code="400">Invalid filters or sort information.</response>
|
||||
[HttpGet]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<Page<T>>> GetAll([FromQuery] string sortBy,
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<Page<T>>> GetAll([FromQuery] string sortBy,
|
||||
[FromQuery] int afterID,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] int limit = 20)
|
||||
@ -120,21 +181,25 @@ namespace Kyoo.Core.Api
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(new { Error = ex.Message });
|
||||
return BadRequest(new RequestError(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
protected Page<TResult> Page<TResult>(ICollection<TResult> resources, int limit)
|
||||
where TResult : IResource
|
||||
{
|
||||
return new Page<TResult>(resources,
|
||||
new Uri(BaseURL, Request.Path),
|
||||
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
||||
limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create new
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Create a new item and store it. You may leave the ID unspecified, it will be filed by Kyoo.
|
||||
/// </remarks>
|
||||
/// <param name="resource">The resource to create.</param>
|
||||
/// <returns>The created resource.</returns>
|
||||
/// <response code="400">The resource in the request body is invalid.</response>
|
||||
/// <response code="409">This item already exists (maybe a duplicated slug).</response>
|
||||
[HttpPost]
|
||||
[PartialPermission(Kind.Create)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(ActionResult<>))]
|
||||
public virtual async Task<ActionResult<T>> Create([FromBody] T resource)
|
||||
{
|
||||
try
|
||||
@ -143,7 +208,7 @@ namespace Kyoo.Core.Api
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(new { Error = ex.Message });
|
||||
return BadRequest(new RequestError(ex.Message));
|
||||
}
|
||||
catch (DuplicatedItemException)
|
||||
{
|
||||
@ -152,9 +217,26 @@ namespace Kyoo.Core.Api
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Edit an item. If the ID is specified it will be used to identify the resource.
|
||||
/// If not, the slug will be used to identify it.
|
||||
/// </remarks>
|
||||
/// <param name="resource">The resource to edit.</param>
|
||||
/// <param name="resetOld">
|
||||
/// Should old properties of the resource be discarded or should null values considered as not changed?
|
||||
/// </param>
|
||||
/// <returns>The created resource.</returns>
|
||||
/// <response code="400">The resource in the request body is invalid.</response>
|
||||
/// <response code="404">No item found with the specified ID (or slug).</response>
|
||||
[HttpPut]
|
||||
[PartialPermission(Kind.Write)]
|
||||
public virtual async Task<ActionResult<T>> Edit([FromQuery] bool resetOld, [FromBody] T resource)
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<T>> Edit([FromBody] T resource, [FromQuery] bool resetOld = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -171,37 +253,6 @@ namespace Kyoo.Core.Api
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[PartialPermission(Kind.Write)]
|
||||
public virtual async Task<ActionResult<T>> Edit(int id, [FromQuery] bool resetOld, [FromBody] T resource)
|
||||
{
|
||||
resource.ID = id;
|
||||
try
|
||||
{
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
}
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{slug}")]
|
||||
[PartialPermission(Kind.Write)]
|
||||
public virtual async Task<ActionResult<T>> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
T old = await _repository.Get(slug);
|
||||
resource.ID = old.ID;
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
}
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[PartialPermission(Kind.Delete)]
|
||||
public virtual async Task<IActionResult> Delete(int id)
|
||||
|
65
src/Kyoo.Swagger/GenericResponseProvider.cs
Normal file
65
src/Kyoo.Swagger/GenericResponseProvider.cs
Normal file
@ -0,0 +1,65 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
|
||||
namespace Kyoo.Swagger
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that change <see cref="ProducesResponseTypeAttribute"/>'s
|
||||
/// <see cref="ProducesResponseTypeAttribute.Type"/> that where set to <see cref="ActionResult{T}"/> to the
|
||||
/// return type of the method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only useful when the return type of the method is a generics type and that can't be specified in the
|
||||
/// attribute directly (since attributes don't support generics). This should not be used otherwise.
|
||||
/// </remarks>
|
||||
public class GenericResponseProvider : IApplicationModelProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Order => -1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnProvidersExecuted(ApplicationModelProviderContext context)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnProvidersExecuting(ApplicationModelProviderContext context)
|
||||
{
|
||||
foreach (ActionModel action in context.Result.Controllers.SelectMany(x => x.Actions))
|
||||
{
|
||||
IEnumerable<ProducesResponseTypeAttribute> responses = action.Filters
|
||||
.OfType<ProducesResponseTypeAttribute>()
|
||||
.Where(x => x.Type == typeof(ActionResult<>));
|
||||
foreach (ProducesResponseTypeAttribute response in responses)
|
||||
{
|
||||
Type type = action.ActionMethod.ReturnType;
|
||||
type = Utility.GetGenericDefinition(type, typeof(Task<>))?.GetGenericArguments()[0] ?? type;
|
||||
type = Utility.GetGenericDefinition(type, typeof(ActionResult<>))?.GetGenericArguments()[0] ?? type;
|
||||
response.Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.1" />
|
||||
<PackageReference Include="NSwag.AspNetCore" Version="13.13.2" />
|
||||
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -18,11 +18,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using NSwag;
|
||||
using NSwag.Generation.AspNetCore;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Swagger
|
||||
@ -47,44 +48,50 @@ namespace Kyoo.Swagger
|
||||
/// <inheritdoc />
|
||||
public void Configure(IServiceCollection services)
|
||||
{
|
||||
services.AddSwaggerGen(options =>
|
||||
services.AddTransient<IApplicationModelProvider, GenericResponseProvider>();
|
||||
services.AddOpenApiDocument(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
options.Title = "Kyoo API";
|
||||
// TODO use a real multi-line description in markdown.
|
||||
options.Description = "The Kyoo's public API";
|
||||
options.Version = "1.0.0";
|
||||
options.DocumentName = "v1";
|
||||
options.UseControllerSummaryAsTagDescription = true;
|
||||
options.GenerateExamples = true;
|
||||
options.PostProcess = x =>
|
||||
{
|
||||
Version = "v1",
|
||||
Title = "Kyoo API",
|
||||
Description = "The Kyoo's public API",
|
||||
Contact = new OpenApiContact
|
||||
x.Info.Contact = new OpenApiContact
|
||||
{
|
||||
Name = "Kyoo's github",
|
||||
Url = new Uri("https://github.com/AnonymusRaccoon/Kyoo/issues/new/choose")
|
||||
},
|
||||
License = new OpenApiLicense
|
||||
Url = "https://github.com/AnonymusRaccoon/Kyoo"
|
||||
};
|
||||
x.Info.License = new OpenApiLicense
|
||||
{
|
||||
Name = "GPL-3.0-or-later",
|
||||
Url = new Uri("https://github.com/AnonymusRaccoon/Kyoo/blob/master/LICENSE")
|
||||
}
|
||||
Url = "https://github.com/AnonymusRaccoon/Kyoo/blob/master/LICENSE"
|
||||
};
|
||||
};
|
||||
options.AddOperationFilter(x =>
|
||||
{
|
||||
if (x is AspNetCoreOperationProcessorContext ctx)
|
||||
return ctx.ApiDescription.ActionDescriptor.AttributeRouteInfo?.Order != AlternativeRoute;
|
||||
return true;
|
||||
});
|
||||
|
||||
options.LoadXmlDocumentation();
|
||||
options.UseAllOfForInheritance();
|
||||
options.SwaggerGeneratorOptions.SortKeySelector = x => x.RelativePath;
|
||||
options.DocInclusionPredicate((_, apiDescription)
|
||||
=> apiDescription.ActionDescriptor.AttributeRouteInfo?.Order != AlternativeRoute);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IStartupAction> ConfigureSteps => new IStartupAction[]
|
||||
{
|
||||
SA.New<IApplicationBuilder>(app => app.UseSwagger(), SA.Before + 1),
|
||||
SA.New<IApplicationBuilder>(app => app.UseSwaggerUI(x =>
|
||||
SA.New<IApplicationBuilder>(app => app.UseOpenApi(), SA.Before + 1),
|
||||
SA.New<IApplicationBuilder>(app => app.UseSwaggerUi3(x =>
|
||||
{
|
||||
x.SwaggerEndpoint("/swagger/v1/swagger.json", "Kyoo v1");
|
||||
x.OperationsSorter = "alpha";
|
||||
x.TagsSorter = "alpha";
|
||||
}), SA.Before),
|
||||
SA.New<IApplicationBuilder>(app => app.UseReDoc(x =>
|
||||
{
|
||||
x.SpecUrl = "/swagger/v1/swagger.json";
|
||||
x.Path = "/redoc";
|
||||
}), SA.Before)
|
||||
};
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Kyoo.Swagger
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class containing a custom way to include XML to Swagger.
|
||||
/// </summary>
|
||||
public static class XmlDocumentationLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files
|
||||
/// </summary>
|
||||
/// <param name="options">The swagger generator to add documentation to.</param>
|
||||
public static void LoadXmlDocumentation(this SwaggerGenOptions options)
|
||||
{
|
||||
ICollection<XDocument> docs = Directory.GetFiles(AppContext.BaseDirectory, "*.xml")
|
||||
.Select(XDocument.Load)
|
||||
.ToList();
|
||||
Dictionary<string, XElement> elements = docs
|
||||
.SelectMany(x => x.XPathSelectElements("/doc/members/member[@name and not(inheritdoc)]"))
|
||||
.ToDictionary(x => x.Attribute("name")!.Value, x => x);
|
||||
|
||||
foreach (XElement doc in docs
|
||||
.SelectMany(x => x.XPathSelectElements("/doc/members/member[inheritdoc[@cref]]")))
|
||||
{
|
||||
if (elements.TryGetValue(doc.Attribute("cref")!.Value, out XElement member))
|
||||
doc.Element("inheritdoc")!.ReplaceWith(member);
|
||||
}
|
||||
foreach (XElement doc in docs.SelectMany(x => x.XPathSelectElements("//see[@cref]")))
|
||||
{
|
||||
string fullName = doc.Attribute("cref")!.Value;
|
||||
string shortName = fullName[(fullName.LastIndexOf('.') + 1)..];
|
||||
// TODO won't work with fully qualified methods.
|
||||
if (fullName.StartsWith("M:"))
|
||||
shortName += "()";
|
||||
doc.ReplaceWith(shortName);
|
||||
}
|
||||
|
||||
foreach (XDocument doc in docs)
|
||||
options.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user