mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-23 15:30:34 -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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using Autofac.Core;
|
using Autofac.Core;
|
||||||
using Autofac.Core.Registration;
|
using Autofac.Core.Registration;
|
||||||
using Autofac.Extras.AttributeMetadata;
|
using Autofac.Extras.AttributeMetadata;
|
||||||
using Kyoo.Abstractions;
|
using Kyoo.Abstractions;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Kyoo.Core.Api;
|
using Kyoo.Core.Api;
|
||||||
using Kyoo.Core.Controllers;
|
using Kyoo.Core.Controllers;
|
||||||
using Kyoo.Core.Models.Options;
|
using Kyoo.Core.Models.Options;
|
||||||
using Kyoo.Core.Tasks;
|
using Kyoo.Core.Tasks;
|
||||||
using Kyoo.Database;
|
using Kyoo.Database;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -139,8 +142,21 @@ namespace Kyoo.Core
|
|||||||
string publicUrl = _configuration.GetPublicUrl();
|
string publicUrl = _configuration.GetPublicUrl();
|
||||||
|
|
||||||
services.AddMvcCore()
|
services.AddMvcCore()
|
||||||
|
.AddDataAnnotations()
|
||||||
.AddControllersAsServices()
|
.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()
|
services.AddControllers()
|
||||||
.AddNewtonsoftJson(x =>
|
.AddNewtonsoftJson(x =>
|
||||||
{
|
{
|
||||||
|
@ -77,7 +77,6 @@ namespace Kyoo.Core.Api
|
|||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="limit">The number of shows to return.</param>
|
/// <param name="limit">The number of shows to return.</param>
|
||||||
/// <returns>A page of shows.</returns>
|
/// <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="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>
|
/// <response code="404">No collection with the ID <paramref name="id"/> could be found.</response>
|
||||||
[HttpGet("{id:int}/shows")]
|
[HttpGet("{id:int}/shows")]
|
||||||
|
@ -24,6 +24,8 @@ using Kyoo.Abstractions.Controllers;
|
|||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
namespace Kyoo.Core.Api
|
||||||
@ -63,15 +65,38 @@ namespace Kyoo.Core.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a <typeparamref name="T"/> by ID.
|
/// Construct and return a page from an api.
|
||||||
/// </summary>
|
/// </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>
|
/// <param name="id">The ID of the resource to retrieve.</param>
|
||||||
/// <returns>The retrieved <typeparamref name="T"/>.</returns>
|
/// <returns>The retrieved resource.</returns>
|
||||||
/// <response code="200">The <typeparamref name="T"/> exist and is returned.</response>
|
/// <response code="404">A resource with the given ID does not exist.</response>
|
||||||
/// <response code="404">A resource with the ID <paramref name="id"/> does not exist.</response>
|
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
[PartialPermission(Kind.Read)]
|
[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);
|
T ret = await _repository.GetOrDefault(id);
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
@ -79,9 +104,20 @@ namespace Kyoo.Core.Api
|
|||||||
return ret;
|
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}")]
|
[HttpGet("{slug}")]
|
||||||
[PartialPermission(Kind.Read)]
|
[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);
|
T ret = await _repository.GetOrDefault(slug);
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
@ -89,9 +125,20 @@ namespace Kyoo.Core.Api
|
|||||||
return ret;
|
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")]
|
[HttpGet("count")]
|
||||||
[PartialPermission(Kind.Read)]
|
[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
|
try
|
||||||
{
|
{
|
||||||
@ -99,13 +146,27 @@ namespace Kyoo.Core.Api
|
|||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
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]
|
[HttpGet]
|
||||||
[PartialPermission(Kind.Read)]
|
[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] int afterID,
|
||||||
[FromQuery] Dictionary<string, string> where,
|
[FromQuery] Dictionary<string, string> where,
|
||||||
[FromQuery] int limit = 20)
|
[FromQuery] int limit = 20)
|
||||||
@ -120,21 +181,25 @@ namespace Kyoo.Core.Api
|
|||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
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)
|
/// <summary>
|
||||||
where TResult : IResource
|
/// Create new
|
||||||
{
|
/// </summary>
|
||||||
return new Page<TResult>(resources,
|
/// <remarks>
|
||||||
new Uri(BaseURL, Request.Path),
|
/// Create a new item and store it. You may leave the ID unspecified, it will be filed by Kyoo.
|
||||||
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
/// </remarks>
|
||||||
limit);
|
/// <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]
|
[HttpPost]
|
||||||
[PartialPermission(Kind.Create)]
|
[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)
|
public virtual async Task<ActionResult<T>> Create([FromBody] T resource)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -143,7 +208,7 @@ namespace Kyoo.Core.Api
|
|||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
catch (ArgumentException ex)
|
||||||
{
|
{
|
||||||
return BadRequest(new { Error = ex.Message });
|
return BadRequest(new RequestError(ex.Message));
|
||||||
}
|
}
|
||||||
catch (DuplicatedItemException)
|
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]
|
[HttpPut]
|
||||||
[PartialPermission(Kind.Write)]
|
[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
|
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}")]
|
[HttpDelete("{id:int}")]
|
||||||
[PartialPermission(Kind.Delete)]
|
[PartialPermission(Kind.Delete)]
|
||||||
public virtual async Task<IActionResult> Delete(int id)
|
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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.1" />
|
<PackageReference Include="NSwag.AspNetCore" Version="13.13.2" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.1" />
|
|
||||||
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
|
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -18,11 +18,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.OpenApi.Models;
|
using NSwag;
|
||||||
|
using NSwag.Generation.AspNetCore;
|
||||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
namespace Kyoo.Swagger
|
namespace Kyoo.Swagger
|
||||||
@ -47,44 +48,50 @@ namespace Kyoo.Swagger
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Configure(IServiceCollection services)
|
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",
|
x.Info.Contact = new OpenApiContact
|
||||||
Title = "Kyoo API",
|
|
||||||
Description = "The Kyoo's public API",
|
|
||||||
Contact = new OpenApiContact
|
|
||||||
{
|
{
|
||||||
Name = "Kyoo's github",
|
Name = "Kyoo's github",
|
||||||
Url = new Uri("https://github.com/AnonymusRaccoon/Kyoo/issues/new/choose")
|
Url = "https://github.com/AnonymusRaccoon/Kyoo"
|
||||||
},
|
};
|
||||||
License = new OpenApiLicense
|
x.Info.License = new OpenApiLicense
|
||||||
{
|
{
|
||||||
Name = "GPL-3.0-or-later",
|
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 />
|
/// <inheritdoc />
|
||||||
public IEnumerable<IStartupAction> ConfigureSteps => new IStartupAction[]
|
public IEnumerable<IStartupAction> ConfigureSteps => new IStartupAction[]
|
||||||
{
|
{
|
||||||
SA.New<IApplicationBuilder>(app => app.UseSwagger(), SA.Before + 1),
|
SA.New<IApplicationBuilder>(app => app.UseOpenApi(), SA.Before + 1),
|
||||||
SA.New<IApplicationBuilder>(app => app.UseSwaggerUI(x =>
|
SA.New<IApplicationBuilder>(app => app.UseSwaggerUi3(x =>
|
||||||
{
|
{
|
||||||
x.SwaggerEndpoint("/swagger/v1/swagger.json", "Kyoo v1");
|
x.OperationsSorter = "alpha";
|
||||||
|
x.TagsSorter = "alpha";
|
||||||
}), SA.Before),
|
}), SA.Before),
|
||||||
SA.New<IApplicationBuilder>(app => app.UseReDoc(x =>
|
SA.New<IApplicationBuilder>(app => app.UseReDoc(x =>
|
||||||
{
|
{
|
||||||
x.SpecUrl = "/swagger/v1/swagger.json";
|
x.Path = "/redoc";
|
||||||
}), SA.Before)
|
}), 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