diff --git a/src/ApiGenerator/ApiGenerator.csproj b/src/ApiGenerator/ApiGenerator.csproj index 5204b3a3d0..ea6d9cc7eb 100644 --- a/src/ApiGenerator/ApiGenerator.csproj +++ b/src/ApiGenerator/ApiGenerator.csproj @@ -13,6 +13,7 @@ + diff --git a/src/ApiGenerator/Domain/Code/HighLevel/Methods/BoundFluentMethod.cs b/src/ApiGenerator/Domain/Code/HighLevel/Methods/BoundFluentMethod.cs index 7e005f027f..54e96d8645 100644 --- a/src/ApiGenerator/Domain/Code/HighLevel/Methods/BoundFluentMethod.cs +++ b/src/ApiGenerator/Domain/Code/HighLevel/Methods/BoundFluentMethod.cs @@ -30,17 +30,18 @@ using System.Linq; using ApiGenerator.Configuration; using ApiGenerator.Domain.Specification; +using SemanticVersioning; -namespace ApiGenerator.Domain.Code.HighLevel.Methods +namespace ApiGenerator.Domain.Code.HighLevel.Methods { public class BoundFluentMethod : FluentSyntaxBase { - public BoundFluentMethod(CsharpNames names, IReadOnlyCollection parts, bool selectorIsOptional, string link, string summary) - : base(names, parts, selectorIsOptional, link, summary) { } + public BoundFluentMethod(CsharpNames names, IReadOnlyCollection parts, bool selectorIsOptional, string link, string summary, Deprecation deprecated, Version versionAdded) + : base(names, parts, selectorIsOptional, link, summary, deprecated, versionAdded) { } private string DescriptorTypeParams => string.Join(", ", CsharpNames.DescriptorGenerics .Select(e => CsharpNames.DescriptorBoundDocumentGeneric)); - + private string RequestTypeParams => string.Join(", ", CsharpNames.SplitGeneric(CsharpNames.GenericsDeclaredOnRequest) .Select(e => CsharpNames.DescriptorBoundDocumentGeneric)); @@ -48,17 +49,17 @@ public BoundFluentMethod(CsharpNames names, IReadOnlyCollection parts, || !CodeConfiguration.GenericOnlyInterfaces.Contains(CsharpNames.RequestInterfaceName) ? CsharpNames.RequestInterfaceName : $"{CsharpNames.RequestInterfaceName}<{RequestTypeParams}>"; - + public override string DescriptorName => $"{CsharpNames.DescriptorName}<{DescriptorTypeParams}>"; public override string GenericWhereClause => $"where {CsharpNames.DescriptorBoundDocumentGeneric} : class"; public override string MethodGenerics => $"<{CsharpNames.DescriptorBoundDocumentGeneric}>"; - - public override string RequestMethodGenerics => !string.IsNullOrWhiteSpace(RequestTypeParams) + + public override string RequestMethodGenerics => !string.IsNullOrWhiteSpace(RequestTypeParams) ? $"<{RequestTypeParams}>" : base.RequestMethodGenerics; - + public override string Selector => $"Func<{DescriptorName}, {SelectorReturn}>"; - + } } diff --git a/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentMethod.cs b/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentMethod.cs index 07c5ccd5a9..fe7f3a3690 100644 --- a/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentMethod.cs +++ b/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentMethod.cs @@ -29,13 +29,14 @@ using System.Collections.Generic; using System.Linq; using ApiGenerator.Domain.Specification; +using SemanticVersioning; -namespace ApiGenerator.Domain.Code.HighLevel.Methods +namespace ApiGenerator.Domain.Code.HighLevel.Methods { public class FluentMethod : FluentSyntaxBase { - public FluentMethod(CsharpNames names, IReadOnlyCollection parts, bool selectorIsOptional, string link, string summary) - : base(names, parts, selectorIsOptional, link, summary) { } + public FluentMethod(CsharpNames names, IReadOnlyCollection parts, bool selectorIsOptional, string link, string summary, Deprecation deprecated, Version versionAdded) + : base(names, parts, selectorIsOptional, link, summary, deprecated, versionAdded) { } public override string GenericWhereClause => string.Join(" ", CsharpNames.HighLevelDescriptorMethodGenerics diff --git a/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentSyntaxBase.cs b/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentSyntaxBase.cs index abff18c282..e3f9d02765 100644 --- a/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentSyntaxBase.cs +++ b/src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentSyntaxBase.cs @@ -30,15 +30,16 @@ using System.Linq; using ApiGenerator.Configuration; using ApiGenerator.Domain.Specification; +using SemanticVersioning; -namespace ApiGenerator.Domain.Code.HighLevel.Methods +namespace ApiGenerator.Domain.Code.HighLevel.Methods { public abstract class FluentSyntaxBase : MethodSyntaxBase { private readonly bool _selectorIsOptional; - protected FluentSyntaxBase(CsharpNames names, IReadOnlyCollection parts, bool selectorIsOptional, string link, string summary) - : base(names, link, summary) => + protected FluentSyntaxBase(CsharpNames names, IReadOnlyCollection parts, bool selectorIsOptional, string link, string summary, Deprecation deprecated, Version versionAdded) + : base(names, link, summary, deprecated, versionAdded) => (UrlParts, _selectorIsOptional) = (CreateDescriptorArgs(parts), selectorIsOptional); private IReadOnlyCollection UrlParts { get; } @@ -61,7 +62,7 @@ protected FluentSyntaxBase(CsharpNames names, IReadOnlyCollection parts CodeConfiguration.GenericOnlyInterfaces.Contains(CsharpNames.RequestInterfaceName) ? CsharpNames.GenericsDeclaredOnRequest : DescriptorGenerics; - + public virtual string RequestMethodGenerics => CodeConfiguration.GenericOnlyInterfaces.Contains(CsharpNames.RequestInterfaceName) ? CsharpNames.GenericsDeclaredOnRequest @@ -74,11 +75,11 @@ protected FluentSyntaxBase(CsharpNames names, IReadOnlyCollection parts private List CreateDescriptorArgs(IReadOnlyCollection parts) { var requiredParts = parts.Where(p => p.Required).ToList(); - + //Many api's return ALOT of information by default e.g get_alias or get_mapping //the client methods that take a descriptor default to forcing a choice on the user. //except for cat api's where the amount of information returned is manageable - + var willInferFromDocument = CsharpNames.GenericsDeclaredOnDescriptor?.Contains("Document") ?? false; if (!requiredParts.Any() && CsharpNames.Namespace != "Cat") { @@ -113,15 +114,15 @@ private List CreateDescriptorArgs(IReadOnlyCollection parts) } private bool IsDocumentRequest => CodeConfiguration.DocumentRequests.Contains(CsharpNames.RequestInterfaceName); - private string GenericFirstArgument => + private string GenericFirstArgument => CsharpNames.GenericsDeclaredOnDescriptor.Replace("<", "").Replace(">", "").Split(",").First().Trim(); - + public string DescriptorArguments() { string codeArgs; if (CodeConfiguration.DescriptorConstructors.TryGetValue(CsharpNames.DescriptorName, out codeArgs)) codeArgs += ","; - + if (!UrlParts.Any()) return codeArgs; string Optional(UrlPart p) => !p.Required && SelectorIsOptional ? " = null" : string.Empty; @@ -136,17 +137,17 @@ public string SelectorArguments() codeArgs = string.Join(", ", codeArgs.Split(',').Select(a=>a.Split(' ').Last())); return codeArgs; } - + var parts = UrlParts.Where(p => p.Required).ToList(); if (!parts.Any()) return null; string ToArg(UrlPart p) { if (IsDocumentRequest) return "documentWithId: document"; - + if (p.HighLevelTypeName.StartsWith("DocumentPath")) return "documentWithId: id?.Document, index: id?.Self?.Index, id: id?.Self?.Id"; - + return $"{p.Name.ToCamelCase()}: {p.Name.ToCamelCase()}"; } diff --git a/src/ApiGenerator/Domain/Code/HighLevel/Methods/InitializerMethod.cs b/src/ApiGenerator/Domain/Code/HighLevel/Methods/InitializerMethod.cs index d47228b3ce..f0f048e494 100644 --- a/src/ApiGenerator/Domain/Code/HighLevel/Methods/InitializerMethod.cs +++ b/src/ApiGenerator/Domain/Code/HighLevel/Methods/InitializerMethod.cs @@ -28,12 +28,14 @@ using System.Linq; using ApiGenerator.Configuration; +using ApiGenerator.Domain.Specification; +using SemanticVersioning; namespace ApiGenerator.Domain.Code.HighLevel.Methods { public class InitializerMethod : MethodSyntaxBase { - public InitializerMethod(CsharpNames names, string link, string summary) : base(names, link, summary) { } + public InitializerMethod(CsharpNames names, string link, string summary, Deprecation deprecated, Version versionAdded) : base(names, link, summary, deprecated, versionAdded) { } public string MethodName => CsharpNames.MethodName; diff --git a/src/ApiGenerator/Domain/Code/HighLevel/Methods/MethodSyntaxBase.cs b/src/ApiGenerator/Domain/Code/HighLevel/Methods/MethodSyntaxBase.cs index 5e27c6d8e2..a0b1f329df 100644 --- a/src/ApiGenerator/Domain/Code/HighLevel/Methods/MethodSyntaxBase.cs +++ b/src/ApiGenerator/Domain/Code/HighLevel/Methods/MethodSyntaxBase.cs @@ -26,17 +26,24 @@ * under the License. */ +using ApiGenerator.Domain.Specification; +using SemanticVersioning; + namespace ApiGenerator.Domain.Code.HighLevel.Methods { public abstract class MethodSyntaxBase { - protected MethodSyntaxBase(CsharpNames names, string link, string summary) => - (CsharpNames, DocumentationLink, XmlDocSummary) = (names, link, summary); + protected MethodSyntaxBase(CsharpNames names, string link, string summary, Deprecation deprecated, Version versionAdded) => + (CsharpNames, DocumentationLink, XmlDocSummary, Deprecated, VersionAdded) = (names, link, summary, deprecated, versionAdded); public string DocumentationLink { get; } public string XmlDocSummary { get; } + public Deprecation Deprecated { get; } + + public Version VersionAdded { get; set; } + protected CsharpNames CsharpNames { get; } public bool InterfaceResponse => ResponseName.StartsWith("ISearchResponse<"); diff --git a/src/ApiGenerator/Domain/Code/HighLevel/Requests/Constructor.cs b/src/ApiGenerator/Domain/Code/HighLevel/Requests/Constructor.cs index a46a85660e..fcf1562b36 100644 --- a/src/ApiGenerator/Domain/Code/HighLevel/Requests/Constructor.cs +++ b/src/ApiGenerator/Domain/Code/HighLevel/Requests/Constructor.cs @@ -127,7 +127,7 @@ string generic Parameterless = true, Generated = $"protected {typeName}() : base()", Description = - $"///Used for serialization purposes, making sure we have a parameterless constructor{Indent}[SerializationConstructor]", + $"/// Used for serialization purposes, making sure we have a parameterless constructor{Indent}[SerializationConstructor]", }); return constructors; } diff --git a/src/ApiGenerator/Domain/Code/HighLevel/Requests/DescriptorPartialImplementation.cs b/src/ApiGenerator/Domain/Code/HighLevel/Requests/DescriptorPartialImplementation.cs index 3039482559..3f5a39882d 100644 --- a/src/ApiGenerator/Domain/Code/HighLevel/Requests/DescriptorPartialImplementation.cs +++ b/src/ApiGenerator/Domain/Code/HighLevel/Requests/DescriptorPartialImplementation.cs @@ -30,7 +30,7 @@ using System.Linq; using ApiGenerator.Domain.Specification; -namespace ApiGenerator.Domain.Code.HighLevel.Requests +namespace ApiGenerator.Domain.Code.HighLevel.Requests { public class DescriptorPartialImplementation { @@ -41,7 +41,7 @@ public class DescriptorPartialImplementation public IReadOnlyCollection Paths { get; set; } public IReadOnlyCollection Params { get; set; } public bool HasBody { get; set; } - + public IEnumerable GetFluentRouteSetters() { var setters = new List(); @@ -67,26 +67,26 @@ public IEnumerable GetFluentRouteSetters() var code = $"public {returnType} {p.InterfaceName}({p.HighLevelTypeName} {paramName}) => Assign({paramName}, (a,v)=>a.RouteValues.{routeSetter}(\"{p.Name}\", {routeValue}));"; - var xmlDoc = $"///{p.Description}"; + var xmlDoc = $"/// {p.Description}"; setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc }); if (paramName == "index") { code = $"public {returnType} {p.InterfaceName}() where TOther : class "; code += $"=> Assign(typeof(TOther), (a,v)=>a.RouteValues.{routeSetter}(\"{p.Name}\", ({p.HighLevelTypeName})v));"; - xmlDoc = $"///a shortcut into calling {p.InterfaceName}(typeof(TOther))"; + xmlDoc = $"/// a shortcut into calling {p.InterfaceName}(typeof(TOther))"; setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc }); } if (paramName == "index" && p.Type == "list") { code = $"public {returnType} AllIndices() => Index(Indices.All);"; - xmlDoc = $"///A shortcut into calling Index(Indices.All)"; + xmlDoc = $"/// A shortcut into calling Index(Indices.All)"; setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc }); } if (paramName == "fields" && p.Type == "list") { code = $"public {returnType} Fields(params Expression>[] fields) "; code += $"=> Assign(fields, (a,v)=>a.RouteValues.{routeSetter}(\"fields\", (Fields)v));"; - xmlDoc = $"///{p.Description}"; + xmlDoc = $"/// {p.Description}"; setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc }); } } diff --git a/src/ApiGenerator/Domain/Code/LowLevel/LowLevelClientMethod.cs b/src/ApiGenerator/Domain/Code/LowLevel/LowLevelClientMethod.cs index f00f74f9e5..a81e269163 100644 --- a/src/ApiGenerator/Domain/Code/LowLevel/LowLevelClientMethod.cs +++ b/src/ApiGenerator/Domain/Code/LowLevel/LowLevelClientMethod.cs @@ -30,6 +30,7 @@ using System.Linq; using System.Text.RegularExpressions; using ApiGenerator.Domain.Specification; +using SemanticVersioning; namespace ApiGenerator.Domain.Code.LowLevel { @@ -44,35 +45,31 @@ public class LowLevelClientMethod public string PerPathMethodName { get; set; } public string HttpMethod { get; set; } - public DeprecatedPath DeprecatedPath { get; set; } + public Deprecation Deprecation { get; set; } public UrlInformation Url { get; set; } public bool HasBody { get; set; } public IEnumerable Parts { get; set; } public string Path { get; set; } + public Version VersionAdded { get; set; } public string UrlInCode { get { - string Evaluator(Match m) - { - - var arg = m.Groups[^1].Value.ToCamelCase(); - return $"{{{arg}:{arg}}}"; - } - - var url = Path.TrimStart('/'); - var options = Url.OriginalParts?.Select(p => p.Key) ?? Enumerable.Empty(); + var url = Path.TrimStart('/'); + var options = Url.AllPaths.SelectMany(p => p.Parts).Select(p => p.Name).Distinct(); var pattern = string.Join("|", options); var urlCode = $"\"{url}\""; - if (Path.Contains("{")) - { - var patchedUrl = Regex.Replace(url, "{(" + pattern + ")}", Evaluator); - urlCode = $"Url($\"{patchedUrl}\")"; - } - return urlCode; + if (!Path.Contains('{')) return urlCode; + + var patchedUrl = Regex.Replace(url, "{(" + pattern + ")}", m => + { + var arg = m.Groups[^1].Value.ToCamelCase(); + return $"{{{arg}:{arg}}}"; + }); + return $"Url($\"{patchedUrl}\")"; } } diff --git a/src/ApiGenerator/Domain/RestApiSpec.cs b/src/ApiGenerator/Domain/RestApiSpec.cs index c474fa71d6..042910cefb 100644 --- a/src/ApiGenerator/Domain/RestApiSpec.cs +++ b/src/ApiGenerator/Domain/RestApiSpec.cs @@ -66,7 +66,7 @@ public IEnumerable EnumsInTheSpec var urlParameterEnums = Endpoints .Values .SelectMany(e => e.Url.Params.Values) - .Where(p => p.Options != null && p.Options.Any()) + .Where(p => !p.Skip && p.Options != null && p.Options.Any()) .Select(p => new EnumDescription { Name = p.ClsName, diff --git a/src/ApiGenerator/Domain/Specification/ApiEndpoint.cs b/src/ApiGenerator/Domain/Specification/ApiEndpoint.cs index 8ff2f8c41f..f22c15731f 100644 --- a/src/ApiGenerator/Domain/Specification/ApiEndpoint.cs +++ b/src/ApiGenerator/Domain/Specification/ApiEndpoint.cs @@ -33,6 +33,7 @@ using ApiGenerator.Domain.Code.HighLevel.Methods; using ApiGenerator.Domain.Code.HighLevel.Requests; using ApiGenerator.Domain.Code.LowLevel; +using SemanticVersioning; namespace ApiGenerator.Domain.Specification { @@ -78,7 +79,7 @@ public class ApiEndpoint CsharpNames = CsharpNames, OfficialDocumentationLink = OfficialDocumentationLink?.Url, Stability = Stability, - Paths = Url.Paths, + Paths = Url.Paths.ToList(), Parts = Url.Parts, Params = ParamsToGenerate.ToList(), Constructors = Constructor.RequestConstructors(CsharpNames, Url, inheritsFromPlainRequestBase: true).ToList(), @@ -91,7 +92,7 @@ public class ApiEndpoint CsharpNames = CsharpNames, OfficialDocumentationLink = OfficialDocumentationLink?.Url, Constructors = Constructor.DescriptorConstructors(CsharpNames, Url).ToList(), - Paths = Url.Paths, + Paths = Url.Paths.ToList(), Parts = Url.Parts, Params = ParamsToGenerate.ToList(), HasBody = Body != null, @@ -117,24 +118,45 @@ public class ApiEndpoint public string HighLevelMethodXmlDocDescription => $"{PreferredHttpMethod} request to the {Name} API, read more about this API online:"; - public HighLevelModel HighLevelModel => new HighLevelModel - { + private bool BodyIsOptional => Body is not { Required: true } || HttpMethods.Contains("GET"); + + private Deprecation Deprecated => + !Url.Paths.Any() && Url.AllPaths.Count > 0 + ? Url.DeprecatedPaths + .Select(p => p.Deprecation) + .MaxBy(d => d.Version) + : null; + + private Version VersionAdded => + Url.AllPaths + .Select(p => p.VersionAdded) + .Where(v => v != null) + .Min(); + + public HighLevelModel HighLevelModel => new() + { CsharpNames = CsharpNames, Fluent = new FluentMethod(CsharpNames, Url.Parts, - selectorIsOptional: Body == null || !Body.Required || HttpMethods.Contains("GET"), + selectorIsOptional: BodyIsOptional, link: OfficialDocumentationLink?.Url, - summary: HighLevelMethodXmlDocDescription + summary: HighLevelMethodXmlDocDescription, + deprecated: Deprecated, + versionAdded: VersionAdded ), FluentBound = !CsharpNames.DescriptorBindsOverMultipleDocuments ? null : new BoundFluentMethod(CsharpNames, Url.Parts, - selectorIsOptional: Body == null || !Body.Required || HttpMethods.Contains("GET"), + selectorIsOptional: BodyIsOptional, link: OfficialDocumentationLink?.Url, - summary: HighLevelMethodXmlDocDescription + summary: HighLevelMethodXmlDocDescription, + deprecated: Deprecated, + versionAdded: VersionAdded ), Initializer = new InitializerMethod(CsharpNames, link: OfficialDocumentationLink?.Url, - summary: HighLevelMethodXmlDocDescription + summary: HighLevelMethodXmlDocDescription, + deprecated: Deprecated, + versionAdded: VersionAdded ) }; @@ -153,7 +175,7 @@ public IReadOnlyCollection LowLevelClientMethods Generator.ApiGenerator.Warnings.Add($"API '{Name}' has no documentation"); var httpMethod = PreferredHttpMethod; - foreach (var path in Url.PathsWithDeprecations) + foreach (var path in Url.AllPaths) { var methodName = CsharpNames.PerPathMethodName(path.Path); var parts = new List(path.Parts); @@ -183,11 +205,12 @@ public IReadOnlyCollection LowLevelClientMethods HttpMethod = httpMethod, OfficialDocumentationLink = OfficialDocumentationLink?.Url, Stability = Stability, - DeprecatedPath = path.Deprecation, + Deprecation = path.Deprecation, Path = path.Path, Parts = parts, Url = Url, - HasBody = Body != null + HasBody = Body != null, + VersionAdded = path.VersionAdded, }; _lowLevelClientMethods.Add(apiMethod); } diff --git a/src/ApiGenerator/Domain/Specification/Deprecation.cs b/src/ApiGenerator/Domain/Specification/Deprecation.cs new file mode 100644 index 0000000000..96ac03c7d7 --- /dev/null +++ b/src/ApiGenerator/Domain/Specification/Deprecation.cs @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +*/ +/* +* Modifications Copyright OpenSearch Contributors. See +* GitHub history for details. +* +* Licensed to Elasticsearch B.V. under one or more contributor +* license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright +* ownership. Elasticsearch B.V. licenses this file to you under +* the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +namespace ApiGenerator.Domain.Specification; + +public class Deprecation +{ + public string Version { get; set; } + + public string Description { get; set; } + + public override string ToString() => + (!string.IsNullOrEmpty(Version), !string.IsNullOrEmpty(Description)) switch + { + (true, true) => $"Deprecated as of: {Version}, reason: {Description}", + (true, false) => $"Deprecated as of: {Version}", + (false, true) => $"reason: {Description}", + _ => "deprecated" + }; +} diff --git a/src/ApiGenerator/Domain/Specification/QueryParameters.cs b/src/ApiGenerator/Domain/Specification/QueryParameters.cs index f77b851068..71fae53ebd 100644 --- a/src/ApiGenerator/Domain/Specification/QueryParameters.cs +++ b/src/ApiGenerator/Domain/Specification/QueryParameters.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Linq; using ApiGenerator.Generator; +using Version = SemanticVersioning.Version; namespace ApiGenerator.Domain.Specification { @@ -45,7 +46,7 @@ public class QueryParameters public string Description { get; set; } - public string VersionAdded { get; set; } + public Version VersionAdded { get; set; } public IEnumerable DescriptionHighLevel { @@ -97,27 +98,13 @@ public IEnumerable DescriptionHighLevel public string Obsolete { - get - { - if (!string.IsNullOrEmpty(_obsolete)) return _obsolete; - if (Deprecated != null) - { - if (!string.IsNullOrEmpty(Deprecated.Version) && !string.IsNullOrEmpty(Deprecated.Description)) - return $"Deprecated as of: {Deprecated.Version}, reason: {Deprecated.Description}"; - if (!string.IsNullOrEmpty(Deprecated.Version)) - return $"Deprecated as of: {Deprecated.Version}"; - if (!string.IsNullOrEmpty(Deprecated.Description)) - return $"reason: {Deprecated.Description}"; - - return "deprecated"; - } - - return null; - } - set => _obsolete = value; + get => !string.IsNullOrEmpty(_obsolete) + ? _obsolete + : Deprecated?.ToString(); + set => _obsolete = value; } - public QueryParameterDeprecation Deprecated { get; set; } + public Deprecation Deprecated { get; set; } public IEnumerable Options { get; set; } public string QueryStringKey { get; set; } @@ -193,14 +180,7 @@ public string TypeLowLevel } - public string InitializerGenerator(string @namespace, string type, string name, string key, string setter, string versionAdded, params string[] doc) => + public string InitializerGenerator(string @namespace, string type, string name, string key, string setter, Version versionAdded, params string[] doc) => CodeGenerator.Property(@namespace, type, name, key, setter, Obsolete, versionAdded, doc); } - - public class QueryParameterDeprecation - { - public string Version { get; set; } - - public string Description { get; set; } - } } diff --git a/src/ApiGenerator/Domain/Specification/UrlInformation.cs b/src/ApiGenerator/Domain/Specification/UrlInformation.cs index e64e36d2ff..34f7c55591 100644 --- a/src/ApiGenerator/Domain/Specification/UrlInformation.cs +++ b/src/ApiGenerator/Domain/Specification/UrlInformation.cs @@ -26,9 +26,7 @@ * under the License. */ -using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; namespace ApiGenerator.Domain.Specification @@ -39,80 +37,10 @@ public class UrlInformation { public IDictionary Params { get; set; } = new SortedDictionary(); - public IList OriginalPaths { get; set; } = new List(); - - public IDictionary OriginalParts { get; set; } - - public IList DeprecatedPaths { get; set; } = new List(); - - private List _paths; - public IReadOnlyCollection Paths - { - get - { - if (_paths != null && _paths.Count > 0) return _paths; - - _paths = OriginalPaths.Select(p => new UrlPath(p, OriginalParts)).ToList(); - return _paths; - } - } - - private List _pathsWithDeprecation; - public IReadOnlyCollection PathsWithDeprecations - { - get - { - if (_pathsWithDeprecation != null && _pathsWithDeprecation.Count > 0) return _pathsWithDeprecation; - - var paths = Paths ?? new UrlPath[] {}; - if (DeprecatedPaths == null || DeprecatedPaths.Count == 0) return Paths; - if (OriginalParts == null) return Paths; - - //some deprecated paths describe aliases to the canonical using the same path e.g - // PUT /{index}/_mapping/{type} - // PUT /{index}/{type}/_mappings - // - //The following routine dedups these occasions and prefers either the canonical path - //or the first duplicate deprecated path - - var canonicalPartNameLookup = paths.Select(path => new HashSet(path.Parts.Select(p => p.Name))).ToList(); - var withoutDeprecatedAliases = DeprecatedPaths - .Select(deprecatedPath => new - { - deprecatedPath, - parts = new HashSet(OriginalParts.Keys.Where(k => deprecatedPath.Path.Contains($"{{{k}}}"))) - }) - .GroupBy(t => t.parts, HashSet.CreateSetComparer()) - .Where(grouped => !canonicalPartNameLookup.Any(set => set.SetEquals(grouped.Key))) - .Select(grouped => grouped.First().deprecatedPath); - - _pathsWithDeprecation = paths - .Concat(withoutDeprecatedAliases.Select(p => new UrlPath(p, OriginalParts, Paths))) - .ToList(); - - // now, check for and prefer deprecated URLs - - var finalPathsWithDeprecations = new List(_pathsWithDeprecation.Count); - - foreach (var path in _pathsWithDeprecation) - { - if (path.Deprecation is null && - DeprecatedPaths.SingleOrDefault(p => p.Path.Equals(path.Path, StringComparison.OrdinalIgnoreCase)) is { } match) - { - finalPathsWithDeprecations.Add(new UrlPath(match, OriginalParts, Paths)); - } - else - { - finalPathsWithDeprecations.Add(path); - } - } - - _pathsWithDeprecation = finalPathsWithDeprecations; - - return _pathsWithDeprecation; - } - } + public IEnumerable Paths => AllPaths.Where(p => p.Deprecation == null); + public IEnumerable DeprecatedPaths => AllPaths.Where(p => p.Deprecation != null); + public IList AllPaths = new List(); public IReadOnlyCollection Parts => Paths .SelectMany(p => p.Parts) @@ -127,7 +55,7 @@ public IReadOnlyCollection PathsWithDeprecations public bool IsDocumentApi => IsADocumentRoute(Parts); public static bool IsADocumentRoute(IReadOnlyCollection parts) => - parts.Count() == DocumentApiParts.Length + parts.Count == DocumentApiParts.Length && parts.All(p => DocumentApiParts.Contains(p.Name)); @@ -136,8 +64,8 @@ public bool TryGetDocumentApiPath(out UrlPath path) path = null; if (!IsDocumentApi) return false; - var mostVerbosePath = _paths.OrderByDescending(p => p.Parts.Count()).First(); - path = new UrlPath(mostVerbosePath.Path, OriginalParts, mostVerbosePath.Parts); + var mostVerbosePath = Paths.OrderByDescending(p => p.Parts.Count).First(); + path = new UrlPath(mostVerbosePath.Path, mostVerbosePath.Parts, mostVerbosePath.Deprecation, mostVerbosePath.VersionAdded, mostVerbosePath.Parts); return true; } } diff --git a/src/ApiGenerator/Domain/Specification/UrlPart.cs b/src/ApiGenerator/Domain/Specification/UrlPart.cs index f0f0c27bbb..08a3d02089 100644 --- a/src/ApiGenerator/Domain/Specification/UrlPart.cs +++ b/src/ApiGenerator/Domain/Specification/UrlPart.cs @@ -30,16 +30,6 @@ namespace ApiGenerator.Domain.Specification { - - // Rename this type to Deprecation and remove Path duplication - public class DeprecatedPath - { - public string Version { get; set; } - public string Path { get; set; } - public string Description { get; set; } - } - - public class UrlPart { private string _description; @@ -146,30 +136,23 @@ public string Description set => _description = CleanUpDescription(value); } - public string InterfaceName - { - get - { - switch (Name) - { - case "repository": return "RepositoryName"; - default: return Name.ToPascalCase(); - } - } - } + public string InterfaceName => + Name switch + { + "repository" => "RepositoryName", + _ => Name.ToPascalCase() + }; - public string Name { get; set; } + public string Name { get; set; } public string NameAsArgument => Name.ToCamelCase(); public IEnumerable Options { get; set; } public bool Required { get; set; } public bool Deprecated { get; set; } public string Type { get; set; } - private string CleanUpDescription(string value) - { - if (string.IsNullOrWhiteSpace(value)) return value; - - return value.Replace("use `_all` or empty string", "use the special string `_all` or Indices.All"); - } - } + private static string CleanUpDescription(string value) => + string.IsNullOrWhiteSpace(value) + ? value + : value.Replace("use `_all` or empty string", "use the special string `_all` or Indices.All"); + } } diff --git a/src/ApiGenerator/Domain/Specification/UrlPath.cs b/src/ApiGenerator/Domain/Specification/UrlPath.cs index b1546f4f6e..63f215ea07 100644 --- a/src/ApiGenerator/Domain/Specification/UrlPath.cs +++ b/src/ApiGenerator/Domain/Specification/UrlPath.cs @@ -26,50 +26,28 @@ * under the License. */ -using System; using System.Collections.Generic; using System.Linq; +using SemanticVersioning; -namespace ApiGenerator.Domain.Specification +namespace ApiGenerator.Domain.Specification { public class UrlPath { - private readonly List _additionalPartsForConstructor; + private readonly IList _additionalPartsForConstructor; public string Path { get; } - public DeprecatedPath Deprecation { get; } + public Deprecation Deprecation { get; } + public Version VersionAdded { get; } + public IList Parts { get; } - - public List Parts { get; } - - //TODO mark the parts that are deprecated - public UrlPath(DeprecatedPath path, IDictionary originalParts, IReadOnlyCollection allNonDeprecatedPaths) - : this(path.Path, originalParts) - { - Deprecation = path; - foreach (var part in Parts) - { - if (!part.Deprecated && !allNonDeprecatedPaths.Any(p => p.Path.Contains($"{{{part.Name}}}"))) - part.Deprecated = true; - } - } - public UrlPath(string path, IDictionary allParts, List additionalPartsForConstructor = null) + public UrlPath(string path, IList parts, Deprecation deprecation, Version versionAdded, IList additionalPartsForConstructor = null) { _additionalPartsForConstructor = additionalPartsForConstructor ?? new List(); Path = LeadingBackslash(path); - if (allParts == null) - { - Parts = new List(); - return; - } - var parts = - from p in allParts - //so deliciously side effect-y but at least its more isolated then in ApiEndpoint.CsharpMethods - let name = p.Value.Name = p.Key - where path.Contains($"{{{name}}}") - orderby path.IndexOf($"{{{name}}}", StringComparison.Ordinal) - select p.Value; - Parts = parts.ToList(); - } + Parts = parts; + Deprecation = deprecation; + VersionAdded = versionAdded; + } public string ConstructorArguments => string.Join(", ", Parts.Select(p => $"{p.HighLevelTypeName} {p.NameAsArgument}")); public string RequestBaseArguments => @@ -95,7 +73,7 @@ public string DocumentPathConstructorArgument(string generic) => string.Join(", public string GetXmlDocs(string indent, bool skipResolvable = false, bool documentConstructor = false) { - var doc = $@"///{Path}"; + var doc = $@"/// {Path}"; var parts = Parts.Where(p => !skipResolvable || !ResolvabeFromT.Contains(p.Name)).ToList(); if (!parts.Any()) return doc; @@ -112,7 +90,7 @@ string GetDescription(UrlPart p) } } - private string P(string name, string description) => $"///{description}"; + private string P(string name, string description) => $"/// {description}"; private string LeadingBackslash(string p) => p.StartsWith("/") ? p : $"/{p}"; } diff --git a/src/ApiGenerator/Extensions.cs b/src/ApiGenerator/Extensions.cs index f61b107fe7..9484b5b428 100644 --- a/src/ApiGenerator/Extensions.cs +++ b/src/ApiGenerator/Extensions.cs @@ -71,5 +71,8 @@ public static string SplitPascalCase(this string s) => public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s); + + public static void SortBy(this List list, Func selector) => + list.Sort((a, b) => Comparer.Default.Compare(selector(a), selector(b))); } } diff --git a/src/ApiGenerator/Generator/ApiEndpointFactory.cs b/src/ApiGenerator/Generator/ApiEndpointFactory.cs index a8640adbe5..f7827792c5 100644 --- a/src/ApiGenerator/Generator/ApiEndpointFactory.cs +++ b/src/ApiGenerator/Generator/ApiEndpointFactory.cs @@ -31,6 +31,7 @@ using System.Collections.Immutable; using System.Linq; using System.Net.Mime; +using System.Text.RegularExpressions; using ApiGenerator.Configuration; using ApiGenerator.Configuration.Overrides; using ApiGenerator.Domain; @@ -39,6 +40,8 @@ using NJsonSchema; using NJsonSchema.References; using NSwag; +using SemanticVersioning; +using Version = SemanticVersioning.Version; namespace ApiGenerator.Generator { @@ -50,63 +53,103 @@ public static ApiEndpoint From(string name, List<(string HttpPath, OpenApiPathIt var methodName = tokens[^1]; var ns = tokens.Length > 1 ? tokens[0] : null; - var urlInfo = new UrlInformation(); - HashSet requiredPathParams = null; - var allPathParams = new List(); + HashSet requiredPathParts = null; + var allParts = new Dictionary(); + var canonicalPaths = new Dictionary, UrlPath>(HashSet.CreateSetComparer()); + var deprecatedPaths = new Dictionary, UrlPath>(HashSet.CreateSetComparer()); + var overloads = new List<(UrlPath Path, List<(string From, string To)> Renames)>(); foreach (var (httpPath, path, _, operation) in variants.DistinctBy(v => v.HttpPath)) - { - if (!operation.IsDeprecated) - urlInfo.OriginalPaths.Add(httpPath); - else - { - urlInfo.DeprecatedPaths.Add(new DeprecatedPath - { - Path = httpPath, - Version = operation.GetExtension("x-version-deprecated") as string, - Description = operation.GetExtension("x-deprecation-message") as string - }); - } - - var pathParams = path.Parameters - .Concat(operation.Parameters) - .Where(p => p.Kind == OpenApiParameterKind.Path) - .ToList(); - - foreach (var overloadedParam in pathParams.Where(p => p.Schema.XOverloadedParam() != null)) + { + var parts = new List(); + var partNames = new HashSet(); + var overloadedParts = new List<(string From, string To)>(); + + foreach (var param in path.Parameters + .Concat(operation.Parameters) + .Where(p => p.Kind == OpenApiParameterKind.Path)) { - urlInfo.OriginalPaths.Add(httpPath.Replace( - $"{{{overloadedParam.Name}}}", - $"{{{overloadedParam.Schema.XOverloadedParam()}}}" - )); + var partName = param.Name; + if (!allParts.TryGetValue(partName, out var part)) + { + part = allParts[partName] = new UrlPart + { + ClrTypeNameOverride = null, + Deprecated = param.IsDeprecated, + Description = param.Description, + Name = partName, + Type = GetOpenSearchType(param.Schema), + Options = GetEnumOptions(param.Schema) + }; + } + partNames.Add(partName); + parts.Add(part); + + if (param.Schema.XOverloadedParam() is {} overloadedParam) overloadedParts.Add((partName, overloadedParam)); } - var paramNames = pathParams.Select(p => p.Name); - if (requiredPathParams != null) - requiredPathParams.IntersectWith(paramNames); - else - requiredPathParams = new HashSet(paramNames); + parts.SortBy(p => httpPath.IndexOf($"{{{p.Name}}}", StringComparison.Ordinal)); - allPathParams.AddRange(pathParams); + var urlPath = new UrlPath(httpPath, parts, GetDeprecation(operation), operation.XVersionAdded()); + (urlPath.Deprecation == null ? canonicalPaths : deprecatedPaths).TryAdd(partNames, urlPath); + + if (overloadedParts.Count > 0) + overloads.Add((urlPath, overloadedParts)); + + if (requiredPathParts != null) + requiredPathParts.IntersectWith(partNames); + else + requiredPathParts = partNames; } - urlInfo.OriginalParts = allPathParams.DistinctBy(p => p.Name) - .Select(p => new UrlPart - { - ClrTypeNameOverride = null, - Deprecated = p.IsDeprecated, - Description = p.Description, - Name = p.Name, - Required = requiredPathParams?.Contains(p.Name) ?? false, - Type = GetOpenSearchType(p.Schema), - Options = GetEnumOptions(p.Schema) - }) - .ToImmutableSortedDictionary(p => p.Name, p => p); - - urlInfo.Params = variants.SelectMany(v => v.Path.Parameters.Concat(v.Operation.Parameters)) - .Where(p => p.Kind == OpenApiParameterKind.Query) - .DistinctBy(p => p.Name) - .ToImmutableSortedDictionary(p => p.Name, BuildQueryParam); + foreach (var (path, renames) in overloads) + { + foreach (var (from, to) in renames) + { + var newPath = path.Path.Replace($"{{{from}}}", $"{{{to}}}"); + var newParts = path.Parts.Select(p => p.Name == from ? allParts[to] : p).ToList(); + var newPartNames = newParts.Select(p => p.Name).ToHashSet(); + var newUrlPath = new UrlPath(newPath, newParts, path.Deprecation, path.VersionAdded); + (newUrlPath.Deprecation == null ? canonicalPaths : deprecatedPaths).TryAdd(newPartNames, newUrlPath); + } + } + + //some deprecated paths describe aliases to the canonical using the same path e.g + // PUT /{index}/_mapping/{type} + // PUT /{index}/{type}/_mappings + // + //The following routine dedups these occasions and prefers either the canonical path + //or the first duplicate deprecated path + + var paths = canonicalPaths.Values + .Concat(deprecatedPaths + .Where(p => !canonicalPaths.ContainsKey(p.Key)) + .Select(p => p.Value)) + .ToList(); + paths.Sort((p1, p2) => p1.Parts + .Zip(p2.Parts) + .Select(t => string.Compare(t.First.Name, t.Second.Name, StringComparison.Ordinal)) + .SkipWhile(c => c == 0) + .FirstOrDefault()); + + // // now, check for and prefer deprecated URLs + // + // var finalPathsWithDeprecations = new List(_pathsWithDeprecation.Count); + // + // foreach (var path in _pathsWithDeprecation) + // { + // if (path.Deprecation is null && + // DeprecatedPaths.SingleOrDefault(p => p.Path.Equals(path.Path, StringComparison.OrdinalIgnoreCase)) is { } match) + // { + // finalPathsWithDeprecations.Add(new UrlPath(match, OriginalParts, Paths)); + // } + // else + // { + // finalPathsWithDeprecations.Add(path); + // } + // } + + foreach (var partName in requiredPathParts ?? Enumerable.Empty()) allParts[partName].Required = true; var endpoint = new ApiEndpoint { @@ -120,7 +163,14 @@ public static ApiEndpoint From(string name, List<(string HttpPath, OpenApiPathIt Description = variants[0].Operation.Description, Url = variants[0].Operation.ExternalDocumentation?.Url }, - Url = urlInfo, + Url = new UrlInformation + { + AllPaths = paths, + Params = variants.SelectMany(v => v.Path.Parameters.Concat(v.Operation.Parameters)) + .Where(p => p.Kind == OpenApiParameterKind.Query) + .DistinctBy(p => p.Name) + .ToImmutableSortedDictionary(p => p.Name, BuildQueryParam) + }, Body = variants .Select(v => v.Operation.RequestBody) .FirstOrDefault(b => b != null) is { } reqBody @@ -192,11 +242,11 @@ private static IEnumerable GetEnumOptions(JsonSchema schema) => ?? schema.ActualSchema.Enumeration?.Select(e => e.ToString()) ?? Enumerable.Empty(); - private static QueryParameterDeprecation GetDeprecation(IJsonExtensionObject schema) => + private static Deprecation GetDeprecation(IJsonExtensionObject schema) => (schema.XDeprecationMessage(), schema.XVersionDeprecated()) switch { (null, null) => null, - var (m, v) => new QueryParameterDeprecation { Description = m, Version = v } + var (m, v) => new Deprecation { Description = m, Version = v } }; private static string GetDescription(OpenApiRequestBody requestBody) @@ -215,8 +265,15 @@ private static string XDeprecationMessage(this IJsonExtensionObject schema) => private static string XVersionDeprecated(this IJsonExtensionObject schema) => schema.GetExtension("x-version-deprecated") as string; - private static string XVersionAdded(this IJsonExtensionObject schema) => - schema.GetExtension("x-version-added") as string; + private static Version XVersionAdded(this IJsonExtensionObject schema) => + schema.GetExtension("x-version-added") is string s + ? s.Split('.').Length switch + { + 1 => new Version($"{s}.0.0"), + 2 => new Version($"{s}.0"), + _ => new Version(s), + } + : null; private static string XDataType(this IJsonExtensionObject schema) => schema.GetExtension("x-data-type") as string; diff --git a/src/ApiGenerator/Generator/CodeGenerator.cs b/src/ApiGenerator/Generator/CodeGenerator.cs index 0f9b35e627..9a0a5cb025 100644 --- a/src/ApiGenerator/Generator/CodeGenerator.cs +++ b/src/ApiGenerator/Generator/CodeGenerator.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Linq; using ApiGenerator.Domain.Code.HighLevel.Requests; +using Version = SemanticVersioning.Version; namespace ApiGenerator.Generator { @@ -45,11 +46,11 @@ public static string CatFormatPropertyGenerator(string type, string name, string public static string PropertyGenerator(string type, string name, string key, string setter) => $"public {type} {name} {{ get => Q<{type}>(\"{key}\"); set => Q(\"{key}\", {setter}); }}"; - public static string Property(string @namespace, string type, string name, string key, string setter, string obsolete, string versionAdded, params string[] doc) + public static string Property(string @namespace, string type, string name, string key, string setter, string obsolete, Version versionAdded, params string[] doc) { var components = new List(); foreach (var d in RenderDocumentation(doc)) A(d); - if (!string.IsNullOrWhiteSpace(versionAdded)) A($"///Supported by OpenSearch servers of version {versionAdded} or greater."); + if (versionAdded != null) A($"/// Supported by OpenSearch servers of version {versionAdded} or greater."); if (!string.IsNullOrWhiteSpace(obsolete)) A($"[Obsolete(\"{obsolete}\")]"); var generated = @namespace != null && @namespace == "Cat" && name == "Format" @@ -89,15 +90,15 @@ private static IEnumerable RenderDocumentation(params string[] doc) { case 0: yield break; case 1: - yield return $"///{doc[0]}"; + yield return $"/// {doc[0]}"; yield break; default: - yield return "///"; + yield return "/// "; foreach (var d in doc) yield return $"/// {d}"; - yield return "///"; + yield return "/// "; yield break; } diff --git a/src/ApiGenerator/Views/HighLevel/Client/FluentSyntax/FluentMethod.cshtml b/src/ApiGenerator/Views/HighLevel/Client/FluentSyntax/FluentMethod.cshtml index 10b88e61eb..e748bf9fff 100644 --- a/src/ApiGenerator/Views/HighLevel/Client/FluentSyntax/FluentMethod.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Client/FluentSyntax/FluentMethod.cshtml @@ -3,10 +3,10 @@ @inherits ApiGenerator.CodeTemplatePage @{ var method = !Model.Async ? Model.Syntax.MethodName : string.Format("{0}Async", Model.Syntax.MethodName); - var asyncKeyword = Model.Syntax.InterfaceResponse && Model.Async ? "async " : String.Empty; + var asyncKeyword = Model.Syntax.InterfaceResponse && Model.Async ? "async " : string.Empty; var awaitKeyWord = Model.Syntax.InterfaceResponse && Model.Async ? "await ": string.Empty; - var configureAwait = Model.Syntax.InterfaceResponse && Model.Async ? ".ConfigureAwait(false)" : String.Empty; - + var configureAwait = Model.Syntax.InterfaceResponse && Model.Async ? ".ConfigureAwait(false)" : string.Empty; + var requestMethodGenerics = Model.Syntax.RequestMethodGenerics; var descriptor = Model.Syntax.DescriptorName; var selectorArgs = Model.Syntax.SelectorArguments(); diff --git a/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.Namespace.cshtml b/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.Namespace.cshtml index 331099fb9b..c3c8e5d31b 100644 --- a/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.Namespace.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.Namespace.cshtml @@ -18,12 +18,12 @@ using OpenSearch.Net.@(CsharpNames.ApiNamespace).@ns@(CsharpNames.ApiNamespaceSu // ReSharper disable RedundantTypeArgumentsOfMethod namespace OpenSearch.Client.@(CsharpNames.ApiNamespace).@ns@(CsharpNames.ApiNamespaceSuffix) { - /// + /// /// @ns.SplitPascalCase() APIs. /// Not intended to be instantiated directly. Use the property /// on . - /// - /// + /// + /// public partial class @(CsharpNames.HighLevelClientNamespacePrefix)@ns@(CsharpNames.ClientNamespaceSuffix) : NamespacedClientProxy { internal @(CsharpNames.HighLevelClientNamespacePrefix)@ns@(CsharpNames.ClientNamespaceSuffix)(OpenSearchClient client) : base(client) {} diff --git a/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.cshtml b/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.cshtml index 9ee1e2cd75..c57e3e1dfe 100644 --- a/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Client/Implementation/OpenSearchClient.cshtml @@ -1,6 +1,6 @@ @using System.Linq @using ApiGenerator.Domain -@using ApiGenerator +@using ApiGenerator @using ApiGenerator.Domain.Code @inherits CodeTemplatePage @{ await IncludeGeneratorNotice(); } @@ -17,15 +17,15 @@ using OpenSearch.Client; // ReSharper disable RedundantTypeArgumentsOfMethod namespace OpenSearch.Client { - /// - ///OpenSearch high level client - /// + /// + /// OpenSearch high level client + /// public partial class OpenSearchClient : IOpenSearchClient { foreach (var ns in namespaces) { - ///@(ns.SplitPascalCase()) APIs + /// @(ns.SplitPascalCase()) APIs public @CsharpNames.HighLevelClientNamespacePrefix@(ns)@CsharpNames.ClientNamespaceSuffix @ns { get; private set; } } @@ -41,7 +41,7 @@ namespace OpenSearch.Client } - + foreach (var kv in Model.EndpointsPerNamespaceHighLevel) { diff --git a/src/ApiGenerator/Views/HighLevel/Client/Interface/IOpenSearchClient.cshtml b/src/ApiGenerator/Views/HighLevel/Client/Interface/IOpenSearchClient.cshtml index 2cef1f859e..3dd2c0ba9d 100644 --- a/src/ApiGenerator/Views/HighLevel/Client/Interface/IOpenSearchClient.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Client/Interface/IOpenSearchClient.cshtml @@ -15,16 +15,16 @@ using OpenSearch.Client; namespace OpenSearch.Client { - /// - ///OpenSearch high level client - /// - public partial interface IOpenSearchClient + /// + /// OpenSearch high level client + /// + public partial interface IOpenSearchClient { @foreach(var (ns, endpoints) in Model.EndpointsPerNamespaceHighLevel) { if (ns != CsharpNames.RootNamespace) { - ///@ns.SplitPascalCase() APIs + /// @ns.SplitPascalCase() APIs @CsharpNames.HighLevelClientNamespacePrefix@(ns)@CsharpNames.ClientNamespaceSuffix @ns { get; } continue; diff --git a/src/ApiGenerator/Views/HighLevel/Client/MethodXmlDocs.cshtml b/src/ApiGenerator/Views/HighLevel/Client/MethodXmlDocs.cshtml index 26445d9ea9..0a4ec9a61b 100644 --- a/src/ApiGenerator/Views/HighLevel/Client/MethodXmlDocs.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Client/MethodXmlDocs.cshtml @@ -1,8 +1,18 @@ @using ApiGenerator @using ApiGenerator.Domain.Code.HighLevel.Methods; +@using Version = SemanticVersioning.Version; @inherits CodeTemplatePage /// /// @Raw(Model.XmlDocSummary) /// @Raw("") /// @Model.DocumentationLink /// +@if (Model.VersionAdded is {} versionAdded && versionAdded > new Version("1.0.0")) +{ +/// Supported by OpenSearch servers of version @(versionAdded) or greater. + +} +@if (Model.Deprecated is {} deprecation) +{ +@Raw($"[Obsolete(\"{deprecation}\")]") +} diff --git a/src/ApiGenerator/Views/HighLevel/Descriptors/Descriptor.cshtml b/src/ApiGenerator/Views/HighLevel/Descriptors/Descriptor.cshtml index f2ca53d42f..2e0626c3c1 100644 --- a/src/ApiGenerator/Views/HighLevel/Descriptors/Descriptor.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Descriptors/Descriptor.cshtml @@ -10,9 +10,9 @@ var baseInterface = names.GenericOrNonGenericInterfacePreference; var apiLookup = $"ApiUrlsLookups.{Model.CsharpNames.Namespace}{Model.CsharpNames.MethodName}"; } - ///Descriptor for @names.MethodName@(Raw(Model.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + Model.OfficialDocumentationLink + "")) + /// Descriptor for @names.MethodName@(Raw(Model.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + Model.OfficialDocumentationLink + "")) public partial class @Raw(type) @(Raw(string.Format(" : RequestDescriptorBase<{0},{1}, {2}>, {2}", type,names.ParametersName, concreteInterface))) - { + { internal override ApiUrls ApiUrls => @apiLookup; @foreach (var c in Model.Constructors) { @@ -50,36 +50,27 @@ var tSuffix = (t == "bool" || t == "bool?") ? " = true" : ""; var typed = !string.IsNullOrEmpty(names.GenericsDeclaredOnDescriptor); var g = typed ? names.GenericsDeclaredOnDescriptor.Replace("<", "").Replace(">", "") : "T"; - var desc = param.DescriptionHighLevel.ToList(); - - await IncludeAsync("HighLevel/Descriptors/XmlDocs.cshtml", desc); - if (!string.IsNullOrWhiteSpace(param.VersionAdded)) - { - - ///Supported by OpenSearch servers of version @(param.VersionAdded) or greater. - } + await IncludeAsync("HighLevel/Descriptors/XmlDocs.cshtml", param); if(!string.IsNullOrWhiteSpace(param.Obsolete)) { - - [Obsolete("@Raw(param.Obsolete)")] - + [Obsolete("@Raw(param.Obsolete)")] + } - - public @Raw(type) @(param.ClsName)(@param.DescriptorArgumentType @param.ClsArgumentName@tSuffix) => Qs("@original", @(param.ClsArgumentName)); + public @Raw(type) @(param.ClsName)(@param.DescriptorArgumentType @param.ClsArgumentName@tSuffix) => Qs("@original", @(param.ClsArgumentName)); if (param.IsFieldsParam) { - ///@param.Description + /// @param.Description public @Raw(type) @param.ClsName@(Raw(typed ? "" : ""))(params @Raw("Expression>[]") fields) @Raw(typed ? "" : "where " + g + " : class") => Qs("@original", fields?@Raw(".Select(e=>(Field)e)")); } else if (param.IsFieldParam) { - ///@param.Description + /// @param.Description public @Raw(type) @param.ClsName@(Raw(typed ? "" : ""))(@Raw("Expression>") field) @Raw(typed ? "" : "where " + g + " : class") => Qs("@original", (Field)field); } @@ -90,6 +81,6 @@ public bool IsUnmapped => true; public bool UseIsUnmapped => IsUnmapped; - } + } } diff --git a/src/ApiGenerator/Views/HighLevel/Descriptors/XmlDocs.cshtml b/src/ApiGenerator/Views/HighLevel/Descriptors/XmlDocs.cshtml index 5015387ffa..5435af2bf5 100644 --- a/src/ApiGenerator/Views/HighLevel/Descriptors/XmlDocs.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Descriptors/XmlDocs.cshtml @@ -1,16 +1,24 @@ @using System.Linq -@inherits ApiGenerator.CodeTemplatePage> +@using Version = SemanticVersioning.Version +@inherits ApiGenerator.CodeTemplatePage @{ - var description = Model.Count == 1 ? Model.First() : string.Join($"{Environment.NewLine}\t\t", Model.Select(d => "/// " + d)); - if (Model.Count == 1) + var docs = Model.DescriptionHighLevel.ToList(); + var description = docs.Count == 1 ? docs.First() : string.Join($"{Environment.NewLine}\t\t", docs.Select(d => "/// " + d)); + if (docs.Count == 1) { - ///@Raw(description) + /// @Raw(description) + } else { - /// + /// @Raw(description) - /// + /// } + if (Model.VersionAdded is {} versionAdded && versionAdded > new Version("1.0.0")) + { + /// Supported by OpenSearch servers of version @(versionAdded) or greater. + + } } diff --git a/src/ApiGenerator/Views/HighLevel/Requests/ApiUrlsLookup.cshtml b/src/ApiGenerator/Views/HighLevel/Requests/ApiUrlsLookup.cshtml index 68d92443e6..4cebf146e9 100644 --- a/src/ApiGenerator/Views/HighLevel/Requests/ApiUrlsLookup.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Requests/ApiUrlsLookup.cshtml @@ -15,7 +15,7 @@ namespace OpenSearch.Client continue; } var propertyName = $"{endpoint.CsharpNames.Namespace}{endpoint.CsharpNames.MethodName}"; - var paths = endpoint.Url.Paths.Count == 0 ? endpoint.Url.PathsWithDeprecations : endpoint.Url.Paths; + var paths = !endpoint.Url.Paths.Any() ? endpoint.Url.AllPaths : endpoint.Url.Paths; internal static readonly ApiUrls @(Raw(propertyName)) = new(new [] {@Raw(string.Join(", ", paths.Select(p=>$"\"{p.Path.TrimStart('/')}\"")))}); diff --git a/src/ApiGenerator/Views/HighLevel/Requests/RequestImplementations.cshtml b/src/ApiGenerator/Views/HighLevel/Requests/RequestImplementations.cshtml index b438e2feff..0be9404b61 100644 --- a/src/ApiGenerator/Views/HighLevel/Requests/RequestImplementations.cshtml +++ b/src/ApiGenerator/Views/HighLevel/Requests/RequestImplementations.cshtml @@ -7,7 +7,7 @@ @{ var apiLookup = $"ApiUrlsLookups.{Model.CsharpNames.Namespace}{Model.CsharpNames.MethodName}"; } -///Request for @Model.CsharpNames.MethodName@(Raw(Model.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + Model.OfficialDocumentationLink + "")) +/// Request for @Model.CsharpNames.MethodName@(Raw(Model.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + Model.OfficialDocumentationLink + "")) @if (Model.Stability != Stability.Stable) { string warningMessage = ""; @@ -20,8 +20,8 @@ warningMessage = "this functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features."; break; } - -///@Raw("Note: " + Model.Stability + " within the OpenSearch server, " + warningMessage + "") + +/// @Raw("Note: " + Model.Stability + " within the OpenSearch server, " + warningMessage + "") } public partial class @Raw(Model.Name) @Raw(string.Format(": PlainRequestBase<{0}>, {1}", Model.CsharpNames.ParametersName, Model.InterfaceName)) diff --git a/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.Namespace.cshtml b/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.Namespace.cshtml index 87a06d99a4..7711359b87 100644 --- a/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.Namespace.cshtml +++ b/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.Namespace.cshtml @@ -1,6 +1,6 @@ @using System.Collections.ObjectModel @using System.Linq -@using ApiGenerator +@using ApiGenerator @using ApiGenerator.Domain.Code @using ApiGenerator.Domain.Specification @inherits CodeTemplatePage>> @@ -25,20 +25,20 @@ using static OpenSearch.Net.HttpMethod; // ReSharper disable RedundantExtendsListEntry namespace OpenSearch.Net.@(CsharpNames.ApiNamespace).@ns@(CsharpNames.ApiNamespaceSuffix) { - /// + /// /// @ns.SplitPascalCase() APIs. /// Not intended to be instantiated directly. Use the property /// on . - /// - /// + /// + /// public partial class @(CsharpNames.LowLevelClientNamespacePrefix)@ns@(CsharpNames.ClientNamespaceSuffix) : NamespacedClientProxy { internal @(CsharpNames.LowLevelClientNamespacePrefix)@ns@(CsharpNames.ClientNamespaceSuffix)(OpenSearchLowLevelClient client) : base(client) {} @if (ns == "Cat") { - protected override string ContentType { get; } = "text/plain"; + protected override string ContentType => "text/plain"; } - @{ + @{ var methods = endpoints.SelectMany(e=>e.LowLevelClientMethods).ToList(); foreach (var method in methods) { diff --git a/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.cshtml b/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.cshtml index 231413f7a1..2795157ec4 100644 --- a/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.cshtml +++ b/src/ApiGenerator/Views/LowLevel/Client/Implementation/OpenSearchLowLevelClient.cshtml @@ -1,6 +1,6 @@ @using System.Linq @using ApiGenerator.Domain -@using ApiGenerator +@using ApiGenerator @using ApiGenerator.Domain.Code @inherits CodeTemplatePage @{ await IncludeGeneratorNotice(); } @@ -23,15 +23,15 @@ using static OpenSearch.Net.HttpMethod; // ReSharper disable RedundantExtendsListEntry namespace OpenSearch.Net { - /// - ///OpenSearch low level client - /// + /// + /// OpenSearch low level client + /// public partial class OpenSearchLowLevelClient : IOpenSearchLowLevelClient { foreach (var ns in namespaces) { - public @(CsharpNames.LowLevelClientNamespacePrefix)@(ns)@(CsharpNames.ClientNamespaceSuffix) @ns { get; private set; } + public @(CsharpNames.LowLevelClientNamespacePrefix)@(ns)@(CsharpNames.ClientNamespaceSuffix) @ns { get; private set; } } @@ -46,7 +46,7 @@ namespace OpenSearch.Net } - + foreach (var (ns, endpoints) in Model.EndpointsPerNamespaceLowLevel) { diff --git a/src/ApiGenerator/Views/LowLevel/Client/Interface/IOpenSearchLowLevelClient.cshtml b/src/ApiGenerator/Views/LowLevel/Client/Interface/IOpenSearchLowLevelClient.cshtml index c18cf883b8..4a90af2fb8 100644 --- a/src/ApiGenerator/Views/LowLevel/Client/Interface/IOpenSearchLowLevelClient.cshtml +++ b/src/ApiGenerator/Views/LowLevel/Client/Interface/IOpenSearchLowLevelClient.cshtml @@ -17,17 +17,17 @@ using OpenSearch.Net; namespace OpenSearch.Net { - /// - ///OpenSearch low level client - /// - public partial interface IOpenSearchLowLevelClient + /// + /// OpenSearch low level client + /// + public partial interface IOpenSearchLowLevelClient { @foreach(var (ns, endpoints) in Model.EndpointsPerNamespaceLowLevel) { if (ns != CsharpNames.RootNamespace) { - ///@ns.SplitPascalCase() APIs + /// @ns.SplitPascalCase() APIs @CsharpNames.LowLevelClientNamespacePrefix@(ns)@CsharpNames.ClientNamespaceSuffix @ns { get; } continue; diff --git a/src/ApiGenerator/Views/LowLevel/Client/Methods/MethodDocs.cshtml b/src/ApiGenerator/Views/LowLevel/Client/Methods/MethodDocs.cshtml index 46423d412d..2801001566 100644 --- a/src/ApiGenerator/Views/LowLevel/Client/Methods/MethodDocs.cshtml +++ b/src/ApiGenerator/Views/LowLevel/Client/Methods/MethodDocs.cshtml @@ -1,14 +1,16 @@ @using ApiGenerator @using ApiGenerator.Domain.Code.LowLevel @using ApiGenerator.Domain.Specification +@using SemanticVersioning +@using Version = SemanticVersioning.Version @inherits ApiGenerator.CodeTemplatePage -///@Model.HttpMethod on @Model.Path@(Raw(Model.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + Model.OfficialDocumentationLink + "")) +/// @Model.HttpMethod on @Model.Path@(Raw(Model.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + Model.OfficialDocumentationLink + "")) @foreach (var part in Model.Parts) { - ///@Raw("")@part.Description@Raw("") + /// @Raw("")@part.Description@Raw("") } - ///@Raw(@"Request specific configuration such as querystring parameters & request specific connection settings.") + /// @Raw(@"Request specific configuration such as querystring parameters & request specific connection settings.") @if (Model.Stability != Stability.Stable) { string warningMessage = ""; @@ -23,11 +25,15 @@ } warningMessage += " This functionality is subject to potential breaking changes within a minor version, meaning that your referencing code may break when this library is upgraded."; - - ///@Raw("Note: " + Model.Stability + " within the OpenSearch server, " + warningMessage + "") + /// @Raw("Note: " + Model.Stability + " within the OpenSearch server, " + warningMessage + "") + +} + @if (Model.VersionAdded is {} versionAdded && versionAdded > new Version("1.0.0")) + { + /// @Raw("Supported by OpenSearch servers of version " + versionAdded + " or greater.") } - @if (Model.DeprecatedPath != null) + @if (Model.Deprecation is {} deprecation) { - [Obsolete("Deprecated in version @Model.DeprecatedPath.Version: @Raw(Model.DeprecatedPath.Description)")] + [Obsolete("Deprecated in version @deprecation.Version: @Raw(deprecation.Description)")] } diff --git a/src/ApiGenerator/Views/LowLevel/RequestParameters/RequestParameters.cshtml b/src/ApiGenerator/Views/LowLevel/RequestParameters/RequestParameters.cshtml index 0295c09fd4..fd477d54ef 100644 --- a/src/ApiGenerator/Views/LowLevel/RequestParameters/RequestParameters.cshtml +++ b/src/ApiGenerator/Views/LowLevel/RequestParameters/RequestParameters.cshtml @@ -25,8 +25,8 @@ namespace OpenSearch.Net@(ns) var supportsBody = endpoint.Body != null; var names = r.CsharpNames; - ///Request options for @names.MethodName@Raw(r.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + r.OfficialDocumentationLink + "") - public partial class @names.ParametersName : RequestParameters<@names.ParametersName> + /// Request options for @names.MethodName@Raw(r.OfficialDocumentationLink.IsNullOrEmpty() ? "" : " " + r.OfficialDocumentationLink + "") + public partial class @names.ParametersName : RequestParameters<@names.ParametersName> { public override HttpMethod DefaultHttpMethod => HttpMethod.@r.HttpMethod; public override bool SupportsBody => @(supportsBody ? "true" : "false");