diff --git a/DragonFruit.Common.Data.Tests/QueryCompilationTests.cs b/DragonFruit.Common.Data.Tests/QueryCompilationTests.cs
new file mode 100644
index 0000000..d22d292
--- /dev/null
+++ b/DragonFruit.Common.Data.Tests/QueryCompilationTests.cs
@@ -0,0 +1,49 @@
+// DragonFruit.Common Copyright 2020 DragonFruit Network
+// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details
+
+using System.Linq;
+using DragonFruit.Common.Data.Parameters;
+using NUnit.Framework;
+
+namespace DragonFruit.Common.Data.Tests
+{
+ [TestFixture]
+ public class QueryCompilationTests
+ {
+ [TestCase]
+ public void TestQueries()
+ {
+ var query = new TestRequest().FullUrl.Split('?').Last().Split('&');
+
+ for (int i = 0; i < TestRequest.TestDataset.Length; i++)
+ {
+ var testString = TestRequest.TestDataset[i];
+ Assert.IsTrue(query.Contains($"{TestRequest.QueryName}={testString}"));
+ Assert.IsTrue(query.Contains($"{TestRequest.QueryName}[]={testString}"));
+ Assert.IsTrue(query.Contains($"{TestRequest.QueryName}[{i}]={testString}"));
+ }
+
+ Assert.IsTrue(query.Contains($"{TestRequest.QueryName}={string.Join(":", TestRequest.TestDataset)}"));
+ }
+ }
+
+ internal class TestRequest : ApiRequest
+ {
+ internal const string QueryName = "data";
+ internal static readonly string[] TestDataset = { "a", "b", "c" };
+
+ public override string Path => "http://example.com";
+
+ [QueryParameter(QueryName, CollectionConversionMode.Recursive)]
+ public string[] RecursiveData { get; set; } = TestDataset;
+
+ [QueryParameter(QueryName, CollectionConversionMode.Ordered)]
+ public string[] OrderedData { get; set; } = TestDataset;
+
+ [QueryParameter(QueryName, CollectionConversionMode.Unordered)]
+ public string[] UnorderedData { get; set; } = TestDataset;
+
+ [QueryParameter(QueryName, CollectionConversionMode.Concatenated, CollectionSeparator = ":")]
+ public string[] ConcatenatedData { get; set; } = TestDataset;
+ }
+}
diff --git a/DragonFruit.Common.Data/ApiRequest.cs b/DragonFruit.Common.Data/ApiRequest.cs
index 734a058..5cfc6db 100644
--- a/DragonFruit.Common.Data/ApiRequest.cs
+++ b/DragonFruit.Common.Data/ApiRequest.cs
@@ -6,7 +6,6 @@
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Headers;
-using DragonFruit.Common.Data.Exceptions;
using DragonFruit.Common.Data.Parameters;
using DragonFruit.Common.Data.Serializers;
using DragonFruit.Common.Data.Utils;
diff --git a/DragonFruit.Common.Data/Methods.cs b/DragonFruit.Common.Data/Methods.cs
index 0eda7f6..2ae94cd 100644
--- a/DragonFruit.Common.Data/Methods.cs
+++ b/DragonFruit.Common.Data/Methods.cs
@@ -38,4 +38,27 @@ public enum BodyType
///
Custom
}
+
+ public enum CollectionConversionMode
+ {
+ ///
+ /// The query name is repeated and a new element created for each (a=1&a=2&a=3)
+ ///
+ Recursive,
+
+ ///
+ /// The query name has indexer symbols appended with no order (a[]=1&a[]=2&a[]=3)
+ ///
+ Unordered,
+
+ ///
+ /// The query name has indexer symbols appended explicit order inserted (a[0]=1&a[1]=2&a[2]=3)
+ ///
+ Ordered,
+
+ ///
+ /// The query is concatenated with a string and merged with one key (a=1,2,3)
+ ///
+ Concatenated
+ }
}
diff --git a/DragonFruit.Common.Data/Parameters/FormParameter.cs b/DragonFruit.Common.Data/Parameters/FormParameter.cs
index 84e2c76..7a35abd 100644
--- a/DragonFruit.Common.Data/Parameters/FormParameter.cs
+++ b/DragonFruit.Common.Data/Parameters/FormParameter.cs
@@ -3,16 +3,31 @@
using System;
+#nullable enable
+
namespace DragonFruit.Common.Data.Parameters
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
public class FormParameter : Attribute, IProperty
{
+ public FormParameter()
+ {
+ }
+
public FormParameter(string name)
{
Name = name;
}
- public string Name { get; }
+ public FormParameter(string name, CollectionConversionMode collectionHandling)
+ : this(name)
+ {
+ CollectionHandling = collectionHandling;
+ }
+
+ public string? Name { get; set; }
+ public CollectionConversionMode? CollectionHandling { get; set; }
+
+ public string? CollectionSeparator { get; set; }
}
}
diff --git a/DragonFruit.Common.Data/Parameters/IProperty.cs b/DragonFruit.Common.Data/Parameters/IProperty.cs
index 49fde2d..2ea4744 100644
--- a/DragonFruit.Common.Data/Parameters/IProperty.cs
+++ b/DragonFruit.Common.Data/Parameters/IProperty.cs
@@ -1,10 +1,16 @@
// DragonFruit.Common Copyright 2020 DragonFruit Network
// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details
+#nullable enable
+
namespace DragonFruit.Common.Data.Parameters
{
public interface IProperty
{
- string Name { get; }
+ string? Name { get; set; }
+
+ CollectionConversionMode? CollectionHandling { get; set; }
+
+ string? CollectionSeparator { get; set; }
}
}
diff --git a/DragonFruit.Common.Data/Parameters/QueryParameter.cs b/DragonFruit.Common.Data/Parameters/QueryParameter.cs
index b3cda73..0fb28bc 100644
--- a/DragonFruit.Common.Data/Parameters/QueryParameter.cs
+++ b/DragonFruit.Common.Data/Parameters/QueryParameter.cs
@@ -3,16 +3,30 @@
using System;
+#nullable enable
+
namespace DragonFruit.Common.Data.Parameters
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
public class QueryParameter : Attribute, IProperty
{
+ public QueryParameter()
+ {
+ }
+
public QueryParameter(string name)
{
Name = name;
}
- public string Name { get; }
+ public QueryParameter(string name, CollectionConversionMode collectionConversionMode)
+ : this(name)
+ {
+ CollectionHandling = collectionConversionMode;
+ }
+
+ public string? Name { get; set; }
+ public CollectionConversionMode? CollectionHandling { get; set; }
+ public string? CollectionSeparator { get; set; }
}
}
diff --git a/DragonFruit.Common.Data/Utils/CultureUtils.cs b/DragonFruit.Common.Data/Utils/CultureUtils.cs
index ae54940..08a268d 100644
--- a/DragonFruit.Common.Data/Utils/CultureUtils.cs
+++ b/DragonFruit.Common.Data/Utils/CultureUtils.cs
@@ -14,5 +14,13 @@ public static CultureInfo DefaultCulture
get => _defaultCulture ?? CultureInfo.InvariantCulture;
set => _defaultCulture = value;
}
+
+ internal static string AsString(this object value, CultureInfo culture = null) => value switch
+ {
+ bool boolVar => boolVar.ToString().ToLower(culture ?? DefaultCulture),
+ null => null,
+
+ _ => value.ToString()
+ };
}
}
diff --git a/DragonFruit.Common.Data/Utils/ParameterUtils.cs b/DragonFruit.Common.Data/Utils/ParameterUtils.cs
index 72de68a..609299c 100644
--- a/DragonFruit.Common.Data/Utils/ParameterUtils.cs
+++ b/DragonFruit.Common.Data/Utils/ParameterUtils.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -12,6 +13,8 @@ namespace DragonFruit.Common.Data.Utils
{
public static class ParameterUtils
{
+ private const string DefaultConcatenationCharacter = ",";
+
///
/// Default to search for matching properties
///
@@ -22,26 +25,45 @@ public static class ParameterUtils
///
internal static IEnumerable> GetParameter(object host, CultureInfo culture) where T : IProperty
{
- var type = typeof(T);
-
foreach (var property in host.GetType().GetProperties(DefaultFlags))
{
- if (!(Attribute.GetCustomAttribute(property, type) is T parameter))
+ if (!property.CanRead || !(Attribute.GetCustomAttribute(property, typeof(T)) is T attribute))
+ {
+ continue;
+ }
+
+ var keyName = attribute.Name ?? property.Name;
+ var propertyValue = property.GetValue(host);
+
+ if (propertyValue == null)
{
+ // ignore null values
continue;
}
- var value = property.GetValue(host, null);
- string convertedValue = value switch
+ // check if the type we've got is an IEnumerable of anything AND we have a valid collection handler mode
+ if (attribute.CollectionHandling.HasValue && typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
- bool boolVar => boolVar.ToString().ToLower(culture),
- null => null,
+ Func, string, CultureInfo, IEnumerable>> entityConverter = attribute.CollectionHandling switch
+ {
+ CollectionConversionMode.Recursive => ApplyRecursiveConversion,
+ CollectionConversionMode.Unordered => ApplyUnorderedConversion,
+ CollectionConversionMode.Ordered => ApplyOrderedConversion,
+ CollectionConversionMode.Concatenated => (a, b, c) => ApplyConcatenation(a, b, c, attribute.CollectionSeparator ?? DefaultConcatenationCharacter),
- _ => value.ToString()
- };
+ _ => throw new ArgumentOutOfRangeException()
+ };
- if (convertedValue != null)
- yield return new KeyValuePair(parameter.Name, convertedValue);
+ foreach (var entry in entityConverter.Invoke((IEnumerable