Skip to content

Commit

Permalink
Support no-$ for system query parameters in OData library (Issue ODat…
Browse files Browse the repository at this point in the history
  • Loading branch information
unknown committed Jul 15, 2016
1 parent 397476f commit 393be2f
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 54 deletions.
39 changes: 30 additions & 9 deletions src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.OData.Core.UriParser
#region namespaces
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.OData.Core.Metadata;
using Microsoft.OData.Core.UriParser.Aggregation;
Expand Down Expand Up @@ -506,21 +507,40 @@ private static SearchClause ParseSearchImplementation(string search, ODataUriPar
}

/// <summary>
/// Get query options according to case insensitive.
/// Gets query options according to case sensitivity and
/// whether no dollar query options is enabled.
/// </summary>
/// <param name="queryOptionName">The query option's name.</param>
/// <param name="value">The value for that query option.</param>
/// <param name="queryOptionName">The query option name.</param>
/// <param name="value">The value of the query option.</param>
/// <returns>Whether value successfully retrived.</returns>
private bool TryGetQueryOption(string queryOptionName, out string value)
{
if (!this.Resolver.EnableCaseInsensitive)
value = null;
if (queryOptionName == null)
{
return this.queryOptions.TryGetValue(queryOptionName, out value);
return false;
}

value = null;
// Trim queryOptionName to prevent caller from passing in untrimmed name for comparison with
// already trimmed keys in queryOptions dictionary.
string trimmedQueryOptionName = queryOptionName.Trim();

bool isCaseInsensitiveEnabled = this.Resolver.EnableCaseInsensitive;
bool isNoDollarQueryOptionsEnabled = this.Configuration.EnableNoDollarQueryOptions;

if (!isCaseInsensitiveEnabled && !isNoDollarQueryOptionsEnabled)
{
return this.queryOptions.TryGetValue(trimmedQueryOptionName, out value);
}

StringComparison stringComparison = isCaseInsensitiveEnabled ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

string noDollarQueryOptionName = (isNoDollarQueryOptionsEnabled && trimmedQueryOptionName.StartsWith(UriQueryConstants.DollarSign)) ?
trimmedQueryOptionName.Substring(1) : null;

var list = this.queryOptions
.Where(pair => string.Equals(queryOptionName, pair.Key, StringComparison.OrdinalIgnoreCase))
.Where(pair => string.Equals(trimmedQueryOptionName, pair.Key, stringComparison)
|| (noDollarQueryOptionName != null && string.Equals(noDollarQueryOptionName, pair.Key, stringComparison)))
.ToList();

if (list.Count == 0)
Expand All @@ -533,8 +553,9 @@ private bool TryGetQueryOption(string queryOptionName, out string value)
return true;
}

throw new ODataException(Strings.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce(queryOptionName));
throw new ODataException(Strings.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce(
isNoDollarQueryOptionsEnabled ? string.Format(CultureInfo.InvariantCulture, "${0}/{0}", noDollarQueryOptionName ?? string.Empty) : trimmedQueryOptionName));
}
#endregion private methods
}
}
}
11 changes: 11 additions & 0 deletions src/Microsoft.OData.Core/UriParser/ODataUriParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ public Func<string, BatchReferenceSegment> BatchReferenceCallback
set { this.configuration.BatchReferenceCallback = value; }
}

/// <summary>
/// Whether no dollar query options is enabled.
/// If it is enabled, the '$' prefix of system query options becomes optional.
/// For example, "select" and "$select" are equivalent in this case.
/// </summary>
public bool EnableNoDollarQueryOptions
{
get { return this.configuration.EnableNoDollarQueryOptions; }
set { this.configuration.EnableNoDollarQueryOptions = value; }
}

/// <summary>
/// Whether Uri template parsing is enabled. Uri template for keys and function parameters are supported.
/// See <see cref="UriTemplateExpression"/> class for detail.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ internal bool EnableCaseInsensitiveUriFunctionIdentifier
}
}

/// <summary>
/// Whether no dollar query options is enabled.
/// If it is enabled, the '$' prefix of system query options becomes optional.
/// For example, "select" and "$select" are equivalent in this case.
/// </summary>
internal bool EnableNoDollarQueryOptions { get; set; }

/// <summary>
/// Whether Uri template parsing is enabled. See <see cref="UriTemplateExpression"/> class for detail.
/// </summary>
Expand Down
49 changes: 40 additions & 9 deletions src/Microsoft.OData.Core/UriParser/Parsers/ExpandOptionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal sealed class ExpandOptionParser
/// The parent entity type for expand option in case expand option is star, get all parent navigation properties
/// </summary>
private readonly IEdmStructuredType parentEntityType;

