예제 #1
0
        private void CreateSchemas(IList <HttpFunctionDefinition> functionDefinitions, IOpenApiHttpFunctionFilter functionFilter, OpenApiDocument openApiDocument, SchemaReferenceRegistry registry)
        {
            IOpenApiHttpFunctionFilterContext functionFilterContext = new OpenApiHttpFunctionFilterContext();

            foreach (HttpFunctionDefinition functionDefinition in functionDefinitions)
            {
                if (functionDefinition.OpenApiIgnore)
                {
                    continue;
                }

                var filterdVerbs = new HashSet <HttpMethod>(functionDefinition.Verbs);
                functionFilter.Apply(functionDefinition.RouteConfiguration.Route, filterdVerbs, functionFilterContext);
                if (filterdVerbs.Count == 0)
                {
                    continue;
                }

                if (filterdVerbs.Contains(HttpMethod.Patch) ||
                    filterdVerbs.Contains(HttpMethod.Post) ||
                    filterdVerbs.Contains(HttpMethod.Put))
                {
                    registry.FindOrAddReference(functionDefinition.CommandType);
                }
                if (functionDefinition.CommandResultType != null &&
                    functionDefinition.CommandResultType != typeof(IActionResult))
                {
                    registry.FindOrAddReference(functionDefinition.CommandResultType);
                }
            }

            openApiDocument.Components.Schemas = registry.References;
        }
예제 #2
0
        /// <summary>
        /// Generates schema for the provided list of types and store them in the provided dictionary.
        /// </summary>
        /// <param name="allListedTypes">The listed types to fetch schema for.</param>
        /// <param name="crefSchemaMap">The cref to <see cref="InternalSchemaGenerationInfo"/> map.</param>
        /// <param name="typeFetcher">The type fetcher used to fetch type information using reflection.</param>
        /// <param name="schemaReferenceRegistry"><see cref="SchemaReferenceRegistry"/>.</param>
        private void BuildCrefSchemaMap(
            IList <string> allListedTypes,
            Dictionary <string, InternalSchemaGenerationInfo> crefSchemaMap,
            TypeFetcher typeFetcher,
            SchemaReferenceRegistry schemaReferenceRegistry)
        {
            var key = allListedTypes.ToCrefKey();

            var schemaInfo = new InternalSchemaGenerationInfo();

            try
            {
                var type   = typeFetcher.LoadTypeFromCrefValues(allListedTypes);
                var schema = schemaReferenceRegistry.FindOrAddReference(type);
                schemaInfo.Schema = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0);
            }
            catch (Exception e)
            {
                var error = new GenerationError
                {
                    ExceptionType = e.GetType().Name,
                    Message       = e.Message
                };

                schemaInfo.Error = error;
            }

            if (!crefSchemaMap.ContainsKey(key))
            {
                crefSchemaMap.Add(key, schemaInfo);
            }
        }
예제 #3
0
        private void CreateSchemas(HttpFunctionDefinition[] functionDefinitions, OpenApiDocument openApiDocument, SchemaReferenceRegistry registry)
        {
            foreach (HttpFunctionDefinition functionDefinition in functionDefinitions)
            {
                if (functionDefinition.Verbs.Contains(HttpMethod.Patch) ||
                    functionDefinition.Verbs.Contains(HttpMethod.Post) ||
                    functionDefinition.Verbs.Contains(HttpMethod.Put))
                {
                    registry.FindOrAddReference(functionDefinition.CommandType);
                }
                if (functionDefinition.CommandResultType != null &&
                    functionDefinition.CommandResultType != typeof(IActionResult))
                {
                    registry.FindOrAddReference(functionDefinition.CommandResultType);
                }
            }

            if (registry.References.Any())
            {
                openApiDocument.Components.Schemas = registry.References;
            }
        }
