Browse Source

Refactor Swagger generator.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
e77ea77bae
  1. 5
      src/Squidex.Infrastructure/StringExtensions.cs
  2. 57
      src/Squidex.Read/Contents/Builders/ContentSchemaBuilder.cs
  3. 1
      src/Squidex.Read/Contents/Builders/EdmModelBuilder.cs
  4. 2
      src/Squidex.Read/Users/UserExtensions.cs
  5. 2
      src/Squidex.Read/Users/UserManagerExtensions.cs
  6. 2
      src/Squidex/Config/Swagger/SwaggerServices.cs
  7. 24
      src/Squidex/Config/Swagger/XmlResponseTypesProcessor.cs
  8. 227
      src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs
  9. 352
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  10. 65
      src/Squidex/Pipeline/Swagger/SwaggerHelper.cs
  11. 1
      tests/Benchmarks/Tests/HandleEvents.cs

5
src/Squidex.Infrastructure/StringExtensions.cs

@ -36,5 +36,10 @@ namespace Squidex.Infrastructure
{
return string.Concat(value.Split(new[] { '-', '_', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(c => char.ToUpper(c[0]) + c.Substring(1)));
}
public static string WithFallback(this string value, string fallback)
{
return !string.IsNullOrWhiteSpace(value) ? value.Trim() : fallback;
}
}
}

57
src/Squidex.Read/Contents/Builders/ContentSchemaBuilder.cs

@ -0,0 +1,57 @@
// ==========================================================================
// ContentSchemaBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using NJsonSchema;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Read.Contents.Builders
{
public sealed class ContentSchemaBuilder
{
public JsonSchema4 CreateContentSchema(Schema schema, JsonSchema4 dataSchema)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(dataSchema, nameof(dataSchema));
var schemaName = schema.Properties.Label.WithFallback(schema.Name);
var contentSchema = new JsonSchema4
{
Properties =
{
["id"] = CreateProperty($"The id of the {schemaName} content."),
["data"] = CreateProperty($"The data of the {schemaName}.", dataSchema),
["version"] = CreateProperty($"The version of the {schemaName}.", JsonObjectType.Number),
["created"] = CreateProperty($"The date and time when the {schemaName} content has been created.", "date-time"),
["createdBy"] = CreateProperty($"The user that has created the {schemaName} content."),
["lastModified"] = CreateProperty($"The date and time when the {schemaName} content has been modified last.", "date-time"),
["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content last.")
},
Type = JsonObjectType.Object
};
return contentSchema;
}
private static JsonProperty CreateProperty(string description, JsonSchema4 dataSchema)
{
return new JsonProperty { Description = description, IsRequired = true, Type = JsonObjectType.Object, SchemaReference = dataSchema };
}
private static JsonProperty CreateProperty(string description, JsonObjectType type)
{
return new JsonProperty { Description = description, IsRequired = true, Type = type };
}
private static JsonProperty CreateProperty(string description, string format = null)
{
return new JsonProperty { Description = description, Format = format, IsRequired = true, Type = JsonObjectType.String };
}
}
}

1
src/Squidex.Read/Contents/Builders/EdmModelBuilder.cs