/// <summary>
/// Max recursion depth. As we recurse, each new instance of this class will have this lowered by 1.
/// </summary>
Expand All @@ -49,15 +49,25 @@ internal sealed class ExpandOptionParser
/// </summary>
private bool enableCaseInsensitiveBuiltinIdentifier;

/// <summary>
/// Whether to enable no dollar query options.
/// </summary>
private bool enableNoDollarQueryOptions;

/// <summary>
/// Creates an instance of this class to parse options.
/// </summary>
/// <param name="maxRecursionDepth">Max recursion depth left.</param>
/// <param name="enableCaseInsensitiveBuiltinIdentifier">Whether to allow case insensitive for builtin identifier.</param>
internal ExpandOptionParser(int maxRecursionDepth, bool enableCaseInsensitiveBuiltinIdentifier = false)
/// <param name="enableNoDollarQueryOptions">Whether to enable no dollar query options.</param>
internal ExpandOptionParser(
int maxRecursionDepth,
bool enableCaseInsensitiveBuiltinIdentifier = false,
bool enableNoDollarQueryOptions = false)
{
this.maxRecursionDepth = maxRecursionDepth;
this.enableCaseInsensitiveBuiltinIdentifier = enableCaseInsensitiveBuiltinIdentifier;
this.enableNoDollarQueryOptions = enableNoDollarQueryOptions;
}

/// <summary>
Expand All @@ -67,8 +77,14 @@ internal ExpandOptionParser(int maxRecursionDepth, bool enableCaseInsensitiveBui
/// <param name="parentEntityType">The parent entity type for expand option</param>
/// <param name="maxRecursionDepth">Max recursion depth left.</param>
/// <param name="enableCaseInsensitiveBuiltinIdentifier">Whether to allow case insensitive for builtin identifier.</param>
internal ExpandOptionParser(ODataUriResolver resolver, IEdmStructuredType parentEntityType, int maxRecursionDepth, bool enableCaseInsensitiveBuiltinIdentifier = false)
: this(maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier)
/// <param name="enableNoDollarQueryOptions">Whether to enable no dollar query options.</param>
internal ExpandOptionParser(
ODataUriResolver resolver,
IEdmStructuredType parentEntityType,
int maxRecursionDepth,
bool enableCaseInsensitiveBuiltinIdentifier = false,
bool enableNoDollarQueryOptions = false)
: this(maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier, enableNoDollarQueryOptions)
{
this.resolver = resolver;
this.parentEntityType = parentEntityType;
Expand Down Expand Up @@ -134,6 +150,13 @@ internal List<ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken,
string text = this.enableCaseInsensitiveBuiltinIdentifier
? this.lexer.CurrentToken.Text.ToLowerInvariant()
: this.lexer.CurrentToken.Text;

// Prepend '$' prefix if needed.
if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign))
{
text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text);
}

