Skip to content

Commit a2d833b

Browse files
committed
Improve parameter encoding
1 parent 689bc1b commit a2d833b

File tree

8 files changed

+175
-15
lines changed

8 files changed

+175
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Runtime.Serialization;
4+
using Newtonsoft.Json;
5+
using Newtonsoft.Json.Converters;
6+
using FluentAssertions;
7+
using Xunit;
8+
using Stripe.Infrastructure;
9+
10+
namespace Stripe.Tests.Xunit
11+
{
12+
public class encoding_parameters
13+
{
14+
public class TestObject
15+
{
16+
[JsonProperty("an_int")]
17+
public int? AnInt { get; set; }
18+
19+
[JsonProperty("a_string")]
20+
public string AString { get; set; }
21+
22+
[JsonProperty("a_dict")]
23+
public Dictionary<string, string> ADict { get; set; }
24+
25+
[JsonProperty("a_list")]
26+
public int[] AList { get; set; }
27+
}
28+
29+
public class UnencodableObject
30+
{
31+
[JsonProperty("dict_int_keys")]
32+
public Dictionary<int, string> DictIntKeys { get; set; }
33+
34+
[JsonProperty("dict_int_values")]
35+
public Dictionary<string, int> DictIntValues { get; set; }
36+
}
37+
38+
public class TestService : StripeService
39+
{
40+
public TestService() : base(null)
41+
{
42+
}
43+
}
44+
45+
public TestService Service { get; }
46+
47+
public encoding_parameters()
48+
{
49+
Service = new TestService();
50+
}
51+
52+
[Fact]
53+
public void parameters_should_be_encoded()
54+
{
55+
var obj = new TestObject
56+
{
57+
AnInt = 3,
58+
AString = "+foo?",
59+
ADict = new Dictionary<string, string>
60+
{
61+
{"a", "A"},
62+
{"b", "B"},
63+
},
64+
AList = new int[] { 1, 2 },
65+
};
66+
var url = Service.ApplyAllParameters(obj, "", false);
67+
url.Should().Be("?an_int=3&a_string=%2Bfoo%3F&a_dict[a]=A&a_dict[b]=B&a_list[]=1&a_list[]=2");
68+
}
69+
70+
[Fact]
71+
public void empty_strings_should_be_encoded()
72+
{
73+
var obj = new TestObject
74+
{
75+
AString = "",
76+
};
77+
var url = Service.ApplyAllParameters(obj, "", false);
78+
url.Should().Be("?a_string=");
79+
}
80+
81+
[Fact]
82+
public void dictionary_keys_should_be_encoded()
83+
{
84+
var obj = new TestObject
85+
{
86+
ADict = new Dictionary<string, string>
87+
{
88+
{"invoice #", "1234"},
89+
},
90+
};
91+
var url = Service.ApplyAllParameters(obj, "", false);
92+
url.Should().Be("?a_dict[invoice+%23]=1234");
93+
}
94+
95+
[Fact]
96+
public void dictionary_values_should_be_encoded()
97+
{
98+
var obj = new TestObject
99+
{
100+
ADict = new Dictionary<string, string>
101+
{
102+
{"invoice", "#1234"},
103+
},
104+
};
105+
var url = Service.ApplyAllParameters(obj, "", false);
106+
url.Should().Be("?a_dict[invoice]=%231234");
107+
}
108+
109+
[Fact]
110+
public void throws_if_dictionary_keys_are_not_strings()
111+
{
112+
var obj = new UnencodableObject
113+
{
114+
DictIntKeys = new Dictionary<int, string>
115+
{
116+
{1, "one"},
117+
},
118+
};
119+
120+
var exception = Assert.Throws<System.ArgumentException>(() =>
121+
Service.ApplyAllParameters(obj, "", false)
122+
);
123+
124+
exception.Message.Should().Contain("Expected System.String as dictionary key type");
125+
}
126+
127+
[Fact]
128+
public void throws_if_dictionary_values_are_not_strings()
129+
{
130+
var obj = new UnencodableObject
131+
{
132+
DictIntValues = new Dictionary<string, int>
133+
{
134+
{"one", 1},
135+
},
136+
};
137+
138+
var exception = Assert.Throws<System.ArgumentException>(() =>
139+
Service.ApplyAllParameters(obj, "", false)
140+
);
141+
142+
exception.Message.Should().Contain("Expected System.String as dictionary value type");
143+
}
144+
}
145+
}

src/Stripe.Tests.XUnit/products/_fixture.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public products_fixture()
2727
Length = 100,
2828
Weight = 100,
2929
Width = 100,
30-
}
30+
},
31+
Attributes = new string[] { "color", "size" },
3132
};
3233

3334
ProductTwoCreateOptions = new StripeProductCreateOptions