예제 #4
0
        /// <summary>
        /// Processes the "header" tag child elements of the provide XElement
        /// and generates a map of string to OpenApiHeader.
        /// </summary>
        /// <param name="xElement">The XElement to process.</param>
        /// <param name="typeFetcher">The type fetcher.</param>
        /// <param name="schemaReferenceRegistry">The schema reference registry.</param>
        /// <returns>The map of string to OpenApiHeader.</returns>
        internal static Dictionary <string, OpenApiHeader> ToOpenApiHeaders(
            this XElement xElement,
            TypeFetcher typeFetcher,
            SchemaReferenceRegistry schemaReferenceRegistry)
        {
            var headerElements = xElement.Elements()
                                 .Where(
                p => p.Name == KnownXmlStrings.Header)
                                 .ToList();

            var openApiHeaders = new Dictionary <string, OpenApiHeader>();

            foreach (var headerElement in headerElements)
            {
                var name = headerElement.Attribute(KnownXmlStrings.Name)?.Value.Trim();
                if (string.IsNullOrWhiteSpace(name))
                {
                    throw new InvalidHeaderException(
                              string.Format(SpecificationGenerationMessages.UndocumentedName, "header"));
                }

                var description = headerElement
                                  .Elements()
                                  .FirstOrDefault(p => p.Name == KnownXmlStrings.Description)?.Value.Trim().RemoveBlankLines();

                var listedTypes = headerElement.GetListedTypes();
                var type        = typeFetcher.LoadTypeFromCrefValues(listedTypes);

                var schema = schemaReferenceRegistry.FindOrAddReference(type);
                openApiHeaders.Add(
                    name,
                    new OpenApiHeader
                {
                    Description = description,
                    Schema      = schema
                });
            }

            return(openApiHeaders);
        }
예제 #5
0
        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);
            }
        }