@ -57,6 +57,7 @@ namespace Squidex.Read.Contents.Builders
var entityType = new EdmEntityType("Squidex", schema.Name);
entityType.AddStructuralProperty("data", new EdmComplexTypeReference(schemaType, false));
entityType.AddStructuralProperty("version", EdmPrimitiveTypeKind.Int32);
entityType.AddStructuralProperty("created", EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty("createdBy", EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty("lastModified", EdmPrimitiveTypeKind.DateTimeOffset);

2
src/Squidex.Read/Users/UserExtensions.cs

@ -56,7 +56,7 @@ namespace Squidex.Read.Users
{
var url = user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value;
if (!string.IsNullOrWhiteSpace(url) && Uri.IsWellFormedUriString(url, UriKind.Absolute) && url.Contains("gravatar"))
if (url != null && !string.IsNullOrWhiteSpace(url) && Uri.IsWellFormedUriString(url, UriKind.Absolute) && url.Contains("gravatar"))
{
if (url.Contains("?"))
{

2
src/Squidex.Read/Users/UserManagerExtensions.cs

@ -39,7 +39,7 @@ namespace Squidex.Read.Users
{
var result = userManager.Users;
if (!string.IsNullOrWhiteSpace(email))
if (email != null && !string.IsNullOrWhiteSpace(email))
{
var upperEmail = email.ToUpperInvariant();

2
src/Squidex/Config/Swagger/SwaggerServices.cs

@ -87,7 +87,7 @@ namespace Squidex.Config.Swagger
settings.DocumentProcessors.Add(new XmlTagProcessor());
settings.OperationProcessors.Add(new XmlTagProcessor());
settings.OperationProcessors.Add(new XmlResponseTypesProcessor(settings));
settings.OperationProcessors.Add(new XmlResponseTypesProcessor());
return settings;
}

24
src/Squidex/Config/Swagger/XmlResponseTypesProcessor.cs

@ -6,16 +6,13 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NJsonSchema.Generation;
using NJsonSchema.Infrastructure;
using NSwag;
using NSwag.AspNetCore;
using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Controllers.Api;
using Squidex.Pipeline.Swagger;
// ReSharper disable UseObjectOrCollectionInitializer
@ -25,13 +22,6 @@ namespace Squidex.Config.Swagger
{
private static readonly Regex ResponseRegex = new Regex("(?<Code>[0-9]{3}) => (?<Description>.*)", RegexOptions.Compiled);
private readonly SwaggerSettings swaggerSettings;
public XmlResponseTypesProcessor(SwaggerSettings swaggerSettings)
{
this.swaggerSettings = swaggerSettings;
}
public async Task<bool> ProcessAsync(OperationProcessorContext context)
{
var hasOkResponse = false;
@ -69,22 +59,14 @@ namespace Squidex.Config.Swagger
return true;
}
private async Task AddInternalErrorResponseAsync(OperationProcessorContext context, SwaggerOperation operation)
private static async Task AddInternalErrorResponseAsync(OperationProcessorContext context, SwaggerOperation operation)
{
if (operation.Responses.ContainsKey("500"))
{
return;
}
var errorType = typeof(ErrorDto);
var errorContract = swaggerSettings.ActualContractResolver.ResolveContract(errorType);
var errorSchema = JsonObjectTypeDescription.FromType(errorType, errorContract, new Attribute[0], swaggerSettings.DefaultEnumHandling);
var response = new SwaggerResponse { Description = "Operation failed." };
response.Schema = await context.SwaggerGenerator.GenerateAndAppendSchemaFromTypeAsync(errorType, errorSchema.IsNullable, null);
operation.Responses.Add("500", response);
operation.AddResponse("500", "Operation failed", await context.SwaggerGenerator.GetErrorDtoSchemaAsync());
}
private static void RemoveOkResponse(SwaggerOperation operation)

227
src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs

@ -0,0 +1,227 @@
// ==========================================================================
// SchemaSwaggerGenerator.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using NJsonSchema;
using NSwag;
using Squidex.Core;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Pipeline.Swagger;
using Squidex.Read.Contents.Builders;
// ReSharper disable InvertIf
namespace Squidex.Controllers.ContentApi.Generator
{
public sealed class SchemaSwaggerGenerator
{
private static readonly string schemaQueryDescription;
private static readonly string schemaBodyDescription;
private readonly ContentSchemaBuilder schemaBuilder = new ContentSchemaBuilder();
private readonly SwaggerDocument document;
private readonly JsonSchema4 contentSchema;
private readonly JsonSchema4 dataSchema;
private readonly string schemaPath;
private readonly string schemaName;
private readonly string schemaKey;
private readonly string appPath;
static SchemaSwaggerGenerator()
{
schemaBodyDescription = SwaggerHelper.LoadDocs("schemabody");
schemaQueryDescription = SwaggerHelper.LoadDocs("schemaquery");
}
public SchemaSwaggerGenerator(SwaggerDocument document, string path, Schema schema, Func<string, JsonSchema4, JsonSchema4> schemaResolver, PartitionResolver partitionResolver)
{
this.document = document;
appPath = path;
schemaPath = schema.Name;
schemaName = schema.Properties.Label.WithFallback(schema.Name);
schemaKey = schema.Name.ToPascalCase();
dataSchema = schemaResolver($"{schemaKey}Dto", schema.BuildJsonSchema(partitionResolver, schemaResolver));
contentSchema = schemaResolver($"{schemaKey}ContentDto", schemaBuilder.CreateContentSchema(schema, dataSchema));
}
public void GenerateSchemaOperations()
{
document.Tags.Add(
new SwaggerTag
{
Name = schemaName, Description = $"API to managed {schemaName} contents."
});
var schemaOperations = new List<SwaggerOperations>
{
GenerateSchemaQueryOperation(),
GenerateSchemaCreateOperation(),
GenerateSchemaGetOperation(),
GenerateSchemaUpdateOperation(),
GenerateSchemaPatchOperation(),
GenerateSchemaPublishOperation(),
GenerateSchemaUnpublishOperation(),
GenerateSchemaDeleteOperation()
};
foreach (var operation in schemaOperations.SelectMany(x => x.Values).Distinct())
{
operation.Tags = new List<string> { schemaName };
}
}
private SwaggerOperations GenerateSchemaQueryOperation()
{
return AddOperation(SwaggerOperationMethod.Get, null, $"{appPath}/{schemaPath}", operation =>
{
operation.OperationId = $"Query{schemaKey}Contents";
operation.Summary = $"Queries {schemaName} contents.";
operation.Description = schemaQueryDescription;
operation.AddQueryParameter("$top", JsonObjectType.Number, "Optional number of contents to take.");
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.AddResponse("200", $"{schemaName} content retrieved.", CreateContentsSchema(schemaName, contentSchema));
});
}
private SwaggerOperations GenerateSchemaGetOperation()
{
return AddOperation(SwaggerOperationMethod.Get, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
{
operation.OperationId = $"Get{schemaKey}Content";
operation.Summary = $"Get a {schemaName} content.";
operation.AddResponse("200", $"{schemaName} content found.", contentSchema);
});
}
private SwaggerOperations GenerateSchemaCreateOperation()
{
return AddOperation(SwaggerOperationMethod.Post, null, $"{appPath}/{schemaPath}", operation =>
{
operation.OperationId = $"Create{schemaKey}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} created.", contentSchema);
});
}
private SwaggerOperations GenerateSchemaUpdateOperation()
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
{
operation.OperationId = $"Update{schemaKey}Content";
operation.Summary = $"Update a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, schemaBodyDescription);
operation.AddResponse("204", $"{schemaName} element updated.");
});
}
private SwaggerOperations GenerateSchemaPatchOperation()
{
return AddOperation(SwaggerOperationMethod.Patch, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
{
operation.OperationId = $"Path{schemaKey}Content";
operation.Summary = $"Patchs a {schemaName} content.";
operation.AddBodyParameter("data", contentSchema, schemaBodyDescription);
operation.AddResponse("204", $"{schemaName} element updated.");
});
}
private SwaggerOperations GenerateSchemaPublishOperation()
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/publish", operation =>
{
operation.OperationId = $"Publish{schemaKey}Content";
operation.Summary = $"Publish a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} element published.");
});
}
private SwaggerOperations GenerateSchemaUnpublishOperation()
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/unpublish", operation =>
{
operation.OperationId = $"Unpublish{schemaKey}Content";
operation.Summary = $"Unpublish a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} element unpublished.");
});
}
private SwaggerOperations GenerateSchemaDeleteOperation()
{
return AddOperation(SwaggerOperationMethod.Delete, schemaName, $"{appPath}/{schemaPath}/{{id}}/", operation =>
{
operation.OperationId = $"Delete{schemaKey}Content";
operation.Summary = $"Delete a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} content deleted.");
});
}
private SwaggerOperations AddOperation(SwaggerOperationMethod method, string entityName, string path, Action<SwaggerOperation> updater)
{
var operations = document.Paths.GetOrAdd(path, k => new SwaggerOperations());
var operation = new SwaggerOperation();
updater(operation);
operations[method] = operation;
if (entityName != null)
{
operation.AddPathParameter("id", JsonObjectType.String, $"The id of the {entityName} content (GUID).");
operation.AddResponse("404", $"App, schema or {entityName} content not found.");
}
return operations;
}
private static JsonSchema4 CreateContentsSchema(string schemaName, JsonSchema4 contentSchema)
{
var schema = new JsonSchema4
{
Properties =
{
["total"] = new JsonProperty
{
Type = JsonObjectType.Number, IsRequired = true, Description = $"The total number of {schemaName} contents."
},
["items"] = new JsonProperty
{
Type = JsonObjectType.Array, IsRequired = true, Item = contentSchema, Description = $"The {schemaName} contents."
}
},
Type = JsonObjectType.Object
};
return schema;
}
}
}

