Skip to content

Commit ccc536d

Browse files
committed
[dotnet] [bidi] Simplify usage of LocalValue
1 parent dbf3dae commit ccc536d

File tree

2 files changed

+253
-28
lines changed

2 files changed

+253
-28
lines changed

dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs

Lines changed: 219 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@
1717
// under the License.
1818
// </copyright>
1919

20+
using System;
2021
using System.Collections.Generic;
22+
using System.Diagnostics;
23+
using System.Linq;
24+
using System.Numerics;
25+
using System.Text.Json.Nodes;
2126
using System.Text.Json.Serialization;
27+
using System.Text.RegularExpressions;
2228

2329
namespace OpenQA.Selenium.BiDi.Modules.Script;
2430

@@ -38,22 +44,37 @@ namespace OpenQA.Selenium.BiDi.Modules.Script;
3844
[JsonDerivedType(typeof(SetLocalValue), "set")]
3945
public abstract record LocalValue
4046
{
41-
public static implicit operator LocalValue(int value) { return new NumberLocalValue(value); }
42-
public static implicit operator LocalValue(string? value) { return value is null ? new NullLocalValue() : new StringLocalValue(value); }
47+
public static implicit operator LocalValue(bool? value) { return value is bool b ? (b ? True : False) : Null; }
48+
public static implicit operator LocalValue(int? value) { return value is int i ? Number(i) : Null; }
49+
public static implicit operator LocalValue(double? value) { return value is double d ? Number(d) : Null; }
50+
public static implicit operator LocalValue(string? value) { return value is null ? Null : String(value); }
4351

4452
// TODO: Extend converting from types
4553
public static LocalValue ConvertFrom(object? value)
4654
{
4755
switch (value)
4856
{
49-
case LocalValue:
50-
return (LocalValue)value;
57+
case LocalValue localValue:
58+
return localValue;
59+
5160
case null:
52-
return new NullLocalValue();
53-
case int:
54-
return (int)value;
55-
case string:
56-
return (string)value;
61+
return Null;
62+
63+
case bool b:
64+
return b ? True : False;
65+
66+
case int i:
67+
return Number(i);
68+
69+
case double d:
70+
return Number(d);
71+
72+
case string str:
73+
return String(str);
74+
75+
case IEnumerable<object?> list:
76+
return Array(list.Select(ConvertFrom).ToList());
77+
5778
case object:
5879
{
5980
var type = value.GetType();
@@ -67,10 +88,198 @@ public static LocalValue ConvertFrom(object? value)
6788
values.Add([property.Name, ConvertFrom(property.GetValue(value))]);
6889
}
6990

70-
return new ObjectLocalValue(values);
91+
return Object(values);
7192
}
7293
}
7394
}
95+
96+
private static readonly BigInteger MaxDouble = new BigInteger(double.MaxValue);
97+
private static readonly BigInteger MinDouble = new BigInteger(double.MinValue);
98+
99+
public static LocalValue ConvertFrom(JsonNode? node)
100+
{
101+
if (node is null)
102+
{
103+
return Null;
104+
}
105+
106+
switch (node.GetValueKind())
107+
{
108+
case System.Text.Json.JsonValueKind.Null:
109+
return Null;
110+
111+
case System.Text.Json.JsonValueKind.True:
112+
return True;
113+
114+
case System.Text.Json.JsonValueKind.False:
115+
return False;
116+
117+
case System.Text.Json.JsonValueKind.String:
118+
return String(node.ToString());
119+
120+
case System.Text.Json.JsonValueKind.Number:
121+
{
122+
var numberString = node.ToString();
123+
124+
var bigNumber = BigInteger.Parse(numberString);
125+
126+
if (bigNumber > MaxDouble || bigNumber < MinDouble)
127+
{
128+
return BigInt(bigNumber);
129+
}
130+
131+
return Number(double.Parse(numberString));
132+
}
133+
134+
case System.Text.Json.JsonValueKind.Array:
135+
return Array(node.AsArray().Select(ConvertFrom));
136+
137+
case System.Text.Json.JsonValueKind.Object:
138+
var convertedToListForm = node.AsObject().Select(property => new LocalValue[] { String(property.Key), ConvertFrom(property.Value) }).ToList();
139+
return Object(convertedToListForm);
140+
141+
default:
142+
throw new InvalidOperationException("Invalid JSON node");
143+
}
144+
}
145+
146+
public static ChannelLocalValue Channel(ChannelLocalValue.ChannelProperties options)
147+
{
148+
return new ChannelLocalValue(options);
149+
}
150+
151+
public static ArrayLocalValue Array(IEnumerable<LocalValue> values)
152+
{
153+
return new ArrayLocalValue(values);
154+
}
155+
156+
public static SetLocalValue Set(HashSet<LocalValue> values)
157+
{
158+
return new SetLocalValue(values);
159+
}
160+
161+
public static ObjectLocalValue Object(IEnumerable<IEnumerable<LocalValue>> values)
162+
{
163+
return new ObjectLocalValue(values);
164+
}
165+
166+
public static ObjectLocalValue Object(IDictionary<string, LocalValue> values)
167+
{
168+
var convertedValues = values.Select(pair => new LocalValue[] { new StringLocalValue(pair.Key), pair.Value }).ToList();
169+
return new ObjectLocalValue(convertedValues);
170+
}
171+
172+
public static MapLocalValue Map(IEnumerable<IEnumerable<LocalValue>> values)
173+
{
174+
return new MapLocalValue(values);
175+
}
176+
177+
public static MapLocalValue Map(IDictionary<LocalValue, LocalValue> values)
178+
{
179+
var convertedValues = values.Select(PairToList).ToList();
180+
return new MapLocalValue(convertedValues);
181+
}
182+
183+
private static LocalValue[] PairToList(KeyValuePair<LocalValue, LocalValue> pair)
184+
{
185+
return [pair.Key, pair.Value];
186+
}
187+
188+
public static BigIntLocalValue BigInt(BigInteger value)
189+
{
190+
return new BigIntLocalValue(value.ToString());
191+
}
192+
193+
public static DateLocalValue Date(DateTime value)
194+
{
195+
return new DateLocalValue(value.ToString("o"));
196+
}
197+
198+
public static StringLocalValue String(string value)
199+
{
200+
if (value is null)
201+
{
202+
throw new ArgumentNullException(nameof(value), $"string value cannot be null, use a {nameof(NullLocalValue)} value instead");
203+
}
204+
205+
return new StringLocalValue(value);
206+
}
207+
208+
public static NumberLocalValue Number(double value)
209+
{
210+
return new NumberLocalValue(value);
211+
}
212+
213+
public static BooleanLocalValue True { get; } = new BooleanLocalValue(true);
214+
215+
public static BooleanLocalValue False { get; } = new BooleanLocalValue(false);
216+
217+
public static NullLocalValue Null { get; } = new NullLocalValue();
218+
219+
public static UndefinedLocalValue Undefined { get; } = new UndefinedLocalValue();
220+
221+
/// <summary>
222+
/// Converts a .NET Regex into a BiDi Regex
223+
/// </summary>
224+
/// <param name="regex">A .NET Regex.</param>
225+
/// <returns>A BiDi Regex.</returns>
226+
/// <remarks>
227+
/// Note that the .NET regular expression engine does not work the same as the JavaScript engine.
228+
/// To minimize the differences between the two engines, it is recommended to enabled the <see cref="RegexOptions.ECMAScript"/> option.
229+
/// </remarks>
230+
public static RegExpLocalValue Regex(Regex regex)
231+
{
232+
RegexOptions options = regex.Options;
233+
234+
if (options == RegexOptions.None)
235+
{
236+
return new RegExpLocalValue(new RegExpValue(regex.ToString()));
237+
}
238+
239+
string flags = string.Empty;
240+
241+
const RegexOptions NonBacktracking = (RegexOptions)1024;
242+
#if NET8_0_OR_GREATER
243+
Debug.Assert(NonBacktracking == RegexOptions.NonBacktracking);
244+
#endif
245+
246+
const RegexOptions NonApplicableOptions = RegexOptions.Compiled | NonBacktracking;
247+
248+
const RegexOptions UnsupportedOptions =
249+
RegexOptions.ExplicitCapture |
250+
RegexOptions.IgnorePatternWhitespace |
251+
RegexOptions.RightToLeft |
252+
RegexOptions.CultureInvariant;
253+
254+
options &= ~NonApplicableOptions;
255+
256+
if ((options & UnsupportedOptions) != 0)
257+
{
258+
throw new NotSupportedException($"The selected RegEx options are not supported in BiDi: {options & UnsupportedOptions}");
259+
}
260+
261+
if ((options & RegexOptions.IgnoreCase) != 0)
262+
{
263+
flags += "i";
264+
options = options & ~RegexOptions.IgnoreCase;
265+
}
266+
267+
if ((options & RegexOptions.Multiline) != 0)
268+
{
269+
options = options & ~RegexOptions.Multiline;
270+
flags += "m";
271+
}
272+
273+
if ((options & RegexOptions.Singleline) != 0)
274+
{
275+
options = options & ~RegexOptions.Singleline;
276+
flags += "s";
277+
}
278+
279+
Debug.Assert(options == RegexOptions.None);
280+
281+
return new RegExpLocalValue(new RegExpValue(regex.ToString()) { Flags = flags });
282+
}
74283
}
75284

76285
public abstract record PrimitiveProtocolLocalValue : LocalValue;

0 commit comments

Comments
 (0)