Skip to content

Commit

Permalink
Make changes to annotation targets describing paths concluding with a…
Browse files Browse the repository at this point in the history
… navigation property (#269)

* Update markdownLink regex to capture more link formats

* Correctly extract docs version from file path

* Remove line break character from text before adding it as description annotation

* Code cleanup

* Update regex to capture path ending in fully qualified entity type

* Update how annotation targets for paths ending with navigation properties look like

* Update ApiDoctor.Publishing/CSDL/csdlwriter.cs

Co-authored-by: Eastman <[email protected]>

---------

Co-authored-by: Eastman <[email protected]>
  • Loading branch information
millicentachieng and andrueastman authored Apr 18, 2024
1 parent 1916d8c commit d419b9e
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 85 deletions.
112 changes: 29 additions & 83 deletions ApiDoctor.Publishing/CSDL/csdlwriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -425,18 +425,18 @@ public EntityFramework CreateEntityFrameworkFromDocs(IssueLogger issues, string
var prefix = schema.Namespace + "." + container.Name;
foreach (var entitySet in container.EntitySets)
{
var annotationName = prefix + "/" + entitySet.Name;
var annotationTarget = prefix + "/" + entitySet.Name;
AddLinkAndRestrictionAnnotations(edmx, annotationsMap, entitySet,
entitySet.SourceMethods as MethodCollection,
annotationName, issues.For(annotationName));
annotationTarget, issues.For(annotationTarget));
}

foreach (var singleton in container.Singletons)
{
var annotationName = prefix + "/" + singleton.Name;
var annotationTarget = prefix + "/" + singleton.Name;
AddLinkAndRestrictionAnnotations(edmx, annotationsMap, singleton,
singleton.SourceMethods as MethodCollection,
annotationName, issues.For(annotationName));
annotationTarget, issues.For(annotationTarget));
}
}
schema.Annotations = annotationsMap.Values.ToList();
Expand Down Expand Up @@ -638,29 +638,22 @@ private static void MergeAnnotations(string fullName, IODataAnnotatable annotata
{
if (annotatable.Annotation?.Count > 0)
{
Annotations annotations;
if (schemaLevelAnnotations.TryGetValue(fullName, out annotations))
if (schemaLevelAnnotations.TryGetValue(fullName, out Annotations targetAnnotations))
{
foreach (var annotation in annotatable.Annotation)
{
var existingAnnotation = annotations.AnnotationList.FirstOrDefault(a => a.Term == annotation.Term);
var existingAnnotation = targetAnnotations.AnnotationList.FirstOrDefault(a => a.Term == annotation.Term);
if (existingAnnotation != null && annotation.Collection != null && annotation.Collection.Records?.Count > 0)
{
if (existingAnnotation.Collection == null)
{
existingAnnotation.Collection = new RecordCollection();
}
existingAnnotation.Collection ??= new RecordCollection();

if (existingAnnotation.Collection.Records == null)
{
existingAnnotation.Collection.Records = new List<Record>();
}
existingAnnotation.Collection.Records ??= new List<Record>();

existingAnnotation.Collection.Records.AddRange(annotation.Collection.Records);
}
else
{
annotations.AnnotationList.Add(annotation);
targetAnnotations.AnnotationList.Add(annotation);
}
}
}
Expand Down Expand Up @@ -875,7 +868,7 @@ private void CreateNewActionOrFunction(
}
}

if (target.Name.Contains("("))
if (target.Name.Contains('('))
{
target.ParameterizedName = target.Name;
var pathParameters = target.Name.TextBetweenCharacters('(', ')').Split(',');
Expand Down Expand Up @@ -986,7 +979,7 @@ private void CreateNewActionOrFunction(
/// <returns></returns>
private static ODataTargetInfo ParseRequestTargetType(string requestPath, EntityFramework edmx, IssueLogger issues)
{
string[] requestParts = requestPath.Substring(1).Split(new char[] { '/' });
string[] requestParts = requestPath[1..].Split(['/']);

EntityContainer entryPoint = (from s in edmx.DataServices.Schemas
where s.EntityContainers.Count > 0
Expand Down Expand Up @@ -1094,7 +1087,7 @@ where s.EntityContainers.Count > 0
private static readonly System.Text.RegularExpressions.Regex EntitySetPathRegEx = new(@"^\/(\w*)\/{var}$", System.Text.RegularExpressions.RegexOptions.Compiled);
// Singleton is something in the format of /name or /root/child/subChild where root is the singleton
private static readonly System.Text.RegularExpressions.Regex SingletonPathRegEx = new(@"^\/(\w*)$", System.Text.RegularExpressions.RegexOptions.Compiled);
private static readonly System.Text.RegularExpressions.Regex FullPathRegEx = new(@"\/(\w+)", System.Text.RegularExpressions.RegexOptions.Compiled);
private static readonly System.Text.RegularExpressions.Regex FullPathRegEx = new(@"\/([\w\.]+)", System.Text.RegularExpressions.RegexOptions.Compiled);

/// <summary>
/// Parse the URI paths for methods defined in the documentation and construct an entity container that contains these
Expand Down Expand Up @@ -1632,7 +1625,7 @@ private void AddSourceMethods(EntityFramework edmx, IssueLogger issues)
private static ActionOrFunctionBase FindActionOrFunctionTarget(string path, EntityFramework edmx, IssueLogger issues)
{
ActionOrFunctionBase actionOrFunction = null;
string[] requestParts = path.Substring(1).Split(new char[] { '/' });
string[] requestParts = path[1..].Split(['/']);

if (requestParts.Length < 2 || requestParts.Last() == "{var}")
return null;
Expand All @@ -1654,13 +1647,15 @@ private static ActionOrFunctionBase FindActionOrFunctionTarget(string path, Enti
if (requestTarget.QualifiedType.IsCollection())
bindingParameterType = $"Collection({bindingParameterType})";

int bracketStartIndex = requestParts[^1].IndexOf('(');
var actionOrFunctionName = bracketStartIndex == -1 ? requestParts[^1] : requestParts[^1][..bracketStartIndex];
var matches = actionsAndFunctions.Where(x =>
x.IsBound &&
(x.Name.IEquals(requestParts.Last()) || x.ParameterizedName.IEquals(requestParts.Last())) &&
(x.Name.IEquals(actionOrFunctionName) || x.ParameterizedName.IEquals(requestParts.Last())) &&
x.Parameters.Any(x => x.Type == bindingParameterType))
.ToList();

if (matches.Any())
if (matches.Count != 0)
{
actionOrFunction = matches.Where(x => x.Parameters.Any(x => x.Name == "bindingParameter")).FirstOrDefault();
if (actionOrFunction == null && matches.Count == 1) // bindingParameter not specified but can be deduced
Expand All @@ -1675,48 +1670,8 @@ private static ActionOrFunctionBase FindActionOrFunctionTarget(string path, Enti
{
issues.Error(ValidationErrorCode.Unknown, path, ex);
}
return actionOrFunction;
}

private static string FindNavigationPropertyTarget(string path, EntityFramework edmx, IssueLogger issues)
{
var requestParts = path.Substring(1).Split(new char[] { '/' }).ToList();

if (requestParts.Count < 2)
return null;
try
{
if (requestParts.Last() == "{var}")
requestParts.RemoveAt(requestParts.Count - 1);

var targetPath = string.Join('/', requestParts.SkipLast(1));
ODataTargetInfo requestTarget = ParseRequestTargetType($"/{targetPath}", edmx, issues);

if (requestTarget?.QualifiedType != null)
{
var requestTargetNamespace = requestTarget.QualifiedType.NamespaceOnly();
var schema = edmx.DataServices.Schemas.FirstOrDefault(x => x.Namespace == requestTargetNamespace || x.Alias == requestTargetNamespace);
if (schema == null)
throw new Exception($"Unknown namespace: {requestTargetNamespace}");

var typeOnly = requestTarget.QualifiedType.TypeOnly();
var complexType = schema.ComplexTypes.Concat(schema.EntityTypes)
.Where(x => x.Name == typeOnly).FirstOrDefault();
if (complexType != null)
{
var navProperty = complexType.NavigateByUriComponent(requestParts.Last(), edmx, issues, true);
if (navProperty != null)
{
return $"{schema.Namespace}.{typeOnly}/{requestParts.Last()}";
}
}
}
}
catch (Exception ex)
{
issues.Error(ValidationErrorCode.Unknown, path, ex);
}
return null;
return actionOrFunction;
}

private static void AddLinkAndRestrictionAnnotations(EntityFramework edmx, Dictionary<string, Annotations> annotationsMap, ISet set, MethodCollection methodCollection, string target, IssueLogger issues)
Expand Down Expand Up @@ -1783,20 +1738,11 @@ private static Dictionary<string, List<IODataAnnotatable>> GenerateLinkAndRestri
requestTargetMapping.TryGetValue(url, out var requestTargetName);
if (requestTargetName == null)
{
var navPropertyTargetName = FindNavigationPropertyTarget(url, edmx, issues);
if (navPropertyTargetName != null)
{
path = navPropertyTargetName;
requestTargetMapping.Add(url, navPropertyTargetName);
}
ActionOrFunctionBase actionOrFunction = FindActionOrFunctionTarget(url, edmx, issues);
if (actionOrFunction != null)
annotatable = actionOrFunction;
else
{
ActionOrFunctionBase actionOrFunction = FindActionOrFunctionTarget(url, edmx, issues);
if (actionOrFunction != null)
{
annotatable = actionOrFunction;
}
}
requestTargetMapping.Add(url, path);
}
else
{
Expand Down Expand Up @@ -1940,10 +1886,10 @@ private static string GetRestrictionTermForMethod(string httpMethod)


#region Links Annotations
private static string CreateHrefValue(string sourceFilePath)
private static string CreateHrefValue(MethodDefinition method)
{
sourceFilePath = sourceFilePath.Remove(sourceFilePath.Length - 3); // remove .md file extension
var version = sourceFilePath.Contains("beta") ? "beta" : "1.0";
var sourceFilePath = method.SourceFile.DisplayName[..^3]; // remove .md file extension
var version = method.SourceFile.FullPath.Contains("beta") ? "beta" : "1.0";

var uriBuilder = new UriBuilder()
{
Expand All @@ -1955,7 +1901,7 @@ private static string CreateHrefValue(string sourceFilePath)
return uriBuilder.ToString();
}

private static Record CreateLinksRecord(string sourceFilePath, string linkRel)
private static Record CreateLinksRecord(MethodDefinition method, string linkRel)
{
return new Record
{
Expand All @@ -1969,7 +1915,7 @@ private static Record CreateLinksRecord(string sourceFilePath, string linkRel)
new()
{
Property = "href",
String = CreateHrefValue(sourceFilePath)
String = CreateHrefValue(method)
}
}
};
Expand Down Expand Up @@ -2002,7 +1948,7 @@ private static void AddLinkAnnotation(IODataAnnotatable annotatable, MethodDefin
var linkRel = GetLinkRelValueForMethod(annotatable, method.HttpMethodVerb());
if (linkRel == null) return;

var linkRecord = CreateLinksRecord(method.SourceFile.DisplayName, linkRel);
var linkRecord = CreateLinksRecord(method, linkRel);

annotatable.Annotation ??= new List<Annotation>();
var linkAnnotation = annotatable.Annotation.FirstOrDefault(x => x.Term == Term.LinksTerm);
Expand Down Expand Up @@ -2036,7 +1982,7 @@ private static void AddReadByKeyLinkAnnotation(IODataAnnotatable annotatable, Me
{
var readRestriction = annotatable.Annotation
.FirstOrDefault(annotation => annotation.Term == Term.LinksTerm);
var currentLinkRecord = CreateLinksRecord(sourceMethod.SourceFile.DisplayName, Term.LinkRel.ReadByKey);
var currentLinkRecord = CreateLinksRecord(sourceMethod, Term.LinkRel.ReadByKey);
if (readRestriction != null)
{
var propertyValue = readRestriction.Collection.Records
Expand Down
7 changes: 5 additions & 2 deletions ApiDoctor.Validation/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static class ExtensionMethods

private static readonly Regex likelyBase64Regex = new("^[a-fA-F0-9+=/]+$", RegexOptions.Compiled);

private static readonly Regex markdownLinkRegex = new(@"\[(.*?)\]((\(.*?\))|(\s*:\s*\w+))", RegexOptions.Compiled);
private static readonly Regex markdownLinkRegex = new(@"\[(.*?)\]((\(.*?\))|(\[.*?\])|(\s*:\s*\w+))", RegexOptions.Compiled);

private static readonly Regex markdownBoldRegex = new(@"\*\*(.*?)\*\*", RegexOptions.Compiled);

Expand Down Expand Up @@ -243,9 +243,12 @@ public static string RemoveMarkdownStyling(this string markdownText)
// Remove code (`code`)
markdownText = markdownCodeRegex.Replace(markdownText, "$1");

// Remove links [text](link_target) or [text]: link_reference
// Remove links [text](link_target) or [text]: link_reference or [text][] or [text][link_reference]
markdownText = markdownLinkRegex.Replace(markdownText, "$1");

// Remove line break character
markdownText = markdownText.Replace("&#xA;", " ");

return markdownText;
}

Expand Down

0 comments on commit d419b9e

Please sign in to comment.