Skip to content

Commit

Permalink
Array parameter tests. (#975)
Browse files Browse the repository at this point in the history
* Array parameter tests.

* Expand tests with one-item-arrays.

* Parse one-element arrays

* Fix a test.

* ODataArray and her tests.

* Culture specific test for numbers.

* Leading char as a separator.

* Handle ODataArray.

* Use ODataArray.

* Comment changes.

Co-authored-by: tusmester <tusmester@gmail.com>
  • Loading branch information
kavics and tusmester committed May 27, 2020
1 parent 77a8238 commit ec40ee8
Show file tree
Hide file tree
Showing 10 changed files with 1,128 additions and 10 deletions.
105 changes: 105 additions & 0 deletions src/ContentRepository/OData/ODataArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace SenseNet.ContentRepository.OData
{
public class ODataArray
{
public static readonly char DefaultSeparator = ',';
public static readonly char[] AvailableSeparators = ",;:|".ToCharArray();
}
/// <summary>
/// Represents an enumerable parameter of an OData Method Based Operation.
/// The value can be parsed from a json array, querystring array or a comma separated list.
/// </summary>
public class ODataArray<T> : ODataArray, IEnumerable<T>
{
private readonly List<T> _list;

public ODataArray(IEnumerable<T> collection)
{
_list = new List<T>(collection.ToArray());
}
public ODataArray(string commaSeparated)
{
_list = ParseList(commaSeparated);
}
public ODataArray(object[] rawItems)
{
_list = new List<T>(rawItems.Select(Convert).ToArray());
}

private List<T> ParseList(string commaSeparated)
{
if(string.IsNullOrEmpty(commaSeparated))
return new List<T>();

char separator;
string source;
if (AvailableSeparators.Contains(commaSeparated[0]))
{
separator = commaSeparated[0];
source = commaSeparated.Substring(1);
}
else
{
separator = DefaultSeparator;
source = commaSeparated;
}

var array = source.Split(separator).Select(x=>x.Trim()).ToArray();
var t = typeof(T);
if (t == typeof(string))
return array.Cast<T>().ToList();
if (t == typeof(int))
return array.Select(int.Parse).Cast<T>().ToList();
if (t == typeof(long))
return array.Select(long.Parse).Cast<T>().ToList();
if (t == typeof(byte))
return array.Select(byte.Parse).Cast<T>().ToList();
if (t == typeof(bool))
return array.Select(bool.Parse).Cast<T>().ToList();
if (t == typeof(decimal))
return array.Select(x=> decimal.TryParse(x, out var v) ? v :
decimal.Parse(x, NumberStyles.Any, CultureInfo.InvariantCulture)).Cast<T>().ToList();
if (t == typeof(float))
return array.Select(x => float.TryParse(x, out var v) ? v :
float.Parse(x, NumberStyles.Any, CultureInfo.InvariantCulture)).Cast<T>().ToList();
if (t == typeof(double))
return array.Select(x => double.TryParse(x, out var v) ? v :
double.Parse(x, NumberStyles.Any, CultureInfo.InvariantCulture)).Cast<T>().ToList();
return array.Select(Parse).ToList();
}
public virtual T Parse(string inputValue)
{
throw new NotSupportedException($"ODataArray<{typeof(T).Name}> is not supported. To support it, override the 'T Parse(string)' method");
}
public virtual T Convert(object inputValue)
{
throw new NotSupportedException($"ODataArray<{typeof(T).Name}> is not supported. To support it, override the 'T Parse(object)' method");
}

/* ============================================================= IList<T> implementation */

public int Count => _list.Count;
public bool IsReadOnly => true;

public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public T this[int index]
{
get => _list[index];
set => _list[index] = value;
}
}
}
16 changes: 16 additions & 0 deletions src/OData/ODataParameterValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SenseNet.ContentRepository.OData;
using SenseNet.Services.Core.Operations;

namespace SenseNet.OData
Expand Down Expand Up @@ -94,6 +95,21 @@ public object ToArray(Type expectedType, out Type realType)
if (expectedType == typeof(IEnumerable<double>)) return array.Select(x => x.ToObject<double>()).ToArray();
if (expectedType == typeof(IEnumerable<decimal>)) return array.Select(x => x.ToObject<decimal>()).ToArray();

if (expectedType == typeof(ODataArray<string>)) return new ODataArray<string>((IEnumerable<string>)array.Select(x => x.ToObject<string>()).ToArray());
if (expectedType == typeof(ODataArray<int>)) return new ODataArray<int>(array.Select(x => x.ToObject<int>()).ToArray());
if (expectedType == typeof(ODataArray<long>)) return new ODataArray<long>(array.Select(x => x.ToObject<long>()).ToArray());
if (expectedType == typeof(ODataArray<bool>)) return new ODataArray<bool>(array.Select(x => x.ToObject<bool>()).ToArray());
if (expectedType == typeof(ODataArray<float>)) return new ODataArray<float>(array.Select(x => x.ToObject<float>()).ToArray());
if (expectedType == typeof(ODataArray<double>)) return new ODataArray<double>(array.Select(x => x.ToObject<double>()).ToArray());
if (expectedType == typeof(ODataArray<decimal>)) return new ODataArray<decimal>(array.Select(x => x.ToObject<decimal>()).ToArray());

if (typeof(ODataArray).IsAssignableFrom(expectedType))
{
realType = expectedType;
var ctorParam = array.Select(x => x.ToObject<object>()).ToArray();
return ODataTools.CreateODataArray(expectedType, ctorParam);
}

realType = typeof(object[]);
return array.Select(x => x.ToObject<object>()).ToArray();
}
Expand Down
29 changes: 28 additions & 1 deletion src/OData/ODataTools.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using SenseNet.ApplicationModel;
using SenseNet.ContentRepository;
using SenseNet.ContentRepository.OData;
using SenseNet.ContentRepository.Storage;

namespace SenseNet.OData
Expand Down Expand Up @@ -98,5 +100,30 @@ private static IEnumerable<ScenarioAction> GetActionsWithScenario(Content conten
Scenario = scenario
});
}

internal static ODataArray CreateODataArray(Type type, object[] data)
{
return CreateODataArray(type, data, false);
}
internal static ODataArray CreateODataArray(Type type, string data)
{
return CreateODataArray(type, data, true);
}
private static ODataArray CreateODataArray(Type type, object data, bool fromString)
{
var ctorParamType = fromString ? typeof(string) : typeof(object[]);
var ctor = type.GetConstructors().Where(c =>
{
var p = c.GetParameters();
if (p.Length != 1)
return false;
return p[0].ParameterType == ctorParamType;
}).FirstOrDefault();

if (ctor == null)
throw new MissingMemberException($"Missing constructor: {type.Name}(object[]).");

return (ODataArray)ctor.Invoke(new [] { data });
}
}
}
Loading

0 comments on commit ec40ee8

Please sign in to comment.