Browse Source

Swagger update.

pull/363/head
Sebastian Stehle 7 years ago
parent
commit
e50b931767
  1. 2
      src/Squidex/Areas/Api/Config/Swagger/FixProcessor.cs
  2. 29
      src/Squidex/Areas/Api/Config/Swagger/SecurityProcessor.cs
  3. 3
      src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs
  4. 39
      src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs
  5. 84
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs
  6. 28
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs
  7. 4
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  8. 10
      src/Squidex/Pipeline/Swagger/NSwagHelper.cs

2
src/Squidex/Areas/Api/Config/Swagger/FixProcessor.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure.Tasks;
namespace Squidex.Areas.Api.Config.Swagger
{
public class FixProcessor : IOperationProcessor
public sealed class FixProcessor : IOperationProcessor
{
private static readonly JsonSchema4 StringSchema = new JsonSchema4 { Type = JsonObjectType.String };

29
src/Squidex/Areas/Api/Config/Swagger/SecurityProcessor.cs

@ -15,7 +15,7 @@ using Squidex.Web;
namespace Squidex.Areas.Api.Config.Swagger
{
public class SecurityProcessor : SecurityDefinitionAppender
public sealed class SecurityProcessor : SecurityDefinitionAppender
{
public SecurityProcessor(IOptions<UrlsOptions> urlOptions)
: base(Constants.SecurityDefinition, Enumerable.Empty<string>(), CreateOAuthSchema(urlOptions.Value))
@ -24,26 +24,33 @@ namespace Squidex.Areas.Api.Config.Swagger
private static SwaggerSecurityScheme CreateOAuthSchema(UrlsOptions urlOptions)
{
var securityScheme = new SwaggerSecurityScheme();
var security = new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.OAuth2
};
var tokenUrl = urlOptions.BuildUrl($"{Constants.IdentityServerPrefix}/connect/token", false);
securityScheme.TokenUrl = tokenUrl;
security.TokenUrl = tokenUrl;
var securityDocs = NSwagHelper.LoadDocs("security");
var securityText = securityDocs.Replace("<TOKEN_URL>", tokenUrl);
SetupDescription(security, tokenUrl);
securityScheme.Description = securityText;
securityScheme.Type = SwaggerSecuritySchemeType.OAuth2;
securityScheme.Flow = SwaggerOAuth2Flow.Application;
security.Flow = SwaggerOAuth2Flow.Application;
securityScheme.Scopes = new Dictionary<string, string>
security.Scopes = new Dictionary<string, string>
{
[Constants.ApiScope] = "Read and write access to the API"
};
return securityScheme;
return security;
}
private static void SetupDescription(SwaggerSecurityScheme securityScheme, string tokenUrl)
{
var securityDocs = NSwagHelper.LoadDocs("security");
var securityText = securityDocs.Replace("<TOKEN_URL>", tokenUrl);
securityScheme.Description = securityText;
}
}
}

3
src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs

@ -22,6 +22,9 @@ namespace Squidex.Areas.Api.Config.Swagger
{
public static void AddMySwaggerSettings(this IServiceCollection services)
{
services.AddSingletonAs<ErrorDtoProcessor>()
.As<IDocumentProcessor>();
services.AddSingletonAs<RuleActionProcessor>()
.As<IDocumentProcessor>();

39
src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs

@ -6,16 +6,12 @@
// ==========================================================================
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NJsonSchema.Infrastructure;
using NSwag;
using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Pipeline.Swagger;
#pragma warning disable RECS0033 // Convert 'if' to '||' expression
namespace Squidex.Areas.Api.Config.Swagger
{
@ -53,42 +49,7 @@ namespace Squidex.Areas.Api.Config.Swagger
}
}
await AddInternalErrorResponseAsync(context, operation);
CleanupResponses(operation);
return true;
}
private static async Task AddInternalErrorResponseAsync(OperationProcessorContext context, SwaggerOperation operation)
{
var errorSchema = await context.SchemaGenerator.GetErrorDtoSchemaAsync(context.SchemaResolver);
if (!operation.Responses.ContainsKey("500"))
{
operation.AddResponse("500", "Operation failed", errorSchema);
}
foreach (var (code, response) in operation.Responses)
{
if (code != "404" && code.StartsWith("4", StringComparison.OrdinalIgnoreCase) && response.Schema == null)
{
response.Schema = errorSchema;
}
}
}
private static void CleanupResponses(SwaggerOperation operation)
{
foreach (var (code, response) in operation.Responses.ToList())
{
if (string.IsNullOrWhiteSpace(response.Description) ||
response.Description?.Contains("=&gt;") == true ||
response.Description?.Contains("=>") == true)
{
operation.Responses.Remove(code);
}
}
}
}
}