switch (text)
{
case ExpressionConstants.QueryOptionFilter:
Expand Down Expand Up @@ -261,14 +284,22 @@ internal List<ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken,
{
var parentProperty = this.resolver.ResolveProperty(parentEntityType, pathToken.Identifier) as IEdmNavigationProperty;

// it is a navigation property, need to find the type. Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)", find navigation property Trips of Friends, then get Entity type of Trips.
// it is a navigation property, need to find the type.
// Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)",
// find navigation property Trips of Friends, then get Entity type of Trips.
if (parentProperty != null)
{
{
targetEntityType = parentProperty.ToEntityType();
}
}

SelectExpandParser innerExpandParser = new SelectExpandParser(resolver, expandText, targetEntityType, this.maxRecursionDepth - 1, enableCaseInsensitiveBuiltinIdentifier);
SelectExpandParser innerExpandParser = new SelectExpandParser(
resolver,
expandText,
targetEntityType,
this.maxRecursionDepth - 1,
this.enableCaseInsensitiveBuiltinIdentifier,
this.enableNoDollarQueryOptions);
expandOption = innerExpandParser.ParseExpand();
break;
}
Expand Down Expand Up @@ -347,8 +378,8 @@ private List<ExpandTermToken> BuildStarExpandTermToken(PathSegmentToken pathToke
{
case ExpressionConstants.QueryOptionLevels:
{
if (!isRefExpand)
{
if (!isRefExpand)
{
levelsOption = ResolveLevelOption();
}
else
Expand Down
34 changes: 29 additions & 5 deletions src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,24 @@ internal sealed class SelectExpandParser
/// </summary>
private bool enableCaseInsensitiveBuiltinIdentifier;

/// <summary>
/// Whether to enable no dollar query options.
/// </summary>
private bool enableNoDollarQueryOptions;

/// <summary>
/// Build the SelectOption strategy.
/// TODO: Really should not take the clauseToParse here. Instead it should be provided with a call to ParseSelect() or ParseExpand().
/// </summary>
/// <param name="clauseToParse">the clause to parse</param>
/// <param name="maxRecursiveDepth">max recursive depth</param>
/// <param name="enableCaseInsensitiveBuiltinIdentifier">Whether to allow case insensitive for builtin identifier.</param>
public SelectExpandParser(string clauseToParse, int maxRecursiveDepth, bool enableCaseInsensitiveBuiltinIdentifier = false)
/// <param name="enableNoDollarQueryOptions">Whether to enable no dollar query options.</param>
public SelectExpandParser(
string clauseToParse,
int maxRecursiveDepth,
bool enableCaseInsensitiveBuiltinIdentifier = false,
bool enableNoDollarQueryOptions = false)
{
this.maxRecursiveDepth = maxRecursiveDepth;

Expand All @@ -77,6 +87,8 @@ public SelectExpandParser(string clauseToParse, int maxRecursiveDepth, bool enab
this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false /*moveToFirstToken*/, false /*useSemicolonDelimiter*/) : null;

this.enableCaseInsensitiveBuiltinIdentifier = enableCaseInsensitiveBuiltinIdentifier;

this.enableNoDollarQueryOptions = enableNoDollarQueryOptions;
}

/// <summary>
Expand All @@ -87,13 +99,20 @@ public SelectExpandParser(string clauseToParse, int maxRecursiveDepth, bool enab
/// <param name="parentEntityType">the parent entity type for expand option</param>
/// <param name="maxRecursiveDepth">max recursive depth</param>
/// <param name="enableCaseInsensitiveBuiltinIdentifier">Whether to allow case insensitive for builtin identifier.</param>
public SelectExpandParser(ODataUriResolver resolver, string clauseToParse, IEdmStructuredType parentEntityType, int maxRecursiveDepth, bool enableCaseInsensitiveBuiltinIdentifier = false)
: this(clauseToParse, maxRecursiveDepth, enableCaseInsensitiveBuiltinIdentifier)
/// <param name="enableNoDollarQueryOptions">Whether to enable no dollar query options.</param>
public SelectExpandParser(
ODataUriResolver resolver,
string clauseToParse,
IEdmStructuredType parentEntityType,
int maxRecursiveDepth,
bool enableCaseInsensitiveBuiltinIdentifier = false,
bool enableNoDollarQueryOptions = false)
: this(clauseToParse, maxRecursiveDepth, enableCaseInsensitiveBuiltinIdentifier, enableNoDollarQueryOptions)
{
this.resolver = resolver;
this.parentEntityType = parentEntityType;
}

/// <summary>
/// The maximum depth for path nested in $expand.
/// </summary>
Expand Down Expand Up @@ -168,7 +187,12 @@ private List<ExpandTermToken> ParseSingleExpandTerm()

if (expandOptionParser == null)
{
expandOptionParser = new ExpandOptionParser(this.resolver, this.parentEntityType, this.maxRecursiveDepth, enableCaseInsensitiveBuiltinIdentifier)
expandOptionParser = new ExpandOptionParser(
this.resolver,
this.parentEntityType,
this.maxRecursiveDepth,
this.enableCaseInsensitiveBuiltinIdentifier,
this.enableNoDollarQueryOptions)
{
MaxFilterDepth = MaxFilterDepth,
MaxOrderByDepth = MaxOrderByDepth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ public static void Parse(
};
selectTree = selectParser.ParseSelect();

SelectExpandParser expandParser = new SelectExpandParser(configuration.Resolver, expandClause, parentEntityType, configuration.Settings.SelectExpandLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier)
SelectExpandParser expandParser = new SelectExpandParser(
configuration.Resolver,
expandClause,
parentEntityType,
configuration.Settings.SelectExpandLimit,
configuration.EnableCaseInsensitiveUriFunctionIdentifier,
configuration.EnableNoDollarQueryOptions)
{
MaxPathDepth = configuration.Settings.PathLimit,
MaxFilterDepth = configuration.Settings.FilterLimit,
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.OData.Core/UriParser/UriQueryConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,8 @@ internal static class UriQueryConstants

/// <summary>A search query option name.</summary>
internal const string SearchQueryOption = "$search";

/// <summary>Dollar sign.</summary>
internal const string DollarSign = "$";
}
}
Loading

0 comments on commit 393be2f

Please sign in to comment.