Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions APIMatic.Core.Test/Utilities/TestHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@ public void IsJsonObjectProperSubsetOf_ListInRight()
Assert.IsTrue(TestHelper.IsJsonObjectProperSubsetOf(leftObject: leftObject, rightObject: rightObject, checkValues: true, allowExtra: true, isOrdered: false));
}

[Test]
public void IsJsonObjectProperSubsetOf_EmptyObject()
{
string leftObject = "{}";

string rightObject = "{}";

Assert.IsTrue(TestHelper.IsJsonObjectProperSubsetOf(leftObject: leftObject, rightObject: rightObject, checkValues: true, allowExtra: true, isOrdered: false));
}

[Test]
public void IsSameAsFile_GistURL()
{
Expand Down
143 changes: 76 additions & 67 deletions APIMatic.Core/Utilities/CoreHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private static void AppendParameters(StringBuilder queryBuilder, ArraySerializat
// load element value as string
if (pair.Value is ICollection)
{
paramKeyValPair = FlattenCollection(pair.Value as ICollection, arraySerialization, GetSeparator(arraySerialization), true, Uri.EscapeDataString(pair.Key));
paramKeyValPair = FlattenCollection(pair.Value as ICollection, arraySerialization, Uri.EscapeDataString(pair.Key));
}
else
{
Expand Down Expand Up @@ -196,7 +196,7 @@ internal static List<KeyValuePair<string, object>> PrepareFormFieldsFromObject(s
}
else if (value is IList enumerable)
{
PrepareFormFieldsForEnumerable(name, arraySerializationFormat, keys, propInfo, enumerable);
PrepareFormFieldsForEnumerable(name, enumerable, arraySerializationFormat, keys, propInfo);
}
else if (value is Stream || value is JToken || value is Enum)
{
Expand All @@ -205,30 +205,21 @@ internal static List<KeyValuePair<string, object>> PrepareFormFieldsFromObject(s
}
else if (value is IDictionary dictionary)
{
PrepareFormFieldsForDictionary(name, arraySerializationFormat, keys, propInfo, dictionary);
}
else if (value is CoreJsonObject jsonObject)
{
PrepareFormFieldsFromObject(name, RemoveNullValues(jsonObject.GetStoredObject()), arraySerializationFormat, keys, propInfo);
PrepareFormFieldsForDictionary(name, dictionary, arraySerializationFormat, keys, propInfo);
return keys;
}
else if (value is CoreJsonValue jsonValue)
else if (value is CoreJsonObject || value is CoreJsonValue)
{
PrepareFormFieldsFromObject(name, jsonValue.GetStoredObject(), arraySerializationFormat, keys, propInfo);
PrepareFormFieldsFromObject(name, GetProcessedValue(value), arraySerializationFormat, keys, propInfo);
}
else if (!value.GetType().Namespace.StartsWith("System"))
{
PrepareFormFieldsForCustomTypes(name, value, arraySerializationFormat, keys);
}
else if (value is DateTime dateTime)
{
var convertedValue = GetConvertedValue(value, propInfo);
keys.Add(new KeyValuePair<string, object>(name, convertedValue ?? dateTime.ToString(DateTimeFormat)));
}
else
{
keys.Add(new KeyValuePair<string, object>(name, GetProcessedValue(value)));
keys.Add(new KeyValuePair<string, object>(name, GetProcessedValue(value, propInfo)));
}

return keys;
}

Expand Down Expand Up @@ -285,7 +276,7 @@ private static void PrepareFormFieldsForCustomTypes(string name, object value, A
}
}

private static void PrepareFormFieldsForDictionary(string name, ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys, PropertyInfo propInfo, IDictionary dictionary)
private static void PrepareFormFieldsForDictionary(string name, IDictionary dictionary, ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys = null, PropertyInfo propInfo = null)
{
foreach (var sName in dictionary.Keys)
{
Expand All @@ -296,21 +287,10 @@ private static void PrepareFormFieldsForDictionary(string name, ArraySerializati
}
}

private static void PrepareFormFieldsForEnumerable(string name, ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys, PropertyInfo propInfo, IList enumerable)
private static void PrepareFormFieldsForEnumerable(string name, IList enumerable, ArraySerialization arraySerializationFormat, List<KeyValuePair<string, object>> keys, PropertyInfo propInfo)
{
var enumerator = enumerable.GetEnumerator();

var hasNested = false;
while (enumerator.MoveNext())
{
var subValue = enumerator.Current;
if (subValue != null && (subValue is JObject || subValue is IList || subValue is IDictionary || !subValue.GetType().Namespace.StartsWith("System")))
{
hasNested = true;
break;
}
}

bool hasNested = HasCustomeNestedType(enumerator);
int i = 0;
enumerator.Reset();
while (enumerator.MoveNext())
Expand All @@ -330,28 +310,50 @@ private static void PrepareFormFieldsForEnumerable(string name, ArraySerializati
{
continue;
}

PrepareFormFieldsFromObject(fullSubName, subValue, arraySerializationFormat, keys, propInfo);
i++;
}
}

private static object GetProcessedValue(object value)
private static bool HasCustomeNestedType(IEnumerator enumerator)
{
while (enumerator.MoveNext())
{
var subValue = enumerator.Current;
if (subValue != null && (subValue is JObject || subValue is IList || subValue is IDictionary || !subValue.GetType().Namespace.StartsWith("System")))
{
return true;
}
}
return false;
}

private static object GetProcessedValue(object value, PropertyInfo propInfo = null)
{
if (value is Stream)
{
return value;
}

if (value is Enum)
{
return JsonSerialize(value).Trim('\"');
}

if (value is JToken)
{
return value.ToString();
}
if (value is CoreJsonObject jsonObject)
{
return RemoveNullValues(jsonObject.GetStoredObject());
}
if (value is CoreJsonValue jsonValue)
{
return jsonValue.GetStoredObject();
}
if (value is DateTime dateTime)
{
return GetConvertedValue(dateTime, propInfo) ?? dateTime.ToString(DateTimeFormat);
}
return value;
}

Expand Down Expand Up @@ -459,18 +461,17 @@ private static int IndexOf(StringBuilder stringBuilder, string strCheck)
private static string FlattenCollection(
ICollection array,
ArraySerialization fmt,
char separator,
bool urlEncode,
string key = "")
{
StringBuilder builder = new StringBuilder();
string format = GetFormatString(fmt, key, builder);

// append all elements in the array into a string
int index = 0;
char separator = GetSeparator(fmt);
foreach (object element in array)
{
builder.AppendFormat(format, GetElementValue(element, urlEncode), separator, index++);
builder.AppendFormat(format, GetElementValue(element, true), separator, index++);
}

// remove the last separator, if appended
Expand Down Expand Up @@ -545,46 +546,54 @@ private static List<KeyValuePair<string, object>> ProcessQueryParamsForCustomTyp

if (kvp.Value.GetType().Namespace.StartsWith("System"))
{
if (kvp.Value is IList)
{
var list = kvp.Value as IList;

if (list?.Count != 0)
{
var item = list[0];

if (item.GetType().Namespace.StartsWith("System"))
{
// List of scalar type
processedParameters.Add(kvp);
}
else
{
// List of custom type
var innerList = PrepareFormFieldsFromObject(kvp.Key, kvp.Value, arraySerializationFormat: ArraySerialization.Indexed);
innerList = ApplySerializationFormatToScalarArrays(innerList);
processedParameters.AddRange(innerList);
}
}
}
else
{
// Scalar type
processedParameters.Add(kvp);
}
HandlePrimitiveTypes(processedParameters, kvp);
}
else
{
// Custom type
var list = PrepareFormFieldsFromObject(kvp.Key, kvp.Value, arraySerializationFormat: ArraySerialization.Indexed);
list = ApplySerializationFormatToScalarArrays(list);
processedParameters.AddRange(list);
HandleCustomType(processedParameters, kvp);
}
}

return processedParameters;
}

private static void HandleCustomType(List<KeyValuePair<string, object>> processedParameters, KeyValuePair<string, object> kvp)
{
var list = PrepareFormFieldsFromObject(kvp.Key, kvp.Value, arraySerializationFormat: ArraySerialization.Indexed);
list = ApplySerializationFormatToScalarArrays(list);
processedParameters.AddRange(list);
}

private static void HandlePrimitiveTypes(List<KeyValuePair<string, object>> processedParameters, KeyValuePair<string, object> kvp)
{
if (kvp.Value is IList)
{
var list = kvp.Value as IList;

if (list?.Count != 0)
{
var item = list[0];

if (item.GetType().Namespace.StartsWith("System"))
{
// List of scalar type
processedParameters.Add(kvp);
}
else
{
// List of custom type
HandleCustomType(processedParameters, kvp);
}
}
}
else
{
// Scalar type
processedParameters.Add(kvp);
}
}

/// <summary>
/// Apply serialization to scalar arrays in custom objects.
/// </summary>
Expand Down
62 changes: 36 additions & 26 deletions APIMatic.Core/Utilities/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -415,36 +415,49 @@ private static bool IsProperSubsetOf(

if (leftVal is JObject leftSideValue)
{
// If left value is tree, right value should be be tree too
if (!(rightVal is JObject rightSideValue))
{
return false;
}
if (!IsProperSubsetOf(leftSideValue, rightSideValue, checkValues, allowExtra, isOrdered))
{
return false;
}
return IsProperSubsetOfJObject(checkValues, allowExtra, isOrdered, rightVal, leftSideValue);
}
else if (checkValues)
{
// If left value is a primitive, check if it equals right value
if (leftVal is JArray leftJArray)
{
if (!DoesRightValContainsSameItems(leftJArray, rightVal, allowExtra, isOrdered))
{
return false;
}
}
else if (!leftVal.Equals(rightVal))
{
return false;
}
return CheckValuesAreSameOnBothSides(allowExtra, isOrdered, leftVal, rightVal);
}
}

return true;
}

private static bool IsProperSubsetOfJObject(bool checkValues, bool allowExtra, bool isOrdered, object rightVal, JObject leftSideValue)
{
bool isProperSubset = true;
// If left value is tree, right value should be be tree too
if (!(rightVal is JObject rightSideValue))
{
return false;
}
if (!IsProperSubsetOf(leftSideValue, rightSideValue, checkValues, allowExtra, isOrdered))
{
isProperSubset = false;
}
return isProperSubset;
}

private static bool CheckValuesAreSameOnBothSides(bool allowExtra, bool isOrdered, object leftVal, object rightVal)
{
bool isSame = true;
// If left value is a primitive, check if it equals right value
if (leftVal is JArray leftJArray)
{
if (!DoesRightValContainsSameItems(leftJArray, rightVal, allowExtra, isOrdered))
{
isSame = false;
}
}
else if (!leftVal.Equals(rightVal))
{
isSame = false;
}
return isSame;
}

private static bool DoesRightValContainsSameItems(JArray leftJArray, object rightVal, bool allowExtra, bool isOrdered)
{
if (!(rightVal is JArray rightJArray))
Expand All @@ -470,10 +483,7 @@ private static bool DoesRightValContainsSameItems(JArray leftJArray, object righ
JArray remainingLeftList = new JArray(remainingLeftListToken);
var remainingRightListToken = rightJArray.Where(x => !(x is JObject));
JArray remainingRightList = new JArray(remainingRightListToken);
if (!IsListProperSubsetOf(remainingLeftList, remainingRightList, allowExtra, isOrdered))
{
return false;
}
return DoesRightValContainsSameItems(remainingLeftList, remainingRightList, allowExtra, isOrdered);
}
else if (leftJArray.First is JObject && bothArrayContainsJObject)
{
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![Version][nuget-version]][nuget-url]
[![Build & Tests][test-badge]][test-url]
[![Test Coverage][coverage-badge]][coverage-url]
[![Maintainability][maintainability-badge]][maintainability-url]
[![Licence][license-badge]][license-url]

## Introduction
Expand Down Expand Up @@ -32,5 +33,7 @@ This project contains core logic and the utilities for the APIMatic's C# SDK
[test-url]: https://github.com/apimatic/core-lib-csharp/actions/workflows/test.yml
[coverage-badge]: https://api.codeclimate.com/v1/badges/d613a5f73f605369e745/test_coverage
[coverage-url]: https://codeclimate.com/github/apimatic/core-lib-csharp/test_coverage
[maintainability-badge]: https://api.codeclimate.com/v1/badges/d613a5f73f605369e745/maintainability
[maintainability-url]: https://codeclimate.com/github/apimatic/core-lib-csharp/maintainability
[license-badge]: https://img.shields.io/badge/licence-APIMATIC-blue
[license-url]: LICENSE