84
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs

@ -32,6 +32,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
private readonly string schemaName;
private readonly string schemaType;
private readonly string appPath;
private readonly JsonSchema4 statusSchema;
private readonly string appName;
static SchemaSwaggerGenerator()
@ -46,6 +47,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
string appPath,
Schema schema,
SchemaResolver schemaResolver,
JsonSchema4 statusSchema,
PartitionResolver partitionResolver)
{
this.document = document;
@ -53,6 +55,8 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
this.appName = appName;
this.appPath = appPath;
this.statusSchema = statusSchema;
schemaPath = schema.Name;
schemaName = schema.DisplayName();
schemaType = schema.TypeName();
@ -72,15 +76,13 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
var schemaOperations = new List<SwaggerPathItem>
{
GenerateSchemaQueryOperation(),
GenerateSchemaCreateOperation(),
GenerateSchemaGetsOperation(),
GenerateSchemaGetOperation(),
GenerateSchemaCreateOperation(),
GenerateSchemaUpdateOperation(),
GenerateSchemaPatchOperation(),
GenerateSchemaPublishOperation(),
GenerateSchemaUnpublishOperation(),
GenerateSchemaArchiveOperation(),
GenerateSchemaRestoreOperation(),
GenerateSchemaUpdatePatchOperation(),
GenerateSchemaStatusOperation(),
GenerateSchemaDiscardOperation(),
GenerateSchemaDeleteOperation()
};
@ -90,11 +92,12 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
}
}
private SwaggerPathItem GenerateSchemaQueryOperation()
private SwaggerPathItem GenerateSchemaGetsOperation()
{
return AddOperation(SwaggerOperationMethod.Get, null, $"{appPath}/{schemaPath}", operation =>
{
operation.OperationId = $"Query{schemaType}Contents";
operation.Summary = $"Queries {schemaName} contents.";
operation.Description = SchemaQueryDescription;
@ -103,7 +106,8 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
operation.AddQueryParameter("$skip", JsonObjectType.Number, "Optional number of contents to skip.");
operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter.");
operation.AddQueryParameter("$search", JsonObjectType.String, "Optional OData full text search.");
operation.AddQueryParameter("orderby", JsonObjectType.String, "Optional OData order definition.");
operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition.");
operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition.");
operation.AddResponse("200", $"{schemaName} content retrieved.", CreateContentsSchema(schemaName, contentSchema));
@ -116,6 +120,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return AddOperation(SwaggerOperationMethod.Get, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
{
operation.OperationId = $"Get{schemaType}Content";
operation.Summary = $"Get a {schemaName} content.";
operation.AddResponse("200", $"{schemaName} content found.", contentSchema);
@ -129,12 +134,14 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return AddOperation(SwaggerOperationMethod.Post, null, $"{appPath}/{schemaPath}", operation =>
{
operation.OperationId = $"Create{schemaType}Content";
operation.Summary = $"Create a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddQueryParameter("publish", JsonObjectType.Boolean, "Set to true to autopublish content.");
operation.AddResponse("201", $"{schemaName} content created.", contentSchema);
operation.AddResponse("200", $"{schemaName} content created.", contentSchema);
operation.AddResponse("400", "Content data valid.");
AddSecurity(operation, Permissions.AppContentsCreate);
});
@ -145,74 +152,64 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
{
operation.OperationId = $"Update{schemaType}Content";
operation.Summary = $"Update a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddResponse("200", $"{schemaName} content updated.", dataSchema);
operation.AddResponse("200", $"{schemaName} content updated.", contentSchema);
operation.AddResponse("400", "Content data valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
});
}
private SwaggerPathItem GenerateSchemaPatchOperation()
private SwaggerPathItem GenerateSchemaUpdatePatchOperation()
{
return AddOperation(SwaggerOperationMethod.Patch, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
{
operation.OperationId = $"Path{schemaType}Content";
operation.Summary = $"Patch a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddResponse("200", $"{schemaName} content patched.", dataSchema);
operation.AddResponse("200", $"{schemaName} content patched.", contentSchema);
operation.AddResponse("400", "Status change not valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
});
}
private SwaggerPathItem GenerateSchemaPublishOperation()
private SwaggerPathItem GenerateSchemaStatusOperation()
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/publish", operation =>
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/status", operation =>
{
operation.OperationId = $"Publish{schemaType}Content";
operation.Summary = $"Publish a {schemaName} content.";
operation.OperationId = $"Change{schemaType}ContentStatus";
operation.AddResponse("204", $"{schemaName} content published.");
});
}
operation.Summary = $"Change status of {schemaName} content.";
private SwaggerPathItem GenerateSchemaUnpublishOperation()
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/unpublish", operation =>
{
operation.OperationId = $"Unpublish{schemaType}Content";
operation.Summary = $"Unpublish a {schemaName} content.";
operation.AddBodyParameter("request", statusSchema, "The request to change content status.");
operation.AddResponse("204", $"{schemaName} content status changed.", contentSchema);
operation.AddResponse("400", "Content data valid.");
operation.AddResponse("204", $"{schemaName} content unpublished.");
AddSecurity(operation, Permissions.AppContentsStatus);
});
}
private SwaggerPathItem GenerateSchemaArchiveOperation()
private SwaggerPathItem GenerateSchemaDiscardOperation()
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/archive", operation =>
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/discard", operation =>
{
operation.OperationId = $"Archive{schemaType}Content";
operation.Summary = $"Archive a {schemaName} content.";
operation.OperationId = $"Discard{schemaType}Content";
operation.AddResponse("204", $"{schemaName} content restored.");
AddSecurity(operation, Permissions.AppContentsRead);
});
}
operation.Summary = $"Discard changes of {schemaName} content.";
private SwaggerPathItem GenerateSchemaRestoreOperation()
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/restore", operation =>
{
operation.OperationId = $"Restore{schemaType}Content";
operation.Summary = $"Restore a {schemaName} content.";
operation.AddResponse("400", "No pending draft.");
operation.AddResponse("200", $"{schemaName} content status changed.", contentSchema);
operation.AddResponse("204", $"{schemaName} content restored.");
AddSecurity(operation, Permissions.AppContentsDiscard);
});
}
@ -221,6 +218,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return AddOperation(SwaggerOperationMethod.Delete, schemaName, $"{appPath}/{schemaPath}/{{id}}/", operation =>
{
operation.OperationId = $"Delete{schemaType}Content";
operation.Summary = $"Delete a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} content deleted.");

28
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs

@ -18,6 +18,7 @@ using NSwag.SwaggerGeneration;
using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Areas.Api.Config.Swagger;
using Squidex.Areas.Api.Controllers.Contents.Models;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
@ -32,6 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
private readonly SwaggerDocumentSettings settings = new SwaggerDocumentSettings();
private SwaggerJsonSchemaGenerator schemaGenerator;
private SwaggerDocument document;
private JsonSchema4 statusSchema;
private JsonSchemaResolver schemaResolver;
public SchemasSwaggerGenerator(IOptions<UrlsOptions> urlOptions, IEnumerable<IDocumentProcessor> documentProcessors)
@ -53,9 +55,9 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
schemaGenerator = new SwaggerJsonSchemaGenerator(settings);
schemaResolver = new SwaggerSchemaResolver(document, settings);
GenerateSchemasOperations(schemas, app);
statusSchema = await GenerateStatusSchemaAsync();
await GenerateDefaultErrorsAsync();
GenerateSchemasOperations(schemas, app);
var context =
new DocumentProcessorContext(document,
@ -73,25 +75,23 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return document;
}
private void GenerateSchemasOperations(IEnumerable<ISchemaEntity> schemas, IAppEntity app)
private Task<JsonSchema4> GenerateStatusSchemaAsync()
{
var appBasePath = $"/content/{app.Name}";
var errorType = typeof(ChangeStatusDto);
foreach (var schema in schemas.Select(x => x.SchemaDef).Where(x => x.IsPublished))
{
new SchemaSwaggerGenerator(document, app.Name, appBasePath, schema, AppendSchema, app.PartitionResolver()).GenerateSchemaOperations();
}
return schemaGenerator.GenerateWithReferenceAsync<JsonSchema4>(errorType, Enumerable.Empty<Attribute>(), schemaResolver);
}
private async Task GenerateDefaultErrorsAsync()
private void GenerateSchemasOperations(IEnumerable<ISchemaEntity> schemas, IAppEntity app)
{
const string errorDescription = "Operation failed with internal server error.";
var errorDtoSchema = await schemaGenerator.GetErrorDtoSchemaAsync(schemaResolver);
var appBasePath = $"/content/{app.Name}";
foreach (var operation in document.Paths.Values.SelectMany(x => x.Values))
foreach (var schema in schemas.Select(x => x.SchemaDef).Where(x => x.IsPublished))
{
operation.Responses.Add("500", new SwaggerResponse { Description = errorDescription, Schema = errorDtoSchema });
var partition = app.PartitionResolver();
new SchemaSwaggerGenerator(document, app.Name, appBasePath, schema, AppendSchema, statusSchema, partition)
.GenerateSchemaOperations();
}
}

4
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -130,14 +130,14 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
if (controller.HasPermission(Permissions.AppContentsUpdate, app, schema))
{
AddPatchLink("patch", controller.Url<ContentsController>(x => nameof(x.PatchContent), values));
AddPutLink("update", controller.Url<ContentsController>(x => nameof(x.PutContent), values));
if (Status == Status.Published)
{
AddPutLink("draft/propose", controller.Url<ContentsController>(x => nameof(x.PutContent), values) + "?asDraft=true");
}
AddPatchLink("patch", controller.Url<ContentsController>(x => nameof(x.PatchContent), values));
}
if (controller.HasPermission(Permissions.AppContentsDelete, app, schema))

10
src/Squidex/Pipeline/Swagger/NSwagHelper.cs

@ -8,11 +8,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using NJsonSchema;
using NJsonSchema.Generation;
using NSwag;
using Squidex.Web;
@ -69,13 +66,6 @@ namespace Squidex.Pipeline.Swagger
return document;
}
public static async Task<JsonSchema4> GetErrorDtoSchemaAsync(this JsonSchemaGenerator schemaGenerator, JsonSchemaResolver resolver)
{
var errorType = typeof(ErrorDto);
return await schemaGenerator.GenerateWithReferenceAsync<JsonSchema4>(errorType, Enumerable.Empty<Attribute>(), resolver);
}
public static void AddQueryParameter(this SwaggerOperation operation, string name, JsonObjectType type, string description = null)
{
var parameter = new SwaggerParameter { Type = type, Name = name, Kind = SwaggerParameterKind.Query };

Loading…
Cancel
Save