Skip to content

Commit

Permalink
Support deprecation and added versions for API methods in ApiGenerator (
Browse files Browse the repository at this point in the history
opensearch-project#402)

* Handle generating deprecation notices on high-level API methods

Signed-off-by: Thomas Farr <tsfarr@amazon.com>

* Improve formatting

Signed-off-by: Thomas Farr <tsfarr@amazon.com>

* Better URL path handling and support x-version-added on APIs

Signed-off-by: Thomas Farr <tsfarr@amazon.com>

* Re-generate

Signed-off-by: Thomas Farr <tsfarr@amazon.com>

---------

Signed-off-by: Thomas Farr <tsfarr@amazon.com>
  • Loading branch information
Xtansia committed Dec 3, 2023
1 parent 3d8d667 commit 2a6c5a6
Show file tree
Hide file tree
Showing 33 changed files with 386 additions and 363 deletions.
1 change: 1 addition & 0 deletions src/ApiGenerator/ApiGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageReference Include="Glob" Version="1.1.9" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NSwag.Core.Yaml" Version="13.20.0" />
<PackageReference Include="SemanticVersioning" Version="2.0.2" />
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
<PackageReference Include="Spectre.Console" Version="0.47.0" />
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.3.0-alpha.20371.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,36 @@
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<UrlPart> parts, bool selectorIsOptional, string link, string summary)
: base(names, parts, selectorIsOptional, link, summary) { }
public BoundFluentMethod(CsharpNames names, IReadOnlyCollection<UrlPart> 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));

private string SelectorReturn => string.IsNullOrWhiteSpace(CsharpNames.GenericsDeclaredOnRequest)
|| !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}>";


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<UrlPart> parts, bool selectorIsOptional, string link, string summary)
: base(names, parts, selectorIsOptional, link, summary) { }
public FluentMethod(CsharpNames names, IReadOnlyCollection<UrlPart> 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
Expand Down
25 changes: 13 additions & 12 deletions src/ApiGenerator/Domain/Code/HighLevel/Methods/FluentSyntaxBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UrlPart> parts, bool selectorIsOptional, string link, string summary)
: base(names, link, summary) =>
protected FluentSyntaxBase(CsharpNames names, IReadOnlyCollection<UrlPart> parts, bool selectorIsOptional, string link, string summary, Deprecation deprecated, Version versionAdded)
: base(names, link, summary, deprecated, versionAdded) =>
(UrlParts, _selectorIsOptional) = (CreateDescriptorArgs(parts), selectorIsOptional);

private IReadOnlyCollection<UrlPart> UrlParts { get; }
Expand All @@ -61,7 +62,7 @@ protected FluentSyntaxBase(CsharpNames names, IReadOnlyCollection<UrlPart> parts
CodeConfiguration.GenericOnlyInterfaces.Contains(CsharpNames.RequestInterfaceName)
? CsharpNames.GenericsDeclaredOnRequest
: DescriptorGenerics;

public virtual string RequestMethodGenerics =>
CodeConfiguration.GenericOnlyInterfaces.Contains(CsharpNames.RequestInterfaceName)
? CsharpNames.GenericsDeclaredOnRequest
Expand All @@ -74,11 +75,11 @@ protected FluentSyntaxBase(CsharpNames names, IReadOnlyCollection<UrlPart> parts
private List<UrlPart> CreateDescriptorArgs(IReadOnlyCollection<UrlPart> 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")
{
Expand Down Expand Up @@ -113,15 +114,15 @@ private List<UrlPart> CreateDescriptorArgs(IReadOnlyCollection<UrlPart> 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;
Expand All @@ -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()}";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ string generic
Parameterless = true,
Generated = $"protected {typeName}() : base()",
Description =
$"///<summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>{Indent}[SerializationConstructor]",
$"/// <summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>{Indent}[SerializationConstructor]",
});
return constructors;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -41,7 +41,7 @@ public class DescriptorPartialImplementation
public IReadOnlyCollection<UrlPath> Paths { get; set; }
public IReadOnlyCollection<QueryParameters> Params { get; set; }
public bool HasBody { get; set; }

public IEnumerable<FluentRouteSetter> GetFluentRouteSetters()
{
var setters = new List<FluentRouteSetter>();
Expand All @@ -67,26 +67,26 @@ public IEnumerable<FluentRouteSetter> GetFluentRouteSetters()

var code =
$"public {returnType} {p.InterfaceName}({p.HighLevelTypeName} {paramName}) => Assign({paramName}, (a,v)=>a.RouteValues.{routeSetter}(\"{p.Name}\", {routeValue}));";
var xmlDoc = $"///<summary>{p.Description}</summary>";
var xmlDoc = $"/// <summary>{p.Description}</summary>";
setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc });
if (paramName == "index")
{
code = $"public {returnType} {p.InterfaceName}<TOther>() where TOther : class ";
code += $"=> Assign(typeof(TOther), (a,v)=>a.RouteValues.{routeSetter}(\"{p.Name}\", ({p.HighLevelTypeName})v));";
xmlDoc = $"///<summary>a shortcut into calling {p.InterfaceName}(typeof(TOther))</summary>";
xmlDoc = $"/// <summary>a shortcut into calling {p.InterfaceName}(typeof(TOther))</summary>";
setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc });
}
if (paramName == "index" && p.Type == "list")
{
code = $"public {returnType} AllIndices() => Index(Indices.All);";
xmlDoc = $"///<summary>A shortcut into calling Index(Indices.All)</summary>";
xmlDoc = $"/// <summary>A shortcut into calling Index(Indices.All)</summary>";
setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc });
}
if (paramName == "fields" && p.Type == "list")
{
code = $"public {returnType} Fields<T>(params Expression<Func<T, object>>[] fields) ";
code += $"=> Assign(fields, (a,v)=>a.RouteValues.{routeSetter}(\"fields\", (Fields)v));";
xmlDoc = $"///<summary>{p.Description}</summary>";
xmlDoc = $"/// <summary>{p.Description}</summary>";
setters.Add(new FluentRouteSetter { Code = code, XmlDoc = xmlDoc });
}
}
Expand Down
29 changes: 13 additions & 16 deletions src/ApiGenerator/Domain/Code/LowLevel/LowLevelClientMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
using System.Linq;
using System.Text.RegularExpressions;
using ApiGenerator.Domain.Specification;
using SemanticVersioning;

namespace ApiGenerator.Domain.Code.LowLevel
{
Expand All @@ -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<UrlPart> 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<string>();
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}\")";
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/ApiGenerator/Domain/RestApiSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public IEnumerable<EnumDescription> 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,
Expand Down
Loading

0 comments on commit 2a6c5a6

Please sign in to comment.