352
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -6,21 +6,17 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using NJsonSchema;
using NJsonSchema.Generation;
using NSwag;
using NSwag.AspNetCore;
using NSwag.SwaggerGeneration;
using Squidex.Config;
using Squidex.Controllers.Api;
using Squidex.Core.Identity;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Pipeline.Swagger;
using Squidex.Read.Apps;
@ -34,108 +30,36 @@ namespace Squidex.Controllers.ContentApi.Generator
{
public sealed class SchemasSwaggerGenerator
{
private readonly SwaggerJsonSchemaGenerator schemaGenerator;
private readonly SwaggerDocument document = new SwaggerDocument { Tags = new List<SwaggerTag>() };
private readonly SwaggerSettings swaggerSettings;
private readonly HttpContext context;
private readonly JsonSchemaResolver schemaResolver;
private readonly SwaggerGenerator swaggerGenerator;
private readonly SwaggerSettings settings;
private readonly MyUrlsOptions urlOptions;
private readonly string schemaQueryDescription;
private readonly string schemaBodyDescription;
private JsonSchema4 errorDtoSchema;
private string appBasePath;
private IAppEntity app;
private SwaggerJsonSchemaGenerator schemaGenerator;
private JsonSchemaResolver schemaResolver;
private SwaggerGenerator swaggerGenerator;
private SwaggerDocument document;
public SchemasSwaggerGenerator(IHttpContextAccessor context, SwaggerSettings settings, IOptions<MyUrlsOptions> urlOptions)
{
this.context = context.HttpContext;
this.settings = settings;
this.urlOptions = urlOptions.Value;
}
public async Task<SwaggerDocument> Generate(IAppEntity app, IEnumerable<ISchemaEntity> schemas)
{
document = SwaggerHelper.CreateApiDocument(context, urlOptions, app.Name);
schemaGenerator = new SwaggerJsonSchemaGenerator(settings);
schemaResolver = new SwaggerSchemaResolver(document, settings);
swaggerSettings = settings;
swaggerGenerator = new SwaggerGenerator(schemaGenerator, settings, schemaResolver);
schemaBodyDescription = SwaggerHelper.LoadDocs("schemabody");
schemaQueryDescription = SwaggerHelper.LoadDocs("schemaquery");
}
public async Task<SwaggerDocument> Generate(IAppEntity targetApp, IEnumerable<ISchemaEntity> schemas)
{
app = targetApp;
await GenerateBasicSchemas();
GenerateBasePath();
GenerateTitle();
GenerateRequestInfo();
GenerateContentTypes();
GenerateSchemes();
GenerateSchemasOperations(schemas);
GenerateSecurityDefinitions();
GenerateSchemasOperations(schemas, app);
GenerateSecurityRequirements();
GenerateDefaultErrors();
GeneratePing();
return document;
}
private void GenerateBasePath()
{
appBasePath = $"/content/{app.Name}";
}
private void GenerateSchemes()
{
document.Schemes.Add(context.Request.Scheme == "http" ? SwaggerSchema.Http : SwaggerSchema.Https);
}
private void GenerateTitle()
{
document.Host = context.Request.Host.Value ?? string.Empty;
document.BasePath = "/api";
}
private void GenerateRequestInfo()
{
document.Info = new SwaggerInfo
{
ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url = urlOptions.BuildUrl("images/logo-white.png", false), backgroundColor = "#3f83df" }
},
Title = $"Suidex API for {app.Name} App"
};
}
private void GenerateContentTypes()
{
document.Consumes = new List<string>
{
"application/json"
};
document.Produces = new List<string>
{
"application/json"
};
}
private void GenerateSecurityDefinitions()
{
document.SecurityDefinitions.Add("OAuth2", SwaggerHelper.CreateOAuthSchema(urlOptions));
}
await GenerateDefaultErrorsAsync();
private async Task GenerateBasicSchemas()
{
var errorType = typeof(ErrorDto);
var errorContract = swaggerSettings.ActualContractResolver.ResolveContract(errorType);
var errorSchema = JsonObjectTypeDescription.FromType(errorType, errorContract, new Attribute[0], swaggerSettings.DefaultEnumHandling);
errorDtoSchema = await swaggerGenerator.GenerateAndAppendSchemaFromTypeAsync(errorType, errorSchema.IsNullable, null);
return document;
}
private void GenerateSecurityRequirements()
@ -154,258 +78,26 @@ namespace Squidex.Controllers.ContentApi.Generator
}
}
private void GenerateDefaultErrors()
private void GenerateSchemasOperations(IEnumerable<ISchemaEntity> schemas, IAppEntity app)
{
foreach (var operation in document.Paths.Values.SelectMany(x => x.Values))
{
operation.Responses.Add("500", new SwaggerResponse { Description = "Operation failed with internal server error.", Schema = errorDtoSchema });
}
}
var appBasePath = $"/content/{app.Name}";
private void GenerateSchemasOperations(IEnumerable<ISchemaEntity> schemas)
{
foreach (var schema in schemas.Where(x => x.IsPublished).Select(x => x.Schema))
{
GenerateSchemaOperations(schema);
}
}
private void GenerateSchemaOperations(Schema schema)
{
var schemaIdentifier = schema.Name.ToPascalCase();
var schemaName = !string.IsNullOrWhiteSpace(schema.Properties.Label) ? schema.Properties.Label.Trim() : schema.Name;
document.Tags.Add(
new SwaggerTag
{
Name = schemaName,
Description = $"API to managed {schemaName} contents."
});
var dataSchema = AppendSchema($"{schemaIdentifier}Dto", schema.BuildJsonSchema(app.PartitionResolver, AppendSchema));
var schemaOperations = new List<SwaggerOperations>
{
GenerateSchemaQueryOperation(schema, schemaName, schemaIdentifier, dataSchema),
GenerateSchemaCreateOperation(schema, schemaName, schemaIdentifier, dataSchema),
GenerateSchemaGetOperation(schema, schemaName, schemaIdentifier, dataSchema),
GenerateSchemaUpdateOperation(schema, schemaName, schemaIdentifier, dataSchema),
GenerateSchemaPatchOperation(schema, schemaName, schemaIdentifier, dataSchema),
GenerateSchemaPublishOperation(schema, schemaName, schemaIdentifier),
GenerateSchemaUnpublishOperation(schema, schemaName, schemaIdentifier),
GenerateSchemaDeleteOperation(schema, schemaName, schemaIdentifier)
};
foreach (var operation in schemaOperations.SelectMany(x => x.Values).Distinct())
{
operation.Tags = new List<string> { schemaName };
}
}
private void GeneratePing()
{
var swaggerOperation = AddOperation(SwaggerOperationMethod.Get, null, $"ping/{app.Name}", operation =>
{
operation.OperationId = "MakePingTest";
operation.Description = "Make a simple request, e.g. to test credentials.";
operation.Summary = "Make Test";
});
foreach (var operation in swaggerOperation.Values)
{
operation.Tags = new List<string> { "PingTest" };
new SchemaSwaggerGenerator(document, appBasePath, schema, AppendSchema, app.PartitionResolver).GenerateSchemaOperations();
}
}
private SwaggerOperations GenerateSchemaQueryOperation(Schema schema, string schemaName, string schemaIdentifier, JsonSchema4 dataSchema)
{
return AddOperation(SwaggerOperationMethod.Get, null, $"{appBasePath}/{schema.Name}", operation =>
{
operation.OperationId = $"Query{schemaIdentifier}Contents";
operation.Summary = $"Queries {schemaName} contents.";
operation.Description = schemaQueryDescription;
operation.AddQueryParameter("$top", JsonObjectType.Number, "Optional number of contents to take.");
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.");
var responseSchema = CreateContentsSchema(schemaName, schema.Name, dataSchema);
operation.AddResponse("200", $"{schemaName} content retrieved.", responseSchema);
});
}
private SwaggerOperations GenerateSchemaGetOperation(Schema schema, string schemaName, string schemaIdentifier, JsonSchema4 dataSchema)
private async Task GenerateDefaultErrorsAsync()
{
return AddOperation(SwaggerOperationMethod.Get, schemaName, $"{appBasePath}/{schema.Name}/{{id}}", operation =>
{
operation.OperationId = $"Get{schemaIdentifier}Content";
operation.Summary = $"Get a {schemaName} content.";
const string errorDescription = "Operation failed with internal server error.";
var responseSchema = CreateContentSchema(schemaName, schemaIdentifier, dataSchema);
operation.AddResponse("200", $"{schemaName} content found.", responseSchema);
});
}
private SwaggerOperations GenerateSchemaCreateOperation(Schema schema, string schemaName, string schemaIdentifier, JsonSchema4 dataSchema)
{
return AddOperation(SwaggerOperationMethod.Post, null, $"{appBasePath}/{schema.Name}", operation =>
{
operation.OperationId = $"Create{schemaIdentifier}Content";
var errorDtoSchema = await swaggerGenerator.GetErrorDtoSchemaAsync();
operation.Summary = $"Create a {schemaName} content.";
var responseSchema = CreateContentSchema(schemaName, schemaIdentifier, dataSchema);
operation.AddBodyParameter(dataSchema, "data", schemaBodyDescription);
operation.AddQueryParameter("publish", JsonObjectType.Boolean, "Set to true to autopublish content.");
operation.AddResponse("201", $"{schemaName} created.", responseSchema);
});
}
private SwaggerOperations GenerateSchemaUpdateOperation(Schema schema, string schemaName, string schemaIdentifier, JsonSchema4 dataSchema)
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appBasePath}/{schema.Name}/{{id}}", operation =>
{
operation.OperationId = $"Update{schemaIdentifier}Content";
operation.Summary = $"Update a {schemaName} content.";
operation.AddBodyParameter(dataSchema, "data", schemaBodyDescription);
operation.AddResponse("204", $"{schemaName} element updated.");
});
}
private SwaggerOperations GenerateSchemaPatchOperation(Schema schema, string schemaName, string schemaIdentifier, JsonSchema4 dataSchema)
{
return AddOperation(SwaggerOperationMethod.Patch, schemaName, $"{appBasePath}/{schema.Name}/{{id}}", operation =>
{
operation.OperationId = $"Path{schemaIdentifier}Content";
operation.Summary = $"Patchs a {schemaName} content.";
operation.AddBodyParameter(dataSchema, "data", schemaBodyDescription);
operation.AddResponse("204", $"{schemaName} element updated.");
});
}
private SwaggerOperations GenerateSchemaPublishOperation(Schema schema, string schemaName, string schemaIdentifier)
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appBasePath}/{schema.Name}/{{id}}/publish", operation =>
{
operation.OperationId = $"Publish{schemaIdentifier}Content";
operation.Summary = $"Publish a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} element published.");
});
}
private SwaggerOperations GenerateSchemaUnpublishOperation(Schema schema, string schemaName, string schemaIdentifier)
{
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appBasePath}/{schema.Name}/{{id}}/unpublish", operation =>
{
operation.OperationId = $"Unpublish{schemaIdentifier}Content";
operation.Summary = $"Unpublish a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} element unpublished.");
});
}
private SwaggerOperations GenerateSchemaDeleteOperation(Schema schema, string schemaName, string schemaIdentifier)
{
return AddOperation(SwaggerOperationMethod.Delete, schemaName, $"{appBasePath}/{schema.Name}/{{id}}/", operation =>
{
operation.OperationId = $"Delete{schemaIdentifier}Content";
operation.Summary = $"Delete a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} content deleted.");
});
}
private SwaggerOperations AddOperation(SwaggerOperationMethod method, string entityName, string path, Action<SwaggerOperation> updater)
{
var operations = document.Paths.GetOrAdd(path, k => new SwaggerOperations());
var operation = new SwaggerOperation();
updater(operation);
operations[method] = operation;
if (entityName != null)
foreach (var operation in document.Paths.Values.SelectMany(x => x.Values))
{
operation.AddPathParameter("id", JsonObjectType.String, $"The id of the {entityName} content (GUID).");
operation.AddResponse("404", $"App, schema or {entityName} content not found.");
operation.Responses.Add("500", new SwaggerResponse { Description = errorDescription, Schema = errorDtoSchema });
}
return operations;
}
private JsonSchema4 CreateContentsSchema(string schemaName, string id, JsonSchema4 dataSchema)
{
var contentSchema = CreateContentSchema(schemaName, id, dataSchema);
var schema = new JsonSchema4
{
Properties =
{
["total"] = new JsonProperty
{
Type = JsonObjectType.Number, IsRequired = true, Description = $"The total number of {schemaName} contents."
},
["items"] = new JsonProperty
{
Type = JsonObjectType.Array, IsRequired = true, Item = contentSchema, Description = $"The {schemaName} contents."
}
},
Type = JsonObjectType.Object
};
return schema;
}
private JsonSchema4 CreateContentSchema(string schemaName, string schemaIdentifier, JsonSchema4 dataSchema)
{
var dataProperty = new JsonProperty { Description = schemaBodyDescription, Type = JsonObjectType.Object, IsRequired = true, SchemaReference = dataSchema };
var schema = new JsonSchema4
{
Properties =
{
["id"] = CreateProperty($"The id of the {schemaName} content."),
["data"] = dataProperty,
["version"] = CreateProperty($"The version of the {schemaName}", JsonObjectType.Number),
["created"] = CreateProperty($"The date and time when the {schemaName} content has been created.", "date-time"),
["createdBy"] = CreateProperty($"The user that has created the {schemaName} content."),
["lastModified"] = CreateProperty($"The date and time when the {schemaName} content has been modified last.", "date-time"),
["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content last.")
},
Type = JsonObjectType.Object
};
return AppendSchema($"{schemaIdentifier}ContentDto", schema);
}
private static JsonProperty CreateProperty(string description, JsonObjectType type)
{
return new JsonProperty { Description = description, IsRequired = true, Type = type };
}
private static JsonProperty CreateProperty(string description, string format = null)
{
return new JsonProperty { Description = description, Format = format, IsRequired = true, Type = JsonObjectType.String };
}
private JsonSchema4 AppendSchema(string name, JsonSchema4 schema)

