/// <summary> /// Fetches the URL value and creates multiple operations based on optional parameters. /// </summary> /// <param name="paths">The paths 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> /// <returns>The list of generation errors, if any produced when processing the filter.</returns> public IList <GenerationError> Apply( OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var generationErrors = new List <GenerationError>(); try { var paramElements = element.Elements() .Where( p => p.Name == KnownXmlStrings.Param) .ToList(); // We need both the full URL and the absolute paths for processing. // Full URL contains all path and query parameters. // Absolute path is needed to get OperationId parsed out correctly. var fullUrl = element.Elements() .FirstOrDefault(p => p.Name == KnownXmlStrings.Url) ?.Value; var absolutePath = fullUrl.UrlStringToAbsolutePath(); var operationMethod = (OperationType)Enum.Parse( typeof(OperationType), element.Elements().FirstOrDefault(p => p.Name == KnownXmlStrings.Verb)?.Value, ignoreCase: true); var allGeneratedPathStrings = GeneratePossiblePaths( absolutePath, paramElements.Where( p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path) .ToList()); foreach (var pathString in allGeneratedPathStrings) { if (!paths.ContainsKey(pathString)) { paths[pathString] = new OpenApiPathItem(); } paths[pathString].Operations[operationMethod] = new OpenApiOperation { OperationId = OperationHandler.GetOperationId(pathString, operationMethod) }; } } catch (Exception ex) { generationErrors.Add( new GenerationError { Message = ex.Message, ExceptionType = ex.GetType().Name }); } return(generationErrors); }
/// <summary> /// Validates the "in" attribute in param tagsif all parameter tags. /// </summary> /// <param name="paths">The paths to be validated.</param> /// <param name="element">The xml element representing an operation in the annotation xml.</param> /// <param name="settings">The operation filter settings.</param> public void Apply(OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var paramElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.Param) .ToList(); var paramElementsWithoutIn = paramElements.Where(p => p.Attribute(KnownXmlStrings.In)?.Value == null) .ToList(); var paramElementsWithoutAllowedValues = paramElements.Where( p => !KnownXmlStrings.AllowedInValues.Contains(p.Attribute(KnownXmlStrings.In)?.Value)).ToList(); if (paramElementsWithoutIn.Any()) { throw new MissingInAttributeException( paramElementsWithoutIn.Select( p => p.Attribute(KnownXmlStrings.Name)?.Value)); } if (paramElementsWithoutAllowedValues.Any()) { throw new NotSupportedInAttributeValueException( paramElementsWithoutAllowedValues.Select( p => p.Attribute(KnownXmlStrings.Name)?.Value), paramElementsWithoutAllowedValues.Select( p => p.Attribute(KnownXmlStrings.In)?.Value)); } var url = element.Elements() .FirstOrDefault(p => p.Name == KnownXmlStrings.Url) ?.Value; var pathParamElements = paramElements .Where(p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path) .ToList(); var matches = new Regex(@"\{(.*?)\}").Matches(url.Split('?')[0]); foreach (Match match in matches) { var pathParamNameFromUrl = match.Groups[1].Value; // All path params in the URL must be documented. if (!pathParamElements.Any(p => p.Attribute(KnownXmlStrings.Name)?.Value == pathParamNameFromUrl)) { throw new UndocumentedPathParameterException(pathParamNameFromUrl, url); } } }
public void PopulateInAttributeShouldSucceed( string testName, XElement xElement, string expectedExceptionMessage) { var filter = new PopulateInAttributeFilter(); var settings = new PreProcessingOperationFilterSettings(); var openApiPaths = new OpenApiPaths(); _output.WriteLine(testName); Action action = () => filter.Apply(openApiPaths, xElement, settings); action.Should().Throw <DocumentationException>(expectedExceptionMessage); }
public void ValidateInAttributeShouldGenerateErrors( string testName, XElement xElement, IList <GenerationError> expectedGenerationErrors) { var filter = new ValidateInAttributeFilter(); var settings = new PreProcessingOperationFilterSettings(); var openApiPaths = new OpenApiPaths(); _output.WriteLine(testName); var actualGenerationErrors = filter.Apply(openApiPaths, xElement, settings); actualGenerationErrors.Should().BeEquivalentTo(expectedGenerationErrors); }
/// <summary> /// Parses the parameters in the URL to populate the in attribute of the param tags as path or query /// if not explicitly documented. /// </summary> /// <param name="paths">The paths 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> public void Apply(OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var paramElementsWithoutIn = element.Elements().Where( p => p.Name == KnownXmlStrings.Param && p.Attribute(KnownXmlStrings.In)?.Value == null) .ToList(); var url = element.Elements() .FirstOrDefault(p => p.Name == KnownXmlStrings.Url) ?.Value; if (!string.IsNullOrWhiteSpace(url)) { foreach (var paramElement in paramElementsWithoutIn) { var paramName = paramElement.Attribute(KnownXmlStrings.Name)?.Value; if (url.Contains( $"/{{{paramName}}}", StringComparison.InvariantCultureIgnoreCase) && url.Contains( $"={{{paramName}}}", StringComparison.InvariantCultureIgnoreCase)) { // The parameter is in both path and query. We cannot determine what to put for "in" attribute. throw new ConflictingPathAndQueryParametersException(paramName, url); } if (url.Contains( $"/{{{paramName}}}", StringComparison.InvariantCultureIgnoreCase)) { paramElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Path)); } else if (url.Contains( $"={{{paramName}}}", StringComparison.InvariantCultureIgnoreCase)) { paramElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Query)); } } } }
/// <summary> /// Converts the alternative param tags (queryParam, pathParam, header) to standard param tags. /// </summary> /// <param name="paths">The paths 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> public void Apply(OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var pathParamElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.PathParam) .ToList(); var queryParamElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.QueryParam) .ToList(); var headerParamElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.Header) .ToList(); var requestTypeElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.RequestType) .ToList(); var paramElements = element.Elements().Where(i => i.Name == KnownXmlStrings.Param); if (pathParamElements.Any()) { foreach (var pathParamElement in pathParamElements) { var conflictingPathParam = paramElements.Where( i => i.Attribute("name")?.Value == pathParamElement.Attribute("name")?.Value); // Remove param tags that have same name as pathParam tags // e.g. if service is documented like below, it will remove the param tag // // <param name="samplePathParam">Sample path param</param> // <pathParam name="samplePathParam" in="path">Sample path param</pathParam> conflictingPathParam?.Remove(); pathParamElement.Name = KnownXmlStrings.Param; pathParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Path)); } } if (queryParamElements.Any()) { foreach (var queryParamElement in queryParamElements) { var conflictingQueryParam = paramElements.Where( i => i.Attribute("name")?.Value == queryParamElement.Attribute("name")?.Value); // Remove param tags that have same name as queryParam tags // e.g. if service is documented like below, it will remove the param tag // // <param name="sampleQueryParam">Sample query param</param> // <queryParam name="sampleQueryParam" in="path">Sample query param</queryParam> conflictingQueryParam?.Remove(); queryParamElement.Name = KnownXmlStrings.Param; queryParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Query)); } } if (requestTypeElements.Any()) { var paramTagToRemove = element.Elements() .Where(i => i.Name == KnownXmlStrings.Param && string.IsNullOrWhiteSpace(i.Attribute("in")?.Value)); // If there are still conflicting param tags remaining, then it's safe to assume that these are neither // path nor query params and could be documented request params which is not intended to be used with // C# document generator so remove the tags. paramTagToRemove?.Remove(); foreach (var requestTypeElement in requestTypeElements) { requestTypeElement.Name = KnownXmlStrings.Param; requestTypeElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Body)); } } foreach (var headerParamElement in headerParamElements) { headerParamElement.Name = KnownXmlStrings.Param; headerParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Header)); } }
/// <summary> /// Converts the alternative param tags (queryParam, pathParam, header) to standard param tags. /// </summary> /// <param name="paths">The paths 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> /// <returns>The list of generation errors, if any produced when processing the filter.</returns> public IList <GenerationError> Apply( OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var generationErrors = new List <GenerationError>(); try { var pathParamElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.PathParam) .ToList(); var queryParamElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.QueryParam) .ToList(); var headerParamElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.Header) .ToList(); var requestTypeElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.RequestType) .ToList(); var paramElements = element.Elements().Where(i => i.Name == KnownXmlStrings.Param); var paramElementsWithInAttributeNotSpecified = paramElements.Where(i => i.Attribute("in") == null); if (pathParamElements.Any()) { foreach (var pathParamElement in pathParamElements) { var conflictingPathParam = paramElements.Where( i => i.Attribute("name")?.Value == pathParamElement.Attribute("name")?.Value); // Remove param tags that have same name as pathParam tags // e.g. if service is documented like below, it will remove the param tag // // <param name="samplePathParam">Sample path param</param> // <pathParam name="samplePathParam" in="path">Sample path param</pathParam> conflictingPathParam?.Remove(); var nameAttribute = pathParamElement.Attribute(KnownXmlStrings.Name); var name = nameAttribute?.Value.Trim(); if (!string.IsNullOrWhiteSpace(name)) { nameAttribute.Value = name; } pathParamElement.Name = KnownXmlStrings.Param; pathParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Path)); } } if (queryParamElements.Any()) { foreach (var queryParamElement in queryParamElements) { var conflictingQueryParam = paramElements.Where( i => i.Attribute("name")?.Value == queryParamElement.Attribute("name")?.Value); // Remove param tags that have same name as queryParam tags // e.g. if service is documented like below, it will remove the param tag // // <param name="sampleQueryParam">Sample query param</param> // <queryParam name="sampleQueryParam" in="path">Sample query param</queryParam> conflictingQueryParam?.Remove(); var nameAttribute = queryParamElement.Attribute(KnownXmlStrings.Name); var name = nameAttribute?.Value.Trim(); if (!string.IsNullOrWhiteSpace(name)) { nameAttribute.Value = name; } queryParamElement.Name = KnownXmlStrings.Param; queryParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Query)); } } if (requestTypeElements.Any()) { var paramTagToRemove = element.Elements() .Where(i => i.Name == KnownXmlStrings.Param && string.IsNullOrWhiteSpace(i.Attribute("in")?.Value)); // If there are still conflicting param tags remaining, then it's safe to assume that these are neither // path nor query params and could be documented request params which is not intended to be used with // C# document generator so remove the tags. paramTagToRemove?.Remove(); foreach (var requestTypeElement in requestTypeElements) { requestTypeElement.Name = KnownXmlStrings.Param; requestTypeElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Body)); } } foreach (var headerParamElement in headerParamElements) { var nameAttribute = headerParamElement.Attribute(KnownXmlStrings.Name); var name = nameAttribute?.Value.Trim(); if (!string.IsNullOrWhiteSpace(name)) { nameAttribute.Value = name; } headerParamElement.Name = KnownXmlStrings.Param; headerParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Header)); } // If any of the alternative tags are present then remove any param tag element that have "in" attribute // not specified b/c assumption is made that those params are not supposed to be processed by // CSharp Annotation Document Generator. if (pathParamElements.Any() || queryParamElements.Any() || requestTypeElements.Any() || headerParamElements.Any()) { paramElementsWithInAttributeNotSpecified?.Remove(); } } catch (Exception ex) { generationErrors.Add( new GenerationError { Message = ex.Message, ExceptionType = ex.GetType().Name }); } return(generationErrors); }
/// <summary> /// Parses the parameters in the URL to populate the in attribute of the param tags as path or query /// if not explicitly documented. /// </summary> /// <param name="paths">The paths 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> public void Apply(OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var paramElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.Param) .ToList(); var paramElementsWithoutIn = paramElements.Where( p => p.Attribute(KnownXmlStrings.In)?.Value == null) .ToList(); var url = element.Elements() .FirstOrDefault(p => p.Name == KnownXmlStrings.Url) ?.Value; if (!string.IsNullOrWhiteSpace(url)) { foreach (var paramElement in paramElementsWithoutIn) { var paramName = paramElement.Attribute(KnownXmlStrings.Name)?.Value; if (url.Contains( $"/{{{paramName}}}", StringComparison.InvariantCultureIgnoreCase) && url.Contains( $"={{{paramName}}}", StringComparison.InvariantCultureIgnoreCase)) { // The parameter is in both path and query. We cannot determine what to put for "in" attribute. throw new ConflictingPathAndQueryParametersException(paramName, url); } if (url.Contains( $"/{{{paramName}}}", StringComparison.InvariantCultureIgnoreCase)) { paramElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Path)); } else if (url.Contains( $"={{{paramName}}}", StringComparison.InvariantCultureIgnoreCase)) { paramElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Query)); } } var pathParamElements = paramElements .Where(p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path) .ToList(); var matches = new Regex(@"\{(.*?)\}").Matches(url.Split('?')[0]); foreach (Match match in matches) { var pathParamNameFromUrl = match.Groups[1].Value; // All path params in the URL must be documented. if (!pathParamElements.Any(p => p.Attribute(KnownXmlStrings.Name)?.Value == pathParamNameFromUrl)) { throw new UndocumentedPathParameterException(pathParamNameFromUrl, url); } } } paramElementsWithoutIn = paramElements.Where(p => p.Attribute(KnownXmlStrings.In)?.Value == null) .ToList(); var paramElementsWithoutAllowedValues = paramElements.Where( p => !KnownXmlStrings.AllowedInValues.Contains(p.Attribute(KnownXmlStrings.In)?.Value)).ToList(); if (paramElementsWithoutIn.Any()) { throw new MissingInAttributeException( paramElementsWithoutIn.Select( p => p.Attribute(KnownXmlStrings.Name)?.Value)); } if (paramElementsWithoutAllowedValues.Any()) { throw new NotSupportedInAttributeValueException( paramElementsWithoutAllowedValues.Select( p => p.Attribute(KnownXmlStrings.Name)?.Value), paramElementsWithoutAllowedValues.Select( p => p.Attribute(KnownXmlStrings.In)?.Value) ); } }
/// <summary> /// Validates the "in" attribute in param tagsif all parameter tags. /// </summary> /// <param name="paths">The paths to be validated.</param> /// <param name="element">The xml element representing an operation in the annotation xml.</param> /// <param name="settings">The operation filter settings.</param> /// <returns>The list of generation errors, if any produced when processing the filter.</returns> public IList <GenerationError> Apply( OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var generationErrors = new List <GenerationError>(); try { var paramElements = element.Elements() .Where(p => p.Name == KnownXmlStrings.Param) .ToList(); var paramWithInValues = paramElements.Where(p => p.Attribute(KnownXmlStrings.In)?.Value != null).ToList(); if (!paramWithInValues.Any()) { return(generationErrors); } var paramElementsWithoutAllowedValues = paramWithInValues.Where( p => !KnownXmlStrings.AllowedInValues.Contains(p.Attribute(KnownXmlStrings.In)?.Value)).ToList(); if (paramElementsWithoutAllowedValues.Any()) { throw new NotSupportedInAttributeValueException( paramElementsWithoutAllowedValues.Select( p => p.Attribute(KnownXmlStrings.Name)?.Value), paramElementsWithoutAllowedValues.Select( p => p.Attribute(KnownXmlStrings.In)?.Value)); } var url = element.Elements() .FirstOrDefault(p => p.Name == KnownXmlStrings.Url) ?.Value; var pathParamElements = paramElements .Where(p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path) .ToList(); var matches = new Regex(@"\{(.*?)\}").Matches(url.Split('?')[0]); foreach (Match match in matches) { var pathParamNameFromUrl = match.Groups[1].Value; // All path params in the URL must be documented. if (!pathParamElements.Any(p => p.Attribute(KnownXmlStrings.Name)?.Value == pathParamNameFromUrl)) { throw new UndocumentedPathParameterException(pathParamNameFromUrl, url); } } } catch (Exception ex) { generationErrors.Add( new GenerationError { Message = ex.Message, ExceptionType = ex.GetType().Name }); } return(generationErrors); }