/// <summary> /// Fetches the value of "url" tag from xml documentation and use it to populate /// Server object of Open Api V3 specification document. /// </summary> /// <param name="specificationDocument">The Open Api V3 specification document to be updated.</param> /// <param name="xmlDocuments">The documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> /// <param name="openApiDocumentGenerationSettings"><see cref="OpenApiDocumentGenerationSettings"/></param> public void Apply( OpenApiDocument specificationDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings, OpenApiDocumentGenerationSettings openApiDocumentGenerationSettings) { var basePaths = new List <string>(); var urlElements = new List <XElement>(); foreach (var xmlDocument in xmlDocuments) { urlElements.AddRange(xmlDocument.XPathSelectElements("//doc/members/member/url")); } foreach (var urlElement in urlElements) { var url = urlElement.Value; var sanitizedUrl = SanitizeUrl(url); if (sanitizedUrl != null) { basePaths.Add(sanitizedUrl.GetLeftPart(UriPartial.Authority)); } } foreach (var basePath in basePaths.Distinct()) { specificationDocument.Servers.Add(new OpenApiServer { Url = basePath }); } }
/// <summary> /// Parses the values from the the security tags for the type member /// to populate <see cref="OpenApiSecurityRequirement"/> in <see cref="OpenApiDocument"/>. /// </summary> /// <param name="openApiDocument">The Open API specification document to be updated.</param> /// <param name="xmlDocuments">The list of documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> /// <param name="openApiDocumentGenerationSettings"><see cref="OpenApiDocumentGenerationSettings"/></param> public void Apply( OpenApiDocument openApiDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings, OpenApiDocumentGenerationSettings openApiDocumentGenerationSettings) { if (openApiDocument == null) { throw new ArgumentNullException(nameof(openApiDocument)); } if (settings == null) { throw new ArgumentNullException(nameof(settings)); } if (xmlDocuments == null) { throw new ArgumentNullException(nameof(xmlDocuments)); } var securityElements = new List <XElement>(); foreach (var xmlDocument in xmlDocuments) { securityElements.AddRange(xmlDocument.XPathSelectElements("//doc/members/member") .Where( m => m.Attribute(KnownXmlStrings.Name) != null && m.Attribute(KnownXmlStrings.Name).Value.StartsWith("T:")) .Elements() .Where(p => p.Name == KnownXmlStrings.Security)); } if (!securityElements.Any()) { return; } var openApiSecurityRequirement = new OpenApiSecurityRequirement(); var securitySchemeRegistry = settings.ReferenceRegistryManager.SecuritySchemeReferenceRegistry; foreach (var securityElement in securityElements) { var securityScheme = securitySchemeRegistry.FindOrAddReference(securityElement); openApiSecurityRequirement.Add(securityScheme, securitySchemeRegistry.Scopes); } openApiDocument.SecurityRequirements.Add(openApiSecurityRequirement); }
/// <summary> /// Fetches the value of "url" tag from xml documentation and use it to populate /// Server object of Open Api V3 specification document. /// </summary> /// <param name="specificationDocument">The Open Api V3 specification document to be updated.</param> /// <param name="xmlDocuments">The documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> /// <param name="openApiDocumentGenerationSettings"><see cref="OpenApiDocumentGenerationSettings"/></param> /// <returns>The list of generation errors, if any produced when processing the filter.</returns> public IList <GenerationError> Apply( OpenApiDocument specificationDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings, OpenApiDocumentGenerationSettings openApiDocumentGenerationSettings) { var generationErrors = new List <GenerationError>(); try { var basePaths = new List <string>(); var urlElements = new List <XElement>(); foreach (var xmlDocument in xmlDocuments) { urlElements.AddRange(xmlDocument.XPathSelectElements("//doc/members/member/url")); } foreach (var urlElement in urlElements) { var url = urlElement.Value; var sanitizedUrl = SanitizeUrl(url); if (sanitizedUrl != null) { basePaths.Add(sanitizedUrl.GetLeftPart(UriPartial.Authority)); } } foreach (var basePath in basePaths.Distinct()) { specificationDocument.Servers.Add(new OpenApiServer { Url = basePath }); } } catch (Exception ex) { generationErrors.Add( new GenerationError { Message = ex.Message, ExceptionType = ex.GetType().Name }); } return(generationErrors); }
/// <summary> /// Fetches the value of "assembly" tag from xml documentation and use it to populate /// Info object of Open Api V3 specification document. /// </summary> /// <param name="specificationDocument">The Open Api V3 specification document to be updated.</param> /// <param name="xmlDocuments">The list of documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> public void Apply( OpenApiDocument specificationDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings) { // Find the xml document that contains member tag with url and verb, // as that should be the service api documenation xml. var xmlDocument = xmlDocuments .FirstOrDefault(i => i.XPathSelectElement("//doc/members/member[url and verb]") != null); if (xmlDocument == null) { return; } specificationDocument.Info = new OpenApiInfo { Title = xmlDocument.XPathSelectElement("//doc/assembly/name")?.Value, Version = settings.OpenApiDocumentVersion }; }
/// <summary> /// Fetches the value of "assembly" tag from xml documentation and use it to populate /// Info object of Open Api V3 specification document. /// </summary> /// <param name="specificationDocument">The Open Api V3 specification document to be updated.</param> /// <param name="xmlDocuments">The list of documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> /// <param name="openApiDocumentGenerationSettings"><see cref="OpenApiDocumentGenerationSettings"/></param> /// <returns>The list of generation errors, if any produced when processing the filter.</returns> public IList <GenerationError> Apply( OpenApiDocument specificationDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings, OpenApiDocumentGenerationSettings openApiDocumentGenerationSettings) { var generationErrors = new List <GenerationError>(); try { // Find the xml document that contains member tag with url and verb, // as that should be the service api documenation xml. var xmlDocument = xmlDocuments .FirstOrDefault(i => i.XPathSelectElement("//doc/members/member[url and verb]") != null); if (xmlDocument == null) { return(generationErrors); } specificationDocument.Info = new OpenApiInfo { Title = xmlDocument.XPathSelectElement("//doc/assembly/name")?.Value, Version = settings.OpenApiDocumentVersion, Description = settings.OpenApiInfoDescription }; } catch (Exception ex) { generationErrors.Add( new GenerationError { Message = ex.Message, ExceptionType = ex.GetType().Name }); } return(generationErrors); }
/// <summary> /// Parses the values from the summary for all the properties to populate descriptions in the schema. /// </summary> /// <param name="openApiDocument">The Open API specification document to be updated.</param> /// <param name="xmlDocuments">The list of documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> /// <param name="openApiDocumentGenerationSettings"><see cref="OpenApiDocumentGenerationSettings"/></param> public void Apply( OpenApiDocument openApiDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings, OpenApiDocumentGenerationSettings openApiDocumentGenerationSettings) { if (openApiDocument == null) { throw new ArgumentNullException(nameof(openApiDocument)); } if (settings == null) { throw new ArgumentNullException(nameof(settings)); } if (xmlDocuments == null) { throw new ArgumentNullException(nameof(xmlDocuments)); } var propertyMembers = new List <XElement>(); foreach (var xmlDocument in xmlDocuments) { propertyMembers.AddRange(xmlDocument.XPathSelectElements("//doc/members/member") .Where( m => m.Attribute(KnownXmlStrings.Name) != null && m.Attribute(KnownXmlStrings.Name).Value.StartsWith("P:"))); } foreach (var propertyMember in propertyMembers) { var fullPropertyName = propertyMember.Attribute(KnownXmlStrings.Name).Value; var splitPropertyName = fullPropertyName.Split('.'); // Take everything before the last period and remove the "P:" prefix. var className = string.Join(".", splitPropertyName.Take(splitPropertyName.Length - 1)) .Substring(startIndex: 2); // We need to sanitize class name to match the format in the schema reference registry. // Note that this class may also match several classes in the registry given that generics // with different types are treated as different schemas. // For example, summary information for properties in class name A // should apply to those properties in schema A, A_B_, and A_B_C__ as well. var sanitizedClassName = className.SanitizeClassName(); var schemas = openApiDocument.Components.Schemas.Where( s => s.Key == sanitizedClassName || s.Key.StartsWith(sanitizedClassName + "_")) .ToList(); if (!schemas.Any()) { continue; } var propertyName = splitPropertyName[splitPropertyName.Length - 1]; var propertyInfo = settings.TypeFetcher.LoadType(className) ?.GetProperties() .FirstOrDefault(p => p.Name == propertyName); if (propertyInfo != null) { propertyName = openApiDocumentGenerationSettings .SchemaGenerationSettings .PropertyNameResolver .ResolvePropertyName(propertyInfo); } foreach (var schema in schemas) { if (schema.Value.Properties.ContainsKey(propertyName)) { schema.Value.Properties[propertyName].Description = propertyMember.Element(KnownXmlStrings.Summary)?.Value.RemoveBlankLines(); } } } }
/// <summary> /// Parses the values from the summary for all the properties to populate descriptions in the schema. /// </summary> /// <param name="openApiDocument">The Open API specification document to be updated.</param> /// <param name="xmlDocuments">The list of documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> /// <param name="openApiDocumentGenerationSettings"><see cref="OpenApiDocumentGenerationSettings"/></param> public void Apply( OpenApiDocument openApiDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings, OpenApiDocumentGenerationSettings openApiDocumentGenerationSettings) { if (openApiDocument == null) { throw new ArgumentNullException(nameof(openApiDocument)); } if (settings == null) { throw new ArgumentNullException(nameof(settings)); } if (xmlDocuments == null) { throw new ArgumentNullException(nameof(xmlDocuments)); } var propertyMembers = new List <XElement>(); foreach (var xmlDocument in xmlDocuments) { propertyMembers.AddRange(xmlDocument.XPathSelectElements("//doc/members/member") .Where( m => m.Attribute(KnownXmlStrings.Name) != null && m.Attribute(KnownXmlStrings.Name).Value.StartsWith("P:"))); } // Fetches distinct type names like // Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleBaseObject // From property memeber names like // P:Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleBaseObject.SampleBaseProprety var typeNames = propertyMembers .Attributes(KnownXmlStrings.Name) .Select(i => string.Join(".", i.Value.Split('.').Take(i.Value.Split('.').Length - 1)) .Substring(2)) .Distinct() .ToList(); foreach (var typeName in typeNames) { // We need to sanitize class name to match the format in the schema reference registry. // Note that this class may also match several classes in the registry given that generics // with different types are treated as different schemas. // For example, summary information for properties in class name A // should apply to those properties in schema A, A_B_, and A_B_C__ as well. var sanitizedClassName = typeName.SanitizeClassName(); var schemas = openApiDocument.Components.Schemas.Where( s => s.Key == sanitizedClassName || s.Key.StartsWith(sanitizedClassName + "_")) .ToList(); if (!schemas.Any()) { continue; } var typeInfo = settings.TypeFetcher.LoadType(typeName); var typesToFetchPropertiesFor = settings.TypeFetcher.GetBaseTypes(typeInfo); typesToFetchPropertiesFor.Add(typeInfo); var propertiesMap = new Dictionary <string, PropertyInfo>(); foreach (var type in typesToFetchPropertiesFor) { foreach (var property in type.GetProperties()) { if (!propertiesMap.ContainsKey(property.Name)) { propertiesMap.Add(property.Name, property); } } } foreach (var property in propertiesMap.Keys) { var propertyName = openApiDocumentGenerationSettings .SchemaGenerationSettings .PropertyNameResolver .ResolvePropertyName(propertiesMap[property]); foreach (var schema in schemas) { var propertyMember = propertyMembers.FirstOrDefault( i => i.Attribute(KnownXmlStrings.Name)?.Value == $"P:{propertiesMap[property].DeclaringType}.{property}"); if (propertyMember == null) { continue; } if (schema.Value.Properties.ContainsKey(propertyName)) { schema.Value.Properties[propertyName].Description = propertyMember.Element(KnownXmlStrings.Summary)?.Value.RemoveBlankLines(); } } } } }
/// <summary> /// Parses the values from the the security tags for the type member /// to populate <see cref="OpenApiSecurityRequirement"/> in <see cref="OpenApiDocument"/>. /// </summary> /// <param name="openApiDocument">The Open API specification document to be updated.</param> /// <param name="xmlDocuments">The list of documents representing the annotation xmls.</param> /// <param name="settings">Settings for document filters.</param> /// <param name="openApiDocumentGenerationSettings"><see cref="OpenApiDocumentGenerationSettings"/></param> /// <returns>The list of generation errors, if any produced when processing the filter.</returns> public IList <GenerationError> Apply( OpenApiDocument openApiDocument, IList <XDocument> xmlDocuments, DocumentFilterSettings settings, OpenApiDocumentGenerationSettings openApiDocumentGenerationSettings) { var generationErrors = new List <GenerationError>(); try { if (openApiDocument == null) { throw new ArgumentNullException(nameof(openApiDocument)); } if (settings == null) { throw new ArgumentNullException(nameof(settings)); } if (xmlDocuments == null) { throw new ArgumentNullException(nameof(xmlDocuments)); } var securityElements = new List <XElement>(); foreach (var xmlDocument in xmlDocuments) { securityElements.AddRange(xmlDocument.XPathSelectElements("//doc/members/member") .Where(m => m.Attribute(KnownXmlStrings.Name) != null && m.Attribute(KnownXmlStrings.Name).Value.Contains("WebApiConfig.Register(System.Web.Http.HttpConfiguration)")) .Elements() .Where(p => p.Name == KnownXmlStrings.Security)); } if (!securityElements.Any()) { return(generationErrors); } var openApiSecurityRequirement = new OpenApiSecurityRequirement(); var securitySchemeRegistry = settings.ReferenceRegistryManager.SecuritySchemeReferenceRegistry; foreach (var securityElement in securityElements) { var securityScheme = securitySchemeRegistry.FindOrAddReference(securityElement); openApiSecurityRequirement.Add(securityScheme, securitySchemeRegistry.Scopes); } openApiDocument.SecurityRequirements.Add(openApiSecurityRequirement); } catch (Exception ex) { generationErrors.Add( new GenerationError { Message = ex.Message, ExceptionType = ex.GetType().Name }); } return(generationErrors); }