/// <summary> /// Generates Swagger document for the specified Azure Function classes, including only the listed Functions. /// </summary> /// <param name="azureFunctionClassTypes">The Azure Function classes (static classes)</param> /// <param name="functionNames">The function names (defined by FunctionNameAttribute)</param> /// <returns>The generated Swagger document</returns> public async Task <OpenApiDocument> GenerateForAzureFunctionClassesAsync(IEnumerable <Type> azureFunctionClassTypes, IList <string> functionNames) { var document = await CreateDocumentAsync().ConfigureAwait(false); var schemaResolver = new OpenApiSchemaResolver(document, Settings); var usedAzureFunctionClassTypes = new List <Type>(); foreach (var azureFunctionClassType in azureFunctionClassTypes) { var generator = new OpenApiDocumentGenerator(Settings, schemaResolver); var isIncluded = await GenerateForAzureFunctionClassAsync(document, azureFunctionClassType, generator, schemaResolver, functionNames); if (isIncluded) { usedAzureFunctionClassTypes.Add(azureFunctionClassType); } } document.GenerateOperationIds(); foreach (var processor in Settings.DocumentProcessors) { processor.Process(new DocumentProcessorContext(document, azureFunctionClassTypes, usedAzureFunctionClassTypes, schemaResolver, Settings.SchemaGenerator, Settings)); } return(document); }
private async Task <bool> GenerateForAzureFunctionClassAsync(OpenApiDocument document, Type staticAzureFunctionClassType, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver, IList <string> functionNames) { var operations = new List <Tuple <OpenApiOperationDescription, MethodInfo> >(); foreach (var method in GetActionMethods(staticAzureFunctionClassType, functionNames)) { var httpPaths = GetHttpPaths(method); var httpMethods = GetSupportedHttpMethods(method); foreach (var httpPath in httpPaths) { foreach (var httpMethod in httpMethods) { var operationDescription = new OpenApiOperationDescription { Path = httpPath, Method = httpMethod, Operation = new OpenApiOperation { IsDeprecated = method.GetCustomAttribute <ObsoleteAttribute>() != null, OperationId = GetOperationId(document, staticAzureFunctionClassType.Name, method) } }; operations.Add(new Tuple <OpenApiOperationDescription, MethodInfo>(operationDescription, method)); } } } return(await AddOperationDescriptionsToDocumentAsync(document, staticAzureFunctionClassType, operations, swaggerGenerator, schemaResolver)); }
/// <summary>Generates a Swagger specification for the given controller types.</summary> /// <param name="controllerTypes">The types of the controller.</param> /// <returns>The <see cref="OpenApiDocument" />.</returns> /// <exception cref="InvalidOperationException">The operation has more than one body parameter.</exception> public async Task <OpenApiDocument> GenerateForControllersAsync(IEnumerable <Type> controllerTypes) { var document = await CreateDocumentAsync().ConfigureAwait(false); var schemaResolver = new OpenApiSchemaResolver(document, Settings); var usedControllerTypes = new List <Type>(); foreach (var controllerType in controllerTypes) { var generator = new OpenApiDocumentGenerator(Settings, schemaResolver); var isIncluded = GenerateForController(document, controllerType, generator, schemaResolver); if (isIncluded) { usedControllerTypes.Add(controllerType); } } document.GenerateOperationIds(); foreach (var processor in Settings.DocumentProcessors) { processor.Process(new DocumentProcessorContext(document, controllerTypes, usedControllerTypes, schemaResolver, Settings.SchemaGenerator, Settings)); } return(document); }
public async Task <OpenApiDocument> GenerateDocument(Assembly assembly) { var document = await CreateDocumentAsync().ConfigureAwait(false); var schemaResolver = new OpenApiSchemaResolver(document, Settings); var handlerTypes = GetAllHandlerTypes(assembly); var usedHandlerTypes = new List <Type>(); foreach (var handlerType in handlerTypes) { var generator = new OpenApiDocumentGenerator(Settings, schemaResolver); var isIncluded = GenerateForHandler(document, handlerType, generator, schemaResolver); if (isIncluded) { usedHandlerTypes.Add(handlerType); } } document.GenerateOperationIds(); foreach (var processor in Settings.DocumentProcessors) { processor.Process(new DocumentProcessorContext(document, handlerTypes, usedHandlerTypes, schemaResolver, Settings.SchemaGenerator, Settings)); } return(document); }
/// <summary>Initializes a new instance of the <see cref="AspNetCoreOperationProcessorContext" /> class.</summary> /// <param name="document">The document.</param> /// <param name="operationDescription">The operation description.</param> /// <param name="controllerType">Type of the controller.</param> /// <param name="methodInfo">The method information.</param> /// <param name="swaggerGenerator">The swagger generator.</param> /// <param name="schemaResolver">The schema resolver.</param> /// <param name="settings">The sett</param> /// <param name="allOperationDescriptions">All operation descriptions.</param> /// <param name="schemaGenerator">The schema generator.</param> public AspNetCoreOperationProcessorContext( OpenApiDocument document, OpenApiOperationDescription operationDescription, Type controllerType, MethodInfo methodInfo, OpenApiDocumentGenerator swaggerGenerator, JsonSchemaGenerator schemaGenerator, JsonSchemaResolver schemaResolver, OpenApiDocumentGeneratorSettings settings, IList <OpenApiOperationDescription> allOperationDescriptions) : base(document, operationDescription, controllerType, methodInfo, swaggerGenerator, schemaGenerator, schemaResolver, settings, allOperationDescriptions) { }
private OpenApiParameter GetParameter(SchemaType schemaType) { var generatorSettings = new OpenApiDocumentGeneratorSettings { SchemaType = schemaType, ReflectionService = new DefaultReflectionService() }; var schemaResolver = new JsonSchemaResolver(new OpenApiDocument(), generatorSettings); var generator = new OpenApiDocumentGenerator(generatorSettings, schemaResolver); var methodInfo = typeof(TestController).GetMethod("HasArrayParameter"); return(generator.CreatePrimitiveParameter("foo", "bar", methodInfo.GetContextualParameters().First())); }
private List <Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo> > AddOperationDescriptionsToDocument( OpenApiDocument document, Type controllerType, List <Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo> > operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { var addedOperations = new List <Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo> >(); var allOperations = new List <OpenApiOperationDescription>(operations.Count); allOperations.AddRange(operations.Select(t => t.Item1)); foreach (var tuple in operations) { var operation = tuple.Item1; var apiDescription = tuple.Item2; var method = tuple.Item3; var addOperation = RunOperationProcessors( document, apiDescription, controllerType, method, operation, allOperations, swaggerGenerator, schemaResolver); if (addOperation) { var path = operation.Path.Replace("//", "/"); if (!document.Paths.TryGetValue(path, out var pathItem)) { document.Paths[path] = pathItem = new OpenApiPathItem(); } if (pathItem.ContainsKey(operation.Method)) { throw new InvalidOperationException($"The method '{operation.Method}' on path '{path}' is registered multiple times."); } pathItem[operation.Method] = operation.Operation; addedOperations.Add(tuple); } } return(addedOperations); }
public void Can_Correctly_Generate_A_Schema() { var generator = new OpenApiDocumentGenerator(); var doc = generator.GenerateOpenApiDocument(new ApiMetadata { Title = "Test Api", Description = "My Test Api", Version = "1.0.0.0", Url = "http://localhost:7071/api/" }, typeof(MyTestFunction).Assembly, new HashSet <Type> { typeof(ILogger), typeof(HttpRequest), typeof(HttpTriggerAttribute) }); using (var fs = File.OpenWrite(Path.Combine(Directory.GetCurrentDirectory(), "test_openapi.json"))) using (var sw = new StreamWriter(fs)) { doc.SerializeAsV2(new OpenApiJsonWriter(sw)); } // Assert.NotEmpty(funcs); // var func = funcs.Single(); // Assert.Equal("TestHttpTrigger", func.Name); // Assert.Equal(OperationType.Get, func.Methods.Single()); // Assert.Equal(5, func.Returns.Length); // Assert.Collection(func.Returns, new Action<ReturnsAttribute>[] { // a => { Assert.Equal(200, a.StatusCode); Assert.Equal("OK", a.Description); Assert.Equal(typeof(TestType), a.BodyType); }, // a => { Assert.Equal(400, a.StatusCode); Assert.Equal("Bad", a.Description); }, // a => { Assert.Equal(404, a.StatusCode); Assert.Equal("NotFound", a.Description); }, // a => { Assert.Equal(401, a.StatusCode); Assert.Equal("Unauthorized", a.Description); }, // a => { Assert.Equal(403, a.StatusCode); Assert.Equal("Forbidden", a.Description); }, // }); // Assert.Equal("/api/result/{sessionId}", func.Route); // Assert.Single(func.Parameters); // Assert.Collection(func.Parameters, new Action<ParameterInfo>[] { // p => { Assert.Equal("sessionId", p.Name); Assert.Equal(typeof(String), p.ParameterType); } // }); }
private bool GenerateForHandler(OpenApiDocument document, Type handlerType, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { var methodInfo = handlerType.GetMethod("Handle"); var isQuery = handlerType.IsAssignableToGenericType(typeof(IQueryRequestHandler <,>)); var httpMethod = isQuery ? OpenApiOperationMethod.Get : OpenApiOperationMethod.Post; var httpPath = $"/rpc/{GetRequestTypeFullName(methodInfo)}"; var operationDescription = new OpenApiOperationDescription { Path = httpPath, Method = httpMethod, Operation = new OpenApiOperation { IsDeprecated = methodInfo.GetCustomAttribute <ObsoleteAttribute>() != null, OperationId = GetOperationId(document, handlerType.Name) } }; return(AddOperationDescriptionsToDocumentAsync(document, handlerType, (operationDescription, methodInfo), swaggerGenerator, schemaResolver)); }
/// <summary>Initializes a new instance of the <see cref="OperationProcessorContext" /> class.</summary> /// <param name="document">The document.</param> /// <param name="operationDescription">The operation description.</param> /// <param name="controllerType">Type of the controller.</param> /// <param name="methodInfo">The method information.</param> /// <param name="openApiDocumentGenerator">The swagger generator.</param> /// <param name="schemaResolver">The schema resolver.</param> /// <param name="settings">The settings.</param> /// <param name="allOperationDescriptions">All operation descriptions.</param> /// <param name="schemaGenerator">The schema generator.</param> public OperationProcessorContext( OpenApiDocument document, OpenApiOperationDescription operationDescription, Type controllerType, MethodInfo methodInfo, OpenApiDocumentGenerator openApiDocumentGenerator, JsonSchemaGenerator schemaGenerator, JsonSchemaResolver schemaResolver, OpenApiDocumentGeneratorSettings settings, IList <OpenApiOperationDescription> allOperationDescriptions) { Document = document; OperationDescription = operationDescription; ControllerType = controllerType; MethodInfo = methodInfo; DocumentGenerator = openApiDocumentGenerator; SchemaGenerator = schemaGenerator; SchemaResolver = schemaResolver; Settings = settings; AllOperationDescriptions = allOperationDescriptions; }
private List <Type> GenerateApiGroups( OpenApiDocument document, IGrouping <Type, Tuple <ApiDescription, ActionDescriptor> >[] apiGroups, OpenApiSchemaResolver schemaResolver) { var usedControllerTypes = new List <Type>(); var swaggerGenerator = new OpenApiDocumentGenerator(Settings, schemaResolver); var allOperations = new List <Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo> >(); foreach (var apiGroup in apiGroups) { var controllerType = apiGroup.Key; var hasIgnoreAttribute = controllerType != null && controllerType .GetTypeInfo() .GetCustomAttributes() .GetAssignableToTypeName("SwaggerIgnoreAttribute", TypeNameStyle.Name) .Any(); if (!hasIgnoreAttribute) { var operations = new List <Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo> >(); foreach (var item in apiGroup) { var apiDescription = item.Item1; if (apiDescription.RelativePath == null) { continue; } var method = (item.Item2 as ControllerActionDescriptor)?.MethodInfo; if (method != null) { var actionHasIgnoreAttribute = method.GetCustomAttributes().GetAssignableToTypeName("SwaggerIgnoreAttribute", TypeNameStyle.Name).Any(); if (actionHasIgnoreAttribute) { continue; } } var path = apiDescription.RelativePath; if (!path.StartsWith("/", StringComparison.Ordinal)) { path = "/" + path; } var httpMethod = apiDescription.HttpMethod?.ToLowerInvariant() ?? (apiDescription.ParameterDescriptions.Where(p => { return(p.Source == Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource.Body); }).Count() > 0 ? OpenApiOperationMethod.Post : OpenApiOperationMethod.Get); var operationDescription = new OpenApiOperationDescription { Path = path, Method = httpMethod, Operation = new OpenApiOperation { IsDeprecated = IsOperationDeprecated(item.Item1, apiDescription.ActionDescriptor, method), OperationId = GetOperationId(document, apiDescription, method, httpMethod), Consumes = apiDescription.SupportedRequestFormats .Select(f => f.MediaType) .Distinct() .ToList(), Produces = apiDescription.SupportedResponseTypes .SelectMany(t => t.ApiResponseFormats.Select(f => f.MediaType)) .Distinct() .ToList() } }; operations.Add(new Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo>(operationDescription, apiDescription, method)); } var addedOperations = AddOperationDescriptionsToDocument(document, controllerType, operations, swaggerGenerator, schemaResolver); if (addedOperations.Any() && apiGroup.Key != null) { usedControllerTypes.Add(apiGroup.Key); } allOperations.AddRange(addedOperations); } } UpdateConsumesAndProduces(document, allOperations); return(usedControllerTypes); }
private async Task <bool> AddOperationDescriptionsToDocumentAsync(OpenApiDocument document, Type staticAzureFunctionClassType, List <Tuple <OpenApiOperationDescription, MethodInfo> > operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { var addedOperations = 0; var allOps = operations.Select(t => t.Item1).ToList(); foreach (var o in operations) { var operation = o.Item1; var method = o.Item2; var addOperation = await RunOperationProcessorsAsync(document, staticAzureFunctionClassType, method, operation, allOps, swaggerGenerator, schemaResolver); if (addOperation) { var path = operation.Path.Replace("//", "/"); if (!document.Paths.ContainsKey(path)) { document.Paths[path] = new OpenApiPathItem(); } if (document.Paths[path].ContainsKey(operation.Method)) { throw new InvalidOperationException("The method '" + operation.Method + "' on path '" + path + "' is registered multiple times"); } document.Paths[path][operation.Method] = operation.Operation; addedOperations++; } } return(addedOperations > 0); }
/// <exception cref="InvalidOperationException">The operation has more than one body parameter.</exception> private bool GenerateForController(OpenApiDocument document, Type controllerType, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { var hasIgnoreAttribute = controllerType.GetTypeInfo() .GetCustomAttributes() .GetAssignableToTypeName("SwaggerIgnoreAttribute", TypeNameStyle.Name) .Any(); if (hasIgnoreAttribute) { return(false); } var operations = new List <Tuple <OpenApiOperationDescription, MethodInfo> >(); var currentControllerType = controllerType; while (currentControllerType != null) { foreach (var method in GetActionMethods(currentControllerType)) { var httpPaths = GetHttpPaths(controllerType, method).ToList(); var httpMethods = GetSupportedHttpMethods(method).ToList(); foreach (var httpPath in httpPaths) { foreach (var httpMethod in httpMethods) { var isPathAlreadyDefinedInInheritanceHierarchy = operations.Any(o => o.Item1.Path == httpPath && o.Item1.Method == httpMethod && o.Item2.DeclaringType != currentControllerType && o.Item2.DeclaringType.IsAssignableToTypeName(currentControllerType.FullName, TypeNameStyle.FullName)); if (isPathAlreadyDefinedInInheritanceHierarchy == false) { var operationDescription = new OpenApiOperationDescription { Path = httpPath, Method = httpMethod, Operation = new OpenApiOperation { IsDeprecated = method.GetCustomAttribute <ObsoleteAttribute>() != null, OperationId = GetOperationId(document, controllerType.Name, method, httpMethod) } }; operations.Add(new Tuple <OpenApiOperationDescription, MethodInfo>(operationDescription, method)); } } } } currentControllerType = currentControllerType.GetTypeInfo().BaseType; } return(AddOperationDescriptionsToDocument(document, controllerType, operations, swaggerGenerator, schemaResolver)); }
private bool AddOperationDescriptionsToDocument(OpenApiDocument document, Type controllerType, List <Tuple <OpenApiOperationDescription, MethodInfo> > operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { var addedOperations = 0; var allOperation = operations.Select(t => t.Item1).ToList(); foreach (var tuple in operations) { var operation = tuple.Item1; var method = tuple.Item2; var addOperation = RunOperationProcessors(document, controllerType, method, operation, allOperation, swaggerGenerator, schemaResolver); if (addOperation) { var path = operation.Path.Replace("//", "/"); // iiQ Custom // .. Prevents "The method 'get' on path '/v1.0' is registered multiple times" exception // .. Also guards for root pathed URLs such as "/v1.0" since all URLs are versioned in iiQ if (path.Split(new char[] { '/' }).Length <= 2) { continue; } if (!document.Paths.ContainsKey(path)) { document.Paths[path] = new OpenApiPathItem(); } if (document.Paths[path].ContainsKey(operation.Method)) { throw new InvalidOperationException("The method '" + operation.Method + "' on path '" + path + "' is registered multiple times " + "(check the DefaultUrlTemplate setting [default for Web API: 'api/{controller}/{id}'; for MVC projects: '{controller}/{action}/{id?}'])."); } document.Paths[path][operation.Method] = operation.Operation; addedOperations++; } } return(addedOperations > 0); }
private List <Type> GenerateForControllers( OpenApiDocument document, IGrouping <Type, Tuple <ApiDescription, ControllerActionDescriptor> >[] apiGroups, OpenApiSchemaResolver schemaResolver) { var usedControllerTypes = new List <Type>(); var swaggerGenerator = new OpenApiDocumentGenerator(Settings, schemaResolver); var allOperations = new List <Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo> >(); foreach (var controllerApiDescriptionGroup in apiGroups) { var controllerType = controllerApiDescriptionGroup.Key; var hasIgnoreAttribute = controllerType.GetTypeInfo() .GetCustomAttributes() .GetAssignableToTypeName("SwaggerIgnoreAttribute", TypeNameStyle.Name) .Any(); if (!hasIgnoreAttribute) { var operations = new List <Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo> >(); foreach (var item in controllerApiDescriptionGroup) { var apiDescription = item.Item1; var method = item.Item2.MethodInfo; var actionHasIgnoreAttribute = method.GetCustomAttributes().GetAssignableToTypeName("SwaggerIgnoreAttribute", TypeNameStyle.Name).Any(); if (actionHasIgnoreAttribute) { continue; } var path = apiDescription.RelativePath; if (!path.StartsWith("/", StringComparison.Ordinal)) { path = "/" + path; } var controllerActionDescriptor = (ControllerActionDescriptor)apiDescription.ActionDescriptor; var httpMethod = apiDescription.HttpMethod?.ToLowerInvariant() ?? OpenApiOperationMethod.Get; var operationDescription = new OpenApiOperationDescription { Path = path, Method = httpMethod, Operation = new OpenApiOperation { IsDeprecated = method.GetCustomAttribute <ObsoleteAttribute>() != null, OperationId = GetOperationId(document, controllerActionDescriptor, method), Consumes = apiDescription.SupportedRequestFormats .Select(f => f.MediaType) .Distinct() .ToList(), Produces = apiDescription.SupportedResponseTypes .SelectMany(t => t.ApiResponseFormats.Select(f => f.MediaType)) .Distinct() .ToList() } }; operations.Add(new Tuple <OpenApiOperationDescription, ApiDescription, MethodInfo>(operationDescription, apiDescription, method)); } var addedOperations = AddOperationDescriptionsToDocument(document, controllerType, operations, swaggerGenerator, schemaResolver); if (addedOperations.Any()) { usedControllerTypes.Add(controllerApiDescriptionGroup.Key); } allOperations.AddRange(addedOperations); } } UpdateConsumesAndProduces(document, allOperations); return(usedControllerTypes); }
private bool RunOperationProcessors(OpenApiDocument document, ApiDescription apiDescription, Type controllerType, MethodInfo methodInfo, OpenApiOperationDescription operationDescription, List <OpenApiOperationDescription> allOperations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { // 1. Run from settings var operationProcessorContext = new AspNetCoreOperationProcessorContext(document, operationDescription, controllerType, methodInfo, swaggerGenerator, Settings.SchemaGenerator, schemaResolver, Settings, allOperations) { ApiDescription = apiDescription, }; foreach (var operationProcessor in Settings.OperationProcessors) { if (operationProcessor.Process(operationProcessorContext) == false) { return(false); } } // 2. Run from class attributes var operationProcessorAttribute = methodInfo.DeclaringType.GetTypeInfo() .GetCustomAttributes() // 3. Run from method attributes .Concat(methodInfo.GetCustomAttributes()) .Where(a => a.GetType().IsAssignableToTypeName("SwaggerOperationProcessorAttribute", TypeNameStyle.Name)); foreach (dynamic attribute in operationProcessorAttribute) { var operationProcessor = ObjectExtensions.HasProperty(attribute, "Parameters") ? (IOperationProcessor)Activator.CreateInstance(attribute.Type, attribute.Parameters) : (IOperationProcessor)Activator.CreateInstance(attribute.Type); if (operationProcessor.Process(operationProcessorContext) == false) { return(false); } } return(true); }
private bool AddOperationDescriptionsToDocument(OpenApiDocument document, Type controllerType, List <Tuple <OpenApiOperationDescription, MethodInfo> > operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) { var addedOperations = 0; var allOperation = operations.Select(t => t.Item1).ToList(); foreach (var tuple in operations) { var operation = tuple.Item1; var method = tuple.Item2; var addOperation = RunOperationProcessors(document, controllerType, method, operation, allOperation, swaggerGenerator, schemaResolver); if (addOperation) { var path = operation.Path.Replace("//", "/"); if (!document.Paths.ContainsKey(path)) { document.Paths[path] = new OpenApiPathItem(); } if (document.Paths[path].ContainsKey(operation.Method)) { throw new InvalidOperationException("The method '" + operation.Method + "' on path '" + path + "' is registered multiple times " + "(check the DefaultUrlTemplate setting [default for Web API: 'api/{controller}/{id}'; for MVC projects: '{controller}/{action}/{id?}'])."); } document.Paths[path][operation.Method] = operation.Operation; addedOperations++; } } return(addedOperations > 0); }