src/Microsoft.Azure.WebJobs.Extensions.OpenApi/Document.cs (194 lines of code) (raw):

using System.Collections.Generic; using System.Dynamic; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Filters; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors; using Microsoft.OpenApi; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using YamlDotNet.Serialization; namespace Microsoft.Azure.WebJobs.Extensions.OpenApi { /// <summary> /// This represents the document entity handling OpenAPI document. /// </summary> public class Document : IDocument { private readonly IDocumentHelper _helper; private NamingStrategy _strategy; private VisitorCollection _collection; private IHttpRequestDataObject _req; /// <summary> /// Initializes a new instance of the <see cref="Document"/> class. /// </summary> public Document(IDocumentHelper helper) { this._helper = helper.ThrowIfNullOrDefault(); } /// <inheritdoc /> public Document(OpenApiDocument openApiDocument) { this.OpenApiDocument = openApiDocument; } /// <inheritdoc /> public OpenApiDocument OpenApiDocument { get; private set; } /// <inheritdoc /> public IDocument InitialiseDocument() { this.OpenApiDocument = new OpenApiDocument() { Components = new OpenApiComponents() }; return this; } /// <inheritdoc /> public IDocument AddMetadata(OpenApiInfo info) { this.OpenApiDocument.Info = info; return this; } /// <inheritdoc /> public IDocument AddServer(IHttpRequestDataObject req, string routePrefix, IOpenApiConfigurationOptions options = null) { this._req = req; var prefix = string.IsNullOrWhiteSpace(routePrefix) ? string.Empty : $"/{routePrefix}"; var baseUrl = $"{this._req.GetScheme(options)}://{this._req.Host}{prefix}"; var server = new OpenApiServer { Url = baseUrl }; if (options.IsNullOrDefault()) { this.OpenApiDocument.Servers = new List<OpenApiServer>() { server }; return this; } // Filters out the existing base URLs that are the same as the current host URL. var servers = options.Servers .Where(p => p.Url.TrimEnd('/') != baseUrl.TrimEnd('/')) .ToList(); if (!servers.Any()) { servers.Insert(0, server); } if (options.IncludeRequestingHostName && !servers.Any(p => p.Url.TrimEnd('/') == baseUrl.TrimEnd('/'))) { servers.Insert(0, server); } this.OpenApiDocument.Servers = servers; return this; } /// <inheritdoc /> public IDocument AddNamingStrategy(NamingStrategy strategy) { this._strategy = strategy.ThrowIfNullOrDefault(); return this; } /// <inheritdoc /> public IDocument AddVisitors(VisitorCollection collection) { this._collection = collection.ThrowIfNullOrDefault(); return this; } /// <inheritdoc /> public IDocument Build(string assemblyPath, OpenApiVersionType version = OpenApiVersionType.V2) { var assembly = Assembly.LoadFrom(assemblyPath); return this.Build(assembly, version); } /// <inheritdoc /> public IDocument Build(Assembly assembly, OpenApiVersionType version = OpenApiVersionType.V2) { if (this._strategy.IsNullOrDefault()) { this._strategy = new DefaultNamingStrategy(); } var paths = new OpenApiPaths(); var tags = this._req.Query["tag"].ToArray(","); var methods = this._helper.GetHttpTriggerMethods(assembly, tags); foreach (var method in methods) { var trigger = this._helper.GetHttpTriggerAttribute(method); if (trigger.IsNullOrDefault()) { continue; } var function = this._helper.GetFunctionNameAttribute(method); if (function.IsNullOrDefault()) { continue; } var path = this._helper.GetHttpEndpoint(function, trigger); if (path.IsNullOrWhiteSpace()) { continue; } var verb = this._helper.GetHttpVerb(trigger); var item = this._helper.GetOpenApiPath(path, paths); var operations = item.Operations; var operation = this._helper.GetOpenApiOperation(method, function, verb); if (operation.IsNullOrDefault()) { continue; } operation.Security = this._helper.GetOpenApiSecurityRequirement(method, this._strategy); operation.Parameters = this._helper.GetOpenApiParameters(method, trigger, this._strategy, this._collection, version); operation.RequestBody = this._helper.GetOpenApiRequestBody(method, this._strategy, this._collection, version); operation.Responses = this._helper.GetOpenApiResponses(method, this._strategy, this._collection, version); operations[verb] = operation; item.Operations = operations; paths[path] = item; } this.OpenApiDocument.Paths = paths; this.OpenApiDocument.Components.Schemas = this._helper.GetOpenApiSchemas(methods, this._strategy, this._collection); this.OpenApiDocument.Components.SecuritySchemes = this._helper.GetOpenApiSecuritySchemes(methods, this._strategy); // this.OpenApiDocument.SecurityRequirements = this.OpenApiDocument // .Paths // .SelectMany(p => p.Value.Operations.SelectMany(q => q.Value.Security)) // .Where(p => !p.IsNullOrDefault()) // .Distinct(new OpenApiSecurityRequirementComparer()) // .ToList(); return this; } /// <inheritdoc /> public IDocument ApplyDocumentFilters(DocumentFilterCollection collection) { foreach (var filter in collection.ThrowIfNullOrDefault().DocumentFilters) { filter.Apply(this._req, this.OpenApiDocument); } return this; } /// <inheritdoc /> public async Task<string> RenderAsync(OpenApiSpecVersion version, OpenApiFormat format) { var result = await Task.Factory .StartNew(() => this.Render(version, format)) .ConfigureAwait(false); return result; } private string Render(OpenApiSpecVersion version, OpenApiFormat format) { //var serialised = default(string); //using (var sw = new StringWriter()) //{ // this.OpenApiDocument.Serialise(sw, version, format); // serialised = sw.ToString(); //} //return serialised; // This is the interim solution to resolve: // https://github.com/Azure/azure-functions-openapi-extension/issues/365 // // It will be removed when the following issue is resolved: // https://github.com/microsoft/OpenAPI.NET/issues/747 var jserialised = default(string); using (var sw = new StringWriter()) { this.OpenApiDocument.Serialise(sw, version, OpenApiFormat.Json); jserialised = sw.ToString(); } var yserialised = default(string); using (var sw = new StringWriter()) { this.OpenApiDocument.Serialise(sw, version, OpenApiFormat.Yaml); yserialised = sw.ToString(); } if (version != OpenApiSpecVersion.OpenApi2_0) { return format == OpenApiFormat.Json ? jserialised : yserialised; } var jo = JsonConvert.DeserializeObject<JObject>(jserialised); var jts = jo.DescendantsAndSelf() .Where(p => p.Type == JTokenType.Property && (p as JProperty).Name == "parameters") .SelectMany(p => p.Values<JArray>().SelectMany(q => q.Children<JObject>())) .Where(p => p.Value<string>("in") == null) .Where(p => p.Value<string>("description") != null) .Where(p => p.Value<string>("description").Contains("[formData]")) .ToList(); foreach (var jt in jts) { jt["in"] = "formData"; jt["description"] = jt.Value<string>("description").Replace("[formData]", string.Empty); } var serialised = JsonConvert.SerializeObject(jo, Formatting.Indented); if (format == OpenApiFormat.Json) { return serialised; } var converter = new ExpandoObjectConverter(); var deserialised = JsonConvert.DeserializeObject<ExpandoObject>(serialised, converter); serialised = new SerializerBuilder().Build().Serialize(deserialised); return serialised; } } }