Skip to content

Commit

Permalink
support navigation properties (#73)
Browse files Browse the repository at this point in the history
* add navigation test

* Optional Any func (#72)

* Optional Any func (#71)

* throw error when func is null, return default if supressed

* add support navigation properties

Co-authored-by: Herman Jensen <hjanimations@hotmail.com>
  • Loading branch information
ZEXSM and h3rmanj committed Jul 10, 2021
1 parent b6ad9fb commit c76d7bd
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OData.QueryBuilder.Conventions.Constants;
using OData.QueryBuilder.Extensions;
using OData.QueryBuilder.Options;
using System.Text;

Expand All @@ -15,6 +16,6 @@ public AbstractODataQueryExpand(StringBuilder stringBuilder, ODataQueryBuilderOp
_odataQueryBuilderOptions = odataQueryBuilderOptions;
}

public string Query => _stringBuilder.ToString().Trim(QuerySeparators.Nested);
public StringBuilder Query => _stringBuilder.LastRemove(QuerySeparators.Nested);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using OData.QueryBuilder.Conventions.AddressingEntities.Options;
using System;
using System.Linq.Expressions;

namespace OData.QueryBuilder.Conventions.AddressingEntities.Query
{
public interface IODataQueryKey<TEntity> : IODataOptionKey<IODataQueryKey<TEntity>, TEntity>, IODataQuery
{
public IAddressingEntries<TResource> For<TResource>(Expression<Func<TEntity, object>> resource);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OData.QueryBuilder.Conventions.Constants;
using OData.QueryBuilder.Extensions;
using OData.QueryBuilder.Options;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -34,7 +35,12 @@ public ODataQuery(StringBuilder stringBuilder, ODataQueryBuilderOptions odataQue
return dictionary;
}

public Uri ToUri(UriKind uriKind = UriKind.RelativeOrAbsolute) =>
new Uri(_stringBuilder.ToString().TrimEnd(QuerySeparators.Main, QuerySeparators.Begin), uriKind);
public Uri ToUri(UriKind uriKind = UriKind.RelativeOrAbsolute)
{
_stringBuilder.LastRemove(QuerySeparators.Begin);
_stringBuilder.LastRemove(QuerySeparators.Main);

return new Uri(_stringBuilder.ToString(), uriKind);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using OData.QueryBuilder.Conventions.AddressingEntities.Resources.Expand;
using OData.QueryBuilder.Conventions.AddressingEntities.Resources;
using OData.QueryBuilder.Conventions.AddressingEntities.Resources.Expand;
using OData.QueryBuilder.Conventions.Constants;
using OData.QueryBuilder.Expressions.Visitors;
using OData.QueryBuilder.Extensions;
using OData.QueryBuilder.Options;
using System;
using System.Linq.Expressions;
Expand All @@ -15,6 +17,13 @@ public ODataQueryKey(StringBuilder stringBuilder, ODataQueryBuilderOptions odata
{
}

public IAddressingEntries<TResource> For<TResource>(Expression<Func<TEntity, object>> resource)
{
_stringBuilder.LastReplace(QuerySeparators.Begin, QuerySeparators.Slash);

return new ODataResource<TEntity>(_stringBuilder, _odataQueryBuilderOptions).For<TResource>(resource);
}

public IODataQueryKey<TEntity> Expand(Expression<Func<TEntity, object>> expand)
{
var query = new ODataOptionExpandExpressionVisitor().ToQuery(expand.Body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class ODataExpandResource<TEntity> : IODataExpandResource<TEntity>
private readonly StringBuilder _stringBuilder;
private AbstractODataQueryExpand _odataQueryExpand;

public string Query => $"{_stringBuilder}({_odataQueryExpand.Query})";
public StringBuilder Query => new StringBuilder($"{_stringBuilder}({_odataQueryExpand.Query})");

public ODataExpandResource(ODataQueryBuilderOptions odataQueryBuilderOptions)
{
Expand All @@ -25,7 +25,7 @@ public IODataQueryExpand<TNestedEntity> For<TNestedEntity>(Expression<Func<TEnti
{
var query = new ODataResourceExpressionVisitor().ToQuery(nestedExpand.Body);

if (!string.IsNullOrEmpty(_odataQueryExpand?.Query))
if (_odataQueryExpand?.Query != default && _odataQueryExpand?.Query.Length != 0)
{
_stringBuilder.Append($"({_odataQueryExpand.Query}),{query}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public interface IODataOperator

bool All<T>(IEnumerable<T> columnName, Func<T, bool> func);

bool Any<T>(IEnumerable<T> columnName);

bool Any<T>(IEnumerable<T> columnName, Func<T, bool> func);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,22 @@ protected override string VisitMethodCallExpression(MethodCallExpression methodC
return $"{all0}/{nameof(IODataOperator.All).ToLowerInvariant()}({all1})";
case nameof(IODataOperator.Any):
var any0 = VisitExpression(methodCallExpression.Arguments[0]);
var any1 = VisitExpression(methodCallExpression.Arguments[1]);
var any1 = default(string);

if (methodCallExpression.Arguments.Count > 1)
{
any1 = VisitExpression(methodCallExpression.Arguments[1]);

if (any1.IsNullOrQuotes())
{
if (!_odataQueryBuilderOptions.SuppressExceptionOfNullOrEmptyOperatorArgs)
{
throw new ArgumentException("Func is null");
}

return default;
}
}

return $"{any0}/{nameof(IODataOperator.Any).ToLowerInvariant()}({any1})";
case nameof(IODataFunction.Date):
Expand Down
31 changes: 31 additions & 0 deletions src/OData.QueryBuilder/Extensions/StringBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text;

namespace OData.QueryBuilder.Extensions
{
internal static class StringBuilderExtensions
{
public static StringBuilder LastRemove(this StringBuilder stringBuilder, char @char)
{
var lastIndex = stringBuilder.Length - 1;

if (stringBuilder[lastIndex] == @char)
{
stringBuilder.Remove(lastIndex, 1);
}

return stringBuilder;
}

public static StringBuilder LastReplace(this StringBuilder stringBuilder, char oldChar, char newChar)
{
var lastIndex = stringBuilder.Length - 1;

if (stringBuilder[lastIndex] == oldChar)
{
stringBuilder[lastIndex] = newChar;
}

return stringBuilder;
}
}
}
1 change: 0 additions & 1 deletion src/OData.QueryBuilder/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using OData.QueryBuilder.Conventions.Constants;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace OData.QueryBuilder.Extensions
Expand Down
44 changes: 44 additions & 0 deletions test/OData.QueryBuilder.Test/ODataQueryCollectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,50 @@ public void ODataQueryBuilderList_Filter_Any_With_Func_Success()
uri.Should().Be("http://mock/odata/ODataType?$filter=ODataKind/ODataCodes/any(v:date(v/Created) eq 2019-02-09T00:00:00Z)");
}

[Fact(DisplayName = "(ODataQueryBuilderList) Filter Any without func => Success")]
public void ODataQueryBuilderList_Filter_Any_Without_Func()
{
var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f, o) => o.Any(s.Labels))
.ToUri();

uri.Should().Be("http://mock/odata/ODataType?$filter=Labels/any()");
}

[Fact(DisplayName = "(ODataQueryBuilderList) Filter Any with func null supressed => Success")]
public void ODataQueryBuilderList_Filter_Any_With_Func_null_Supressed()
{
var odataQueryBuilderOptions = new ODataQueryBuilderOptions { SuppressExceptionOfNullOrEmptyOperatorArgs = true };
var odataQueryBuilder = new ODataQueryBuilder<ODataInfoContainer>(
_commonFixture.BaseUri, odataQueryBuilderOptions);

var func = default(Func<string, bool>);

var uri = odataQueryBuilder
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, _, o) => o.Any(s.Labels, func))
.ToUri();

uri.Should().Be("http://mock/odata/ODataType?$filter=");
}

[Fact(DisplayName = "(ODataQueryBuilderList) Filter Any with func null => ArgumentException")]
public void ODataQueryBuilderList_Filter_Any_With_Func_null()
{
var func = default(Func<string, bool>);

_odataQueryBuilderDefault.Invoking(
(r) => r
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, _, o) => o.Any(s.Labels, func))
.ToUri())
.Should().Throw<ArgumentException>().WithMessage("Func is null");
}

[Fact(DisplayName = "Expand,Filter,Select,OrderBy,OrderByDescending,Skip,Top,Count => Success")]
public void ODataQueryBuilderList_Expand_Filter_Select_OrderBy_OrderByDescending_Skip_Top_Count_Success()
{
Expand Down
27 changes: 27 additions & 0 deletions test/OData.QueryBuilder.Test/ODataQueryKeyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,32 @@ public void ToDicionaryTest()

dictionary.Should().BeEquivalentTo(resultEquivalent);
}

[Fact(DisplayName = "Navigation properties => Success")]
public void Navigation_properties_test_success()
{
var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByKey(223123123)
.For<ODataKindEntity>(s => s.ODataKind)
.ByKey(223123124)
.ToUri();

uri.Should().Be("http://mock/odata/ODataType(223123123)/ODataKind(223123124)");
}

[Fact(DisplayName = "Navigation properties with select => Success")]
public void Navigation_properties_test_with_select_success()
{
var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByKey("223123123")
.For<ODataKindEntity>(s => s.ODataKind)
.ByKey("223123124")
.Select(s => s.Color)
.ToUri();

uri.Should().Be("http://mock/odata/ODataType('223123123')/ODataKind('223123124')?$select=Color");
}
}
}

0 comments on commit c76d7bd

Please sign in to comment.