private static void FilterDocument(OpenApiCompilerConfiguration documentFilters, OpenApiDocument document)
        {
            var documentFilterContext = new OpenApiDocumentFilterContext();

            foreach (var documentFilter in documentFilters.DocumentFilters)
            {
                documentFilter.Apply(document, documentFilterContext);
            }
        }
        private static void CreateOperationsFromRoutes(
            HttpFunctionDefinition[] functionDefinitions,
            OpenApiDocument openApiDocument,
            SchemaReferenceRegistry registry,
            string apiPrefix,
            OpenApiCompilerConfiguration compilerConfiguration)
        {
            string prependedApiPrefix = string.IsNullOrEmpty(apiPrefix) ? $"" : $"/{apiPrefix}";
            var    operationsByRoute  = functionDefinitions.Where(x => x.Route != null).GroupBy(x => $"{prependedApiPrefix}/{x.Route}");

            foreach (IGrouping <string, HttpFunctionDefinition> route in operationsByRoute)
            {
                OpenApiPathItem pathItem = new OpenApiPathItem()
                {
                    Operations = new Dictionary <OperationType, OpenApiOperation>()
                };

                foreach (HttpFunctionDefinition functionByRoute in route)
                {
                    Type commandType = functionByRoute.CommandType;
                    foreach (HttpMethod method in functionByRoute.Verbs)
                    {
                        OpenApiOperation operation = new OpenApiOperation
                        {
                            Description = functionByRoute.OpenApiDescription,
                            Summary     = functionByRoute.OpenApiSummary,
                            Responses   = new OpenApiResponses(),
                            Tags        = string.IsNullOrWhiteSpace(functionByRoute.RouteConfiguration.OpenApiName) ? null : new List <OpenApiTag>()
                            {
                                new OpenApiTag {
                                    Name = functionByRoute.RouteConfiguration.OpenApiName
                                }
                            }
                        };

                        var operationFilterContext = new OpenApiOperationFilterContext
                        {
                            CommandType   = commandType,
                            PropertyNames = new Dictionary <string, string>()
                        };

                        foreach (KeyValuePair <int, OpenApiResponseConfiguration> kvp in functionByRoute.OpenApiResponseConfigurations)
                        {
                            operation.Responses.Add(kvp.Key.ToString(), new OpenApiResponse
                            {
                                Description = kvp.Value.Description,
                                Content     =
                                {
                                    ["application/json"] = new OpenApiMediaType()
                                    {
                                    Schema = kvp.Value.ResponseType == null ? null : registry.FindOrAddReference(kvp.Value.ResponseType)
                                    }
                                }
                            });
                        }

                        // Does any HTTP success response (2xx) exist
                        if (operation.Responses.Keys.FirstOrDefault(x => x.StartsWith("2")) == null)
                        {
                            OpenApiResponse response = new OpenApiResponse
                            {
                                Description = "Successful API operation"
                            };
                            if (functionByRoute.CommandResultType != null)
                            {
                                OpenApiSchema schema = registry.FindOrAddReference(functionByRoute.CommandResultType);
                                response.Content = new Dictionary <string, OpenApiMediaType>
                                {
                                    { "application/json", new OpenApiMediaType {
                                          Schema = schema
                                      } }
                                };
                            }
                            operation.Responses.Add("200", response);
                        }

                        if (method == HttpMethod.Get || method == HttpMethod.Delete)
                        {
                            var schema = registry.GetOrCreateSchema(commandType);
                            foreach (HttpParameter property in functionByRoute.QueryParameters)
                            {
                                var propertyInfo = commandType.GetProperty(property.Name);

                                // Property Name
                                var propertyName = propertyInfo.GetAttributeValue((JsonPropertyAttribute attribute) => attribute.PropertyName);
                                if (string.IsNullOrWhiteSpace(propertyName))
                                {
                                    propertyName = propertyInfo.GetAttributeValue((DataMemberAttribute attribute) => attribute.Name);
                                }
                                if (string.IsNullOrWhiteSpace(propertyName))
                                {
                                    propertyName = propertyInfo.Name.ToCamelCase();
                                }

                                // Property Required
                                var propertyRequired = !property.IsOptional;
                                if (!propertyRequired)
                                {
                                    propertyRequired = propertyInfo.GetAttributeValue((JsonPropertyAttribute attribute) => attribute.Required) == Required.Always;
                                }
                                if (!propertyRequired)
                                {
                                    propertyRequired = propertyInfo.GetAttributeValue((RequiredAttribute attribute) => attribute) != null;
                                }

                                var propertySchema = schema.Properties[propertyName];

                                var parameter = new OpenApiParameter
                                {
                                    Name        = propertyName,
                                    In          = ParameterLocation.Query,
                                    Required    = propertyRequired,
                                    Schema      = propertySchema, // property.Type.MapToOpenApiSchema(),
                                    Description = propertySchema.Description
                                };

                                FilterParameter(compilerConfiguration.ParameterFilters, parameter);

                                operation.Parameters.Add(parameter);
                                operationFilterContext.PropertyNames[parameter.Name] = propertyInfo.Name;
                            }
                        }

                        if (functionByRoute.Authorization == AuthorizationTypeEnum.Function && (method == HttpMethod.Get || method == HttpMethod.Delete))
                        {
                            operation.Parameters.Add(new OpenApiParameter
                            {
                                Name        = "code",
                                In          = ParameterLocation.Query,
                                Required    = true,
                                Schema      = typeof(string).MapToOpenApiSchema(),
                                Description = ""
                            });
                        }

                        foreach (HttpParameter property in functionByRoute.RouteParameters)
                        {
                            var parameter = new OpenApiParameter
                            {
                                Name        = property.RouteName.ToCamelCase(),
                                In          = ParameterLocation.Path,
                                Required    = !property.IsOptional,
                                Schema      = property.Type.MapToOpenApiSchema(),
                                Description = ""
                            };

                            FilterParameter(compilerConfiguration.ParameterFilters, parameter);

                            operation.Parameters.Add(parameter);
                            // TODO: We need to consider what to do with the payload model here - if its a route parameter
                            // we need to ignore it in the payload model
                        }

                        if (method == HttpMethod.Post || method == HttpMethod.Put || method == HttpMethod.Patch)
                        {
                            OpenApiRequestBody requestBody = new OpenApiRequestBody();
                            OpenApiSchema      schema      = registry.FindReference(commandType);
                            requestBody.Content = new Dictionary <string, OpenApiMediaType>
                            {
                                { "application/json", new OpenApiMediaType {
                                      Schema = schema
                                  } }
                            };
                            operation.RequestBody = requestBody;
                        }

                        FilterOperation(compilerConfiguration.OperationFilters, operation, operationFilterContext);

                        pathItem.Operations.Add(MethodToOperationMap[method], operation);
                    }
                }

                openApiDocument.Paths.Add(route.Key, pathItem);
            }
        }
        public OpenApiOutputModel Compile(OpenApiConfiguration configuration, IReadOnlyCollection <AbstractFunctionDefinition> abstractFunctionDefinitions, string outputBinaryFolder)
        {
            if (configuration == null)
            {
                return(null);
            }

            string apiPrefix = GetApiPrefix(outputBinaryFolder);

            if (!configuration.IsValid)
            {
                throw new ConfigurationException("Open API implementation is partially complete, a title and a version must be specified");
            }
            if (!configuration.IsOpenApiOutputEnabled)
            {
                return(null);
            }

            HttpFunctionDefinition[] functionDefinitions = abstractFunctionDefinitions.OfType <HttpFunctionDefinition>().ToArray();
            if (functionDefinitions.Length == 0)
            {
                return(null);
            }

            OpenApiDocument openApiDocument = new OpenApiDocument
            {
                Info = new OpenApiInfo
                {
                    Version = configuration.Version,
                    Title   = configuration.Title
                },
                Servers = configuration.Servers?.Select(x => new OpenApiServer {
                    Url = x
                }).ToArray(),
                Paths      = new OpenApiPaths(),
                Components = new OpenApiComponents
                {
                    Schemas = new Dictionary <string, OpenApiSchema>()
                }
            };

            var compilerConfiguration = new OpenApiCompilerConfiguration(configuration);

            SchemaReferenceRegistry registry = new SchemaReferenceRegistry(compilerConfiguration);

            CreateTags(functionDefinitions, openApiDocument);

            CreateSchemas(functionDefinitions, openApiDocument, registry);

            CreateOperationsFromRoutes(functionDefinitions, openApiDocument, registry, apiPrefix, compilerConfiguration);

            FilterDocument(compilerConfiguration, openApiDocument);

            if (openApiDocument.Paths.Count == 0)
            {
                return(null);
            }

            string             yaml   = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Yaml);
            OpenApiOutputModel result = new OpenApiOutputModel
            {
                OpenApiSpecification = new OpenApiFileReference
                {
                    Content  = yaml,
                    Filename = "openapi.yaml"
                }
            };

            if (!string.IsNullOrWhiteSpace(configuration.UserInterfaceRoute))
            {
                result.SwaggerUserInterface = CopySwaggerUserInterfaceFilesToWebFolder();
            }

            if (!string.IsNullOrWhiteSpace(configuration.OutputPath))
            {
                if (Directory.Exists(configuration.OutputPath))
                {
                    string pathAndFilename = Path.Combine(configuration.OutputPath, "openapi.yaml");
                    if (File.Exists(pathAndFilename))
                    {
                        File.Delete(pathAndFilename);
                    }
                    File.WriteAllText(pathAndFilename, yaml, Encoding.UTF8);
                }
            }

            return(result);
        }