예제 #6
0
        /// <summary>
        /// Fetches the value of "response" tags from xml documentation and populates operation's response.
        /// </summary>
        /// <param name="operation">The operation to be updated.</param>
        /// <param name="element">The xml element representing an operation in the annotation xml.</param>
        /// <param name="settings">The operation filter settings.</param>
        /// <remarks>
        /// Care should be taken to not overwrite the existing value in Operation if already present.
        /// This guarantees the predictable behavior that the first tag in the XML will be respected.
        /// It also guarantees that common annotations in the config file do not overwrite the
        /// annotations in the main documentation.
        /// </remarks>
        public void Apply(OpenApiOperation operation, XElement element, OperationFilterSettings settings)
        {
            var responseElements = element.Elements()
                                   .Where(
                p => p.Name == KnownXmlStrings.Response ||
                p.Name == KnownXmlStrings.ResponseType);

            SchemaReferenceRegistry schemaReferenceRegistry = settings.ReferenceRegistryManager.SchemaReferenceRegistry;

            foreach (var responseElement in responseElements)
            {
                var code = responseElement.Attribute(KnownXmlStrings.Code)?.Value;

                if (string.IsNullOrWhiteSpace(code))
                {
                    // Most APIs only document responses for a successful operation, so if code is not specified,
                    // we will assume it is for a successful operation. This also allows us to comply with OpenAPI spec:
                    //     The Responses Object MUST contain at least one response code,
                    //     and it SHOULD be the response for a successful operation call.
                    code = "200";
                }

                var mediaType = responseElement.Attribute(KnownXmlStrings.Type)?.Value ?? "application/json";

                var description = responseElement.GetDescriptionTextFromLastTextNode();

                var type           = typeof(string);
                var allListedTypes = responseElement.GetListedTypes();

                var responseContractType = settings.TypeFetcher.LoadTypeFromCrefValues(allListedTypes);

                OpenApiSchema schema = null;
                if (responseContractType != null)
                {
                    schema = schemaReferenceRegistry.FindOrAddReference(responseContractType);
                }

                var examples = responseElement.ToOpenApiExamples(settings.TypeFetcher);
                var headers  = responseElement.ToOpenApiHeaders(
                    settings.TypeFetcher,
                    settings.ReferenceRegistryManager.SchemaReferenceRegistry);

                if (schema != null)
                {
                    if (examples.Count > 0)
                    {
                        var firstExample = examples.First().Value?.Value;

                        if (firstExample != null)
                        {
                            if (schema.Reference != null)
                            {
                                var key = schemaReferenceRegistry.GetKey(responseContractType);

                                if (schemaReferenceRegistry.References.ContainsKey(key))
                                {
                                    settings.ReferenceRegistryManager.SchemaReferenceRegistry.References[key].Example
                                        = firstExample;
                                }
                            }
                            else
                            {
                                schema.Example = firstExample;
                            }
                        }
                    }
                }

                if (operation.Responses.ContainsKey(code))
                {
                    if (string.IsNullOrWhiteSpace(operation.Responses[code].Description))
                    {
                        operation.Responses[code].Description = description.RemoveBlankLines();
                    }

                    if (schema != null)
                    {
                        if (!operation.Responses[code].Content.ContainsKey(mediaType))
                        {
                            operation.Responses[code].Content[mediaType] = new OpenApiMediaType
                            {
                                Schema = schema
                            };
                        }
                        else
                        {
                            // If the existing schema is just a single schema (not a list of AnyOf), then
                            // we create a new schema and add that schema to AnyOf to allow us to add
                            // more schemas to it later.
                            if (!operation.Responses[code].Content[mediaType].Schema.AnyOf.Any())
                            {
                                var existingSchema = operation.Responses[code].Content[mediaType].Schema;
                                var newSchema      = new OpenApiSchema();

                                newSchema.AnyOf.Add(existingSchema);

                                operation.Responses[code].Content[mediaType].Schema = newSchema;
                            }

                            operation.Responses[code].Content[mediaType].Schema.AnyOf.Add(schema);
                        }
                    }
                }
                else
                {
                    var response = new OpenApiResponse
                    {
                        Description = description.RemoveBlankLines(),
                    };

                    if (schema != null)
                    {
                        response.Content[mediaType] = new OpenApiMediaType {
                            Schema = schema
                        };
                    }

                    if (headers.Any())
                    {
                        response.Headers = headers;
                    }

                    operation.Responses.Add(code, response);
                }

                if (examples.Count > 0)
                {
                    if (operation.Responses[code].Content[mediaType].Examples.Any())
                    {
                        examples.CopyInto(operation.Responses[code].Content[mediaType].Examples);
                    }
                    else
                    {
                        operation.Responses[code].Content[mediaType].Examples = examples;
                    }
                }
            }

            if (!operation.Responses.Any())
            {
                operation.Responses.Add(
                    "default",
                    new OpenApiResponse {
                    Description = "Responses cannot be located for this operation."
                });
            }
        }
        /// <summary>
        /// Fetches the value of "param" tags from xml documentation and populates operation's parameters values.
        /// </summary>
        /// <param name="operation">The operation to be updated.</param>
        /// <param name="element">The xml element representing an operation in the annotation xml.</param>
        /// <param name="settings">The operation filter settings.</param>
        /// <remarks>
        /// Care should be taken to not overwrite the existing value in Operation if already present.
        /// This guarantees the predictable behavior that the first tag in the XML will be respected.
        /// It also guarantees that common annotations in the config file do not overwrite the
        /// annotations in the main documentation.
        /// </remarks>
        public void Apply(OpenApiOperation operation, XElement element, OperationFilterSettings settings)
        {
            var paramElements = element.Elements()
                                .Where(
                p => p.Name == KnownXmlStrings.Param)
                                .ToList();

            // Query paramElements again to get all the parameter elements that have "in" attribute.
            // This will include those whose "in" attribute were just populated in PopulateInAttributeFilter, but exclude
            // those that have "in" attribute being "body" since they will be handled as a request body.
            var paramElementsWithIn = paramElements.Where(
                p =>
                KnownXmlStrings.InValuesTranslatableToParameter.Contains(
                    p.Attribute(KnownXmlStrings.In)?.Value))
                                      .ToList();

            SchemaReferenceRegistry schemaReferenceRegistry
                = settings.ReferenceRegistryManager.SchemaReferenceRegistry;

            foreach (var paramElement in paramElementsWithIn)
            {
                var inValue = paramElement.Attribute(KnownXmlStrings.In)?.Value.Trim();
                var name    = paramElement.Attribute(KnownXmlStrings.Name)?.Value.Trim();

                if (inValue == KnownXmlStrings.Path &&
                    !settings.Path.Contains($"{{{name}}}", StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }

                var isRequired = paramElement.Attribute(KnownXmlStrings.Required)?.Value.Trim();
                var cref       = paramElement.Attribute(KnownXmlStrings.Cref)?.Value.Trim();

                var description = paramElement.GetDescriptionTextFromLastTextNode();

                var type           = typeof(string);
                var allListedTypes = paramElement.GetListedTypes();

                if (allListedTypes.Any())
                {
                    type = settings.TypeFetcher.LoadTypeFromCrefValues(allListedTypes);
                }

                var schema            = schemaReferenceRegistry.FindOrAddReference(type);
                var parameterLocation = GetParameterKind(inValue);

                var examples = paramElement.ToOpenApiExamples(settings.TypeFetcher);

                var openApiParameter = new OpenApiParameter
                {
                    Name        = name,
                    In          = parameterLocation,
                    Description = description,
                    Required    = parameterLocation == ParameterLocation.Path || Convert.ToBoolean(isRequired),
                    Schema      = schema
                };

                if (examples.Count > 0)
                {
                    var firstExample = examples.First().Value?.Value;

                    if (firstExample != null)
                    {
                        if (openApiParameter.Schema.Reference != null)
                        {
                            var key = schemaReferenceRegistry.GetKey(type);

                            if (schemaReferenceRegistry.References.ContainsKey(key))
                            {
                                schemaReferenceRegistry.References[key].Example = firstExample;
                            }
                        }
                        else
                        {
                            openApiParameter.Schema.Example = firstExample;
                        }
                    }

                    openApiParameter.Examples = examples;
                }

                operation.Parameters.Add(openApiParameter);
            }
        }
        /// <summary>
        /// Fetches the value of "param" tags from xml documentation with in valus of "body"
        /// and populates operation's request body.
        /// </summary>
        /// <param name="operation">The operation to be updated.</param>
        /// <param name="element">The xml element representing an operation in the annotation xml.</param>
        /// <param name="settings">The operation filter settings.</param>
        /// <remarks>
        /// Care should be taken to not overwrite the existing value in Operation if already present.
        /// This guarantees the predictable behavior that the first tag in the XML will be respected.
        /// It also guarantees that common annotations in the config file do not overwrite the
        /// annotations in the main documentation.
        /// </remarks>
        public void Apply(OpenApiOperation operation, XElement element, OperationFilterSettings settings)
        {
            var bodyElements = element.Elements()
                               .Where(
                p => p.Name == KnownXmlStrings.Param &&
                p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Body)
                               .ToList();

            SchemaReferenceRegistry schemaReferenceRegistry = settings.ReferenceRegistryManager.SchemaReferenceRegistry;

            foreach (var bodyElement in bodyElements)
            {
                var name      = bodyElement.Attribute(KnownXmlStrings.Name)?.Value.Trim();
                var mediaType = bodyElement.Attribute(KnownXmlStrings.Type)?.Value ?? "application/json";

                var description = bodyElement.GetDescriptionTextFromLastTextNode();

                var allListedTypes = bodyElement.GetListedTypes();

                if (!allListedTypes.Any())
                {
                    throw new InvalidRequestBodyException(
                              string.Format(SpecificationGenerationMessages.MissingSeeCrefTag, name));
                }

                var type   = settings.TypeFetcher.LoadTypeFromCrefValues(allListedTypes);
                var schema = schemaReferenceRegistry.FindOrAddReference(type);

                var examples = bodyElement.ToOpenApiExamples(settings.TypeFetcher);

                if (examples.Count > 0)
                {
                    var firstExample = examples.First().Value?.Value;

                    if (firstExample != null)
                    {
                        // In case a schema is a reference, find that schmea object in schema registry
                        // and update the example.
                        if (schema.Reference != null)
                        {
                            var key = schemaReferenceRegistry.GetKey(type);

                            if (schemaReferenceRegistry.References.ContainsKey(key))
                            {
                                settings.ReferenceRegistryManager.SchemaReferenceRegistry.References[key].Example
                                    = firstExample;
                            }
                        }
                        else
                        {
                            schema.Example = firstExample;
                        }
                    }
                }

                if (operation.RequestBody == null)
                {
                    operation.RequestBody = new OpenApiRequestBody
                    {
                        Description = description.RemoveBlankLines(),
                        Content     =
                        {
                            [mediaType] = new OpenApiMediaType {
                                Schema = schema
                            }
                        },
                        Required = true
                    };
                }
                else
                {
                    if (string.IsNullOrWhiteSpace(operation.RequestBody.Description))
                    {
                        operation.RequestBody.Description = description.RemoveBlankLines();
                    }

                    if (!operation.RequestBody.Content.ContainsKey(mediaType))
                    {
                        operation.RequestBody.Content[mediaType] = new OpenApiMediaType
                        {
                            Schema = schema
                        };
                    }
                    else
                    {
                        if (!operation.RequestBody.Content[mediaType].Schema.AnyOf.Any())
                        {
                            var existingSchema = operation.RequestBody.Content[mediaType].Schema;
                            var newSchema      = new OpenApiSchema();
                            newSchema.AnyOf.Add(existingSchema);

                            operation.RequestBody.Content[mediaType].Schema = newSchema;
                        }

                        operation.RequestBody.Content[mediaType].Schema.AnyOf.Add(schema);
                    }
                }

                if (examples.Count > 0)
                {
                    if (operation.RequestBody.Content[mediaType].Examples.Any())
                    {
                        examples.CopyInto(operation.RequestBody.Content[mediaType].Examples);
                    }
                    else
                    {
                        operation.RequestBody.Content[mediaType].Examples = examples;
                    }
                }
            }
        }