65
src/Squidex/Pipeline/Swagger/SwaggerHelper.cs

@ -6,34 +6,70 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using NJsonSchema;
using NSwag;
using Squidex.Config;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using NSwag.SwaggerGeneration;
using Squidex.Controllers.Api;
using Squidex.Core.Identity;
namespace Squidex.Pipeline.Swagger
{
public static class SwaggerHelper
{
private static readonly ConcurrentDictionary<string, string> Docs = new ConcurrentDictionary<string, string>();
public static string LoadDocs(string name)
{
return Docs.GetOrAdd(name, x =>
var assembly = typeof(SwaggerHelper).GetTypeInfo().Assembly;
using (var resourceStream = assembly.GetManifestResourceStream($"Squidex.Docs.{name}.md"))
{
var assembly = typeof(SwaggerHelper).GetTypeInfo().Assembly;
var streamReader = new StreamReader(resourceStream);
return streamReader.ReadToEnd();
}
}
using (var resourceStream = assembly.GetManifestResourceStream($"Squidex.Docs.{name}.md"))
public static SwaggerDocument CreateApiDocument(HttpContext context, MyUrlsOptions urlOptions, string appName)
{
var document = new SwaggerDocument
{
Tags = new List<SwaggerTag>(),
Schemes = new List<SwaggerSchema>
{
context.Request.Scheme == "http" ? SwaggerSchema.Http : SwaggerSchema.Https
},
Consumes = new List<string>
{
"application/json"
},
Produces = new List<string>
{
"application/json"
},
Info = new SwaggerInfo
{
var streamReader = new StreamReader(resourceStream);
ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url = urlOptions.BuildUrl("images/logo-white.png", false), backgroundColor = "#3f83df" }
},
Title = $"Suidex API for {appName} App"
},
BasePath = "/api"
};
if (!string.IsNullOrWhiteSpace(context.Request.Host.Value))
{
document.Host = context.Request.Host.Value;
}
return streamReader.ReadToEnd();
}
});
document.SecurityDefinitions.Add("OAuth2", CreateOAuthSchema(urlOptions));
return document;
}
public static SwaggerSecurityScheme CreateOAuthSchema(MyUrlsOptions urlOptions)
@ -62,6 +98,13 @@ namespace Squidex.Pipeline.Swagger
return result;
}
public static async Task<JsonSchema4> GetErrorDtoSchemaAsync(this SwaggerGenerator swaggerGenerator)
{
var errorType = typeof(ErrorDto);
return await swaggerGenerator.GenerateAndAppendSchemaFromTypeAsync(errorType, false, null);
}
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 };
@ -89,7 +132,7 @@ namespace Squidex.Pipeline.Swagger
operation.Parameters.Add(parameter);
}
public static void AddBodyParameter(this SwaggerOperation operation, JsonSchema4 schema, string name, string description)
public static void AddBodyParameter(this SwaggerOperation operation, string name, JsonSchema4 schema, string description)
{
var parameter = new SwaggerParameter { Schema = schema, Name = name, Kind = SwaggerParameterKind.Body };

1
tests/Benchmarks/Tests/HandleEvents.cs

@ -9,7 +9,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Infrastructure;

Loading…
Cancel
Save