src/Stripe.net/Infrastructure/Middleware/ParserPlugin/ArrayPlugin.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections;
1+
using System;
2+
using System.Collections;
23
using System.Linq;
34
using System.Reflection;
45
using Newtonsoft.Json;
@@ -9,14 +10,14 @@ internal class ArrayPlugin : IParserPlugin
910
{
1011
public bool Parse(ref string requestString, JsonPropertyAttribute attribute, PropertyInfo property, object propertyValue, object propertyParent)
1112
{
12-
if (!attribute.PropertyName.Contains("array:")) return false;
13+
// Check if the property is an array
14+
var type = property.PropertyType;
15+
if (!type.GetTypeInfo().IsArray) return false;
1316

1417
var values = ((IEnumerable) propertyValue).Cast<object>().Select(x => x.ToString()).ToArray();
1518

16-
var key = attribute.PropertyName.Replace("array:", "") + "[]";
17-
1819
foreach (var value in values)
19-
RequestStringBuilder.ApplyParameterToRequestString(ref requestString, key, value);
20+
RequestStringBuilder.ApplyParameterToRequestString(ref requestString, $"{attribute.PropertyName}[]", value);
2021

2122
return true;
2223
}

src/Stripe.net/Infrastructure/Middleware/ParserPlugin/DictionaryPlugin.cs

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using System.Net;
34
using System.Reflection;
45
using Newtonsoft.Json;
56

@@ -9,13 +10,25 @@ internal class DictionaryPlugin : IParserPlugin
910
{
1011
public bool Parse(ref string requestString, JsonPropertyAttribute attribute, PropertyInfo property, object propertyValue, object propertyParent)
1112
{
12-
if (!attribute.PropertyName.Contains("metadata") && !attribute.PropertyName.Contains("fraud_details") && !attribute.PropertyName.Contains("attributes")) return false;
13+
// Check if the property is a Dictionary
14+
var type = property.PropertyType;
15+
if (!type.GetTypeInfo().IsGenericType) return false;
16+
if (type.GetTypeInfo().GetGenericTypeDefinition() != typeof(Dictionary<,>)) return false;
17+
18+
// Ensure that key and value types are both string
19+
var keyType = type.GetTypeInfo().GenericTypeArguments[0];
20+
if (keyType != typeof(string))
21+
throw new System.ArgumentException($"Expected {typeof(string).ToString()} as dictionary key type, got {keyType.ToString()}");
22+
23+
var valueType = type.GetTypeInfo().GenericTypeArguments[1];
24+
if (valueType != typeof(string))
25+
throw new System.ArgumentException($"Expected {typeof(string).ToString()} as dictionary value type, got {valueType.ToString()}");
1326

1427
var dictionary = (Dictionary<string, string>) propertyValue;
1528
if (dictionary == null) return true;
1629

1730
foreach (var key in dictionary.Keys)
18-
RequestStringBuilder.ApplyParameterToRequestString(ref requestString, $"{attribute.PropertyName}[{key}]", dictionary[key]);
31+
RequestStringBuilder.ApplyParameterToRequestString(ref requestString, $"{attribute.PropertyName}[{WebUtility.UrlEncode(key)}]", dictionary[key]);
1932

2033
return true;
2134
}

src/Stripe.net/Services/Orders/StripeOrderListOptions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class StripeOrderListOptions : StripeListOptionsWithCreated
1515
/// <summary>
1616
/// Only return orders with the given IDs.
1717
/// </summary>
18-
[JsonProperty("array:ids")]
18+
[JsonProperty("ids")]
1919
public string[] Ids { get; set; }
2020

2121
/// <summary>
@@ -29,7 +29,7 @@ public class StripeOrderListOptions : StripeListOptionsWithCreated
2929
/// <summary>
3030
/// Only return orders with the given upstream order IDs.
3131
/// </summary>
32-
[JsonProperty("array:upstream_ids")]
32+
[JsonProperty("upstream_ids")]
3333
public string[] UpstreamIds { get; set; }
3434
}
3535
}

src/Stripe.net/Services/Products/StripeProductListOptions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public class StripeProductListOptions : StripeListOptions
77
[JsonProperty("active")]
88
public bool? Active { get; set; }
99

10-
[JsonProperty("array:ids")]
10+
[JsonProperty("ids")]
1111
public string[] Ids { get; set; }
1212

1313
[JsonProperty("shippable")]

src/Stripe.net/Services/Products/StripeProductSharedOptions.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public abstract class StripeProductSharedOptions
1414
/// <summary>
1515
/// A list of up to 5 alphanumeric attributes that each SKU can provide values for (e.g. ["color", "size"]).
1616
/// </summary>
17-
[JsonProperty("array:attributes")]
17+
[JsonProperty("attributes")]
1818
public string[] Attributes { get; set; }
1919

2020
/// <summary>
@@ -26,7 +26,7 @@ public abstract class StripeProductSharedOptions
2626
/// <summary>
2727
/// An array of Connect application names or identifiers that should not be able to order the SKUs for this product.
2828
/// </summary>
29-
[JsonProperty("array:deactivate_on")]
29+
[JsonProperty("deactivate_on")]
3030
public string[] DeactivateOn { get; set; }
3131

3232
/// <summary>
@@ -38,7 +38,7 @@ public abstract class StripeProductSharedOptions
3838
/// <summary>
3939
/// A list of up to 8 URLs of images for this product, meant to be displayable to the customer.
4040
/// </summary>
41-
[JsonProperty("array:images")]
41+
[JsonProperty("images")]
4242
public string[] Images { get; set; }
4343

4444
/// <summary>

src/Stripe.net/Services/Skus/StripeSkuListOptions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class StripeSkuListOptions : StripeListOptions
1111
[JsonProperty("attributes")]
1212
public Dictionary<string, string> Attributes { get; set; }
1313

14-
[JsonProperty("array:ids")]
14+
[JsonProperty("ids")]
1515
public string[] Ids { get; set; }
1616

1717
[JsonProperty("in_stock")]

0 commit comments

Comments
 (0)