Example #4
0
        public OpenApiOutputModel Compile(OpenApiConfiguration configuration, IReadOnlyCollection <AbstractFunctionDefinition> abstractFunctionDefinitions, string outputBinaryFolder)
        {
            if (configuration == null)
            {
                return(null);
            }

            string apiPrefix = GetApiPrefix(outputBinaryFolder);

            if (!configuration.IsValid)
            {
                throw new ConfigurationException("Open API implementation is partially complete, a title and a version must be specified");
            }
            if (!configuration.IsOpenApiOutputEnabled)
            {
                return(null);
            }

            var functionDefinitions = abstractFunctionDefinitions.OfType <HttpFunctionDefinition>().ToList();

            if (functionDefinitions.Count() == 0)
            {
                return(null);
            }

            IDictionary <string, OpenApiDocumentsSpec> openApiDocumentsSpec = new Dictionary <string, OpenApiDocumentsSpec>();
            IDictionary <string, OpenApiDocumentsSpec> reDocDocumentsSpec   = new Dictionary <string, OpenApiDocumentsSpec>();
            OpenApiOutputModel outputModel = new OpenApiOutputModel();

            foreach (var keyValuePair in configuration.OpenApiDocumentInfos)
            {
                OpenApiDocument openApiDocument = new OpenApiDocument
                {
                    Info    = keyValuePair.Value.OpenApiInfo,
                    Servers = configuration.Servers?.Select(x => new OpenApiServer {
                        Url = x
                    }).ToArray(),
                    Paths      = new OpenApiPaths(),
                    Components = new OpenApiComponents
                    {
                        Schemas = new Dictionary <string, OpenApiSchema>(),
                    }
                };

                var functionFilter = keyValuePair.Value.HttpFunctionFilter;
                if (functionFilter == null)
                {
                    functionFilter = new OpenApiHttpFunctionFilterDummy();
                }

                var compilerConfiguration = new OpenApiCompilerConfiguration(configuration);

                SchemaReferenceRegistry registry = new SchemaReferenceRegistry(compilerConfiguration);

                CreateTags(functionDefinitions, functionFilter, openApiDocument);

                CreateSchemas(functionDefinitions, functionFilter, openApiDocument, registry);

                CreateOperationsFromRoutes(functionDefinitions, functionFilter, openApiDocument, registry, apiPrefix, compilerConfiguration);

                CreateSecuritySchemes(openApiDocument, configuration);

                FilterDocument(compilerConfiguration.DocumentFilters, openApiDocument, keyValuePair.Value.DocumentRoute);

                if (openApiDocument.Paths.Count == 0)
                {
                    continue;
                }

                // TODO: FIXME:
                // Hack: Empty OpenApiSecurityRequirement lists are not serialized by the standard Microsoft
                // implementation. Therefore we add a null object to the list and fix it here by hand.
                var yaml = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Yaml);
                yaml = Regex.Replace(yaml, $"security:\n.*?- \n", "security: []\n");

                outputModel.OpenApiFileReferences.Add(
                    new OpenApiFileReference
                {
                    Filename = $"OpenApi.{keyValuePair.Value.DocumentRoute.Replace('/', '.')}",
                    Content  = Encoding.UTF8.GetBytes(yaml)
                }
                    );

                openApiDocumentsSpec.Add(openApiDocument.Info.Title, new OpenApiDocumentsSpec
                {
                    Title    = keyValuePair.Value.OpenApiInfo.Title,
                    Selected = keyValuePair.Value.Selected,
                    Path     = $"/{configuration.UserInterfaceRoute ?? "openapi"}/{keyValuePair.Value.DocumentRoute}"
                });

                // Create reDoc YAML
                if (!string.IsNullOrWhiteSpace(configuration.ReDocUserInterfaceRoute))
                {
                    FilterDocument(compilerConfiguration.ReDocDocumentFilters, openApiDocument, keyValuePair.Value.DocumentRoute);

                    // TODO: FIXME:
                    // Hack: Empty OpenApiSecurityRequirement lists are not serialized by the standard Microsoft
                    // implementation. Therefore we add a null object to the list and fix it here by hand.
                    var reDocYaml = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Yaml);
                    reDocYaml = Regex.Replace(reDocYaml, $"security:\n.*?- \n", "security: []\n");

                    outputModel.OpenApiFileReferences.Add(
                        new OpenApiFileReference
                    {
                        Filename = $"ReDoc.{keyValuePair.Value.DocumentRoute.Replace('/', '.')}",
                        Content  = Encoding.UTF8.GetBytes(reDocYaml)
                    }
                        );

                    reDocDocumentsSpec.Add(openApiDocument.Info.Title, new OpenApiDocumentsSpec
                    {
                        Title    = keyValuePair.Value.OpenApiInfo.Title,
                        Selected = keyValuePair.Value.Selected,
                        Path     = $"/{configuration.ReDocUserInterfaceRoute ?? "redoc"}/{keyValuePair.Value.DocumentRoute}"
                    });
                }
            }

            if (!string.IsNullOrWhiteSpace(configuration.UserInterfaceRoute))
            {
                outputModel.OpenApiFileReferences.Add(
                    new OpenApiFileReference
                {
                    Filename = "OpenApi.openapi-documents-spec.json",
                    Content  = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(openApiDocumentsSpec.Values.ToArray()))
                }
                    );

                CopySwaggerUserInterfaceFilesToWebFolder(configuration, outputModel.OpenApiFileReferences);
            }

            if (!string.IsNullOrWhiteSpace(configuration.ReDocUserInterfaceRoute))
            {
                outputModel.OpenApiFileReferences.Add(
                    new OpenApiFileReference
                {
                    Filename = "ReDoc.redoc-documents-spec.json",
                    Content  = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(reDocDocumentsSpec.Values.ToArray()))
                }
                    );

                CopyReDocUserInterfaceFilesToWebFolder(configuration, outputModel.OpenApiFileReferences);
            }

            outputModel.UserInterfaceRoute      = configuration.UserInterfaceRoute;
            outputModel.ReDocUserInterfaceRoute = configuration.ReDocUserInterfaceRoute;
            return(outputModel);
        }