1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using Google.Protobuf.Reflection;
34 using Google.Protobuf.TestProtos;
35 using Google.Protobuf.WellKnownTypes;
36 using NUnit.Framework;
37 using System;
38 
39 namespace Google.Protobuf
40 {
41     /// <summary>
42     /// Unit tests for JSON parsing.
43     /// </summary>
44     public class JsonParserTest
45     {
46         // Sanity smoke test
47         [Test]
AllTypesRoundtrip()48         public void AllTypesRoundtrip()
49         {
50             AssertRoundtrip(SampleMessages.CreateFullTestAllTypes());
51         }
52 
53         [Test]
Maps()54         public void Maps()
55         {
56             AssertRoundtrip(new TestMap { MapStringString = { { "with spaces", "bar" }, { "a", "b" } } });
57             AssertRoundtrip(new TestMap { MapInt32Int32 = { { 0, 1 }, { 2, 3 } } });
58             AssertRoundtrip(new TestMap { MapBoolBool = { { false, true }, { true, false } } });
59         }
60 
61         [Test]
62         [TestCase(" 1 ")]
63         [TestCase("+1")]
64         [TestCase("1,000")]
65         [TestCase("1.5")]
IntegerMapKeysAreStrict(string keyText)66         public void IntegerMapKeysAreStrict(string keyText)
67         {
68             // Test that integer parsing is strict. We assume that if this is correct for int32,
69             // it's correct for other numeric key types.
70             var json = "{ \"mapInt32Int32\": { \"" + keyText + "\" : \"1\" } }";
71             Assert.Throws<InvalidProtocolBufferException>(() => JsonParser.Default.Parse<TestMap>(json));
72         }
73 
74         [Test]
OriginalFieldNameAccepted()75         public void OriginalFieldNameAccepted()
76         {
77             var json = "{ \"single_int32\": 10 }";
78             var expected = new TestAllTypes { SingleInt32 = 10 };
79             Assert.AreEqual(expected, TestAllTypes.Parser.ParseJson(json));
80         }
81 
82         [Test]
SourceContextRoundtrip()83         public void SourceContextRoundtrip()
84         {
85             AssertRoundtrip(new SourceContext { FileName = "foo.proto" });
86         }
87 
88         [Test]
SingularWrappers_DefaultNonNullValues()89         public void SingularWrappers_DefaultNonNullValues()
90         {
91             var message = new TestWellKnownTypes
92             {
93                 StringField = "",
94                 BytesField = ByteString.Empty,
95                 BoolField = false,
96                 FloatField = 0f,
97                 DoubleField = 0d,
98                 Int32Field = 0,
99                 Int64Field = 0,
100                 Uint32Field = 0,
101                 Uint64Field = 0
102             };
103             AssertRoundtrip(message);
104         }
105 
106         [Test]
SingularWrappers_NonDefaultValues()107         public void SingularWrappers_NonDefaultValues()
108         {
109             var message = new TestWellKnownTypes
110             {
111                 StringField = "x",
112                 BytesField = ByteString.CopyFrom(1, 2, 3),
113                 BoolField = true,
114                 FloatField = 12.5f,
115                 DoubleField = 12.25d,
116                 Int32Field = 1,
117                 Int64Field = 2,
118                 Uint32Field = 3,
119                 Uint64Field = 4
120             };
121             AssertRoundtrip(message);
122         }
123 
124         [Test]
SingularWrappers_ExplicitNulls()125         public void SingularWrappers_ExplicitNulls()
126         {
127             // When we parse the "valueField": null part, we remember it... basically, it's one case
128             // where explicit default values don't fully roundtrip.
129             var message = new TestWellKnownTypes { ValueField = Value.ForNull() };
130             var json = new JsonFormatter(new JsonFormatter.Settings(true)).Format(message);
131             var parsed = JsonParser.Default.Parse<TestWellKnownTypes>(json);
132             Assert.AreEqual(message, parsed);
133         }
134 
135         [Test]
136         [TestCase(typeof(BoolValue), "true", true)]
137         [TestCase(typeof(Int32Value), "32", 32)]
138         [TestCase(typeof(Int64Value), "32", 32L)]
139         [TestCase(typeof(Int64Value), "\"32\"", 32L)]
140         [TestCase(typeof(UInt32Value), "32", 32U)]
141         [TestCase(typeof(UInt64Value), "\"32\"", 32UL)]
142         [TestCase(typeof(UInt64Value), "32", 32UL)]
143         [TestCase(typeof(StringValue), "\"foo\"", "foo")]
144         [TestCase(typeof(FloatValue), "1.5", 1.5f)]
145         [TestCase(typeof(DoubleValue), "1.5", 1.5d)]
Wrappers_Standalone(System.Type wrapperType, string json, object expectedValue)146         public void Wrappers_Standalone(System.Type wrapperType, string json, object expectedValue)
147         {
148             IMessage parsed = (IMessage)Activator.CreateInstance(wrapperType);
149             IMessage expected = (IMessage)Activator.CreateInstance(wrapperType);
150             JsonParser.Default.Merge(parsed, "null");
151             Assert.AreEqual(expected, parsed);
152 
153             JsonParser.Default.Merge(parsed, json);
154             expected.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.SetValue(expected, expectedValue);
155             Assert.AreEqual(expected, parsed);
156         }
157 
158         [Test]
ExplicitNullValue()159         public void ExplicitNullValue()
160         {
161             string json = "{\"valueField\": null}";
162             var message = JsonParser.Default.Parse<TestWellKnownTypes>(json);
163             Assert.AreEqual(new TestWellKnownTypes { ValueField = Value.ForNull() }, message);
164         }
165 
166         [Test]
BytesWrapper_Standalone()167         public void BytesWrapper_Standalone()
168         {
169             ByteString data = ByteString.CopyFrom(1, 2, 3);
170             // Can't do this with attributes...
171             var parsed = JsonParser.Default.Parse<BytesValue>(WrapInQuotes(data.ToBase64()));
172             var expected = new BytesValue { Value = data };
173             Assert.AreEqual(expected, parsed);
174         }
175 
176         [Test]
RepeatedWrappers()177         public void RepeatedWrappers()
178         {
179             var message = new RepeatedWellKnownTypes
180             {
181                 BoolField = { true, false },
182                 BytesField = { ByteString.CopyFrom(1, 2, 3), ByteString.CopyFrom(4, 5, 6), ByteString.Empty },
183                 DoubleField = { 12.5, -1.5, 0d },
184                 FloatField = { 123.25f, -20f, 0f },
185                 Int32Field = { int.MaxValue, int.MinValue, 0 },
186                 Int64Field = { long.MaxValue, long.MinValue, 0L },
187                 StringField = { "First", "Second", "" },
188                 Uint32Field = { uint.MaxValue, uint.MinValue, 0U },
189                 Uint64Field = { ulong.MaxValue, ulong.MinValue, 0UL },
190             };
191             AssertRoundtrip(message);
192         }
193 
194         [Test]
RepeatedField_NullElementProhibited()195         public void RepeatedField_NullElementProhibited()
196         {
197             string json = "{ \"repeated_foreign_message\": [null] }";
198             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
199         }
200 
201         [Test]
RepeatedField_NullOverallValueAllowed()202         public void RepeatedField_NullOverallValueAllowed()
203         {
204             string json = "{ \"repeated_foreign_message\": null }";
205             Assert.AreEqual(new TestAllTypes(), TestAllTypes.Parser.ParseJson(json));
206         }
207 
208         [Test]
209         [TestCase("{ \"mapInt32Int32\": { \"10\": null }")]
210         [TestCase("{ \"mapStringString\": { \"abc\": null }")]
211         [TestCase("{ \"mapInt32ForeignMessage\": { \"10\": null }")]
MapField_NullValueProhibited(string json)212         public void MapField_NullValueProhibited(string json)
213         {
214             Assert.Throws<InvalidProtocolBufferException>(() => TestMap.Parser.ParseJson(json));
215         }
216 
217         [Test]
MapField_NullOverallValueAllowed()218         public void MapField_NullOverallValueAllowed()
219         {
220             string json = "{ \"mapInt32Int32\": null }";
221             Assert.AreEqual(new TestMap(), TestMap.Parser.ParseJson(json));
222         }
223 
224         [Test]
IndividualWrapperTypes()225         public void IndividualWrapperTypes()
226         {
227             Assert.AreEqual(new StringValue { Value = "foo" }, StringValue.Parser.ParseJson("\"foo\""));
228             Assert.AreEqual(new Int32Value { Value = 1 }, Int32Value.Parser.ParseJson("1"));
229             // Can parse strings directly too
230             Assert.AreEqual(new Int32Value { Value = 1 }, Int32Value.Parser.ParseJson("\"1\""));
231         }
232 
233         private static void AssertRoundtrip<T>(T message) where T : IMessage<T>, new()
234         {
235             var clone = message.Clone();
236             var json = JsonFormatter.Default.Format(message);
237             var parsed = JsonParser.Default.Parse<T>(json);
238             Assert.AreEqual(clone, parsed);
239         }
240 
241         [Test]
242         [TestCase("0", 0)]
243         [TestCase("-0", 0)] // Not entirely clear whether we intend to allow this...
244         [TestCase("1", 1)]
245         [TestCase("-1", -1)]
246         [TestCase("2147483647", 2147483647)]
247         [TestCase("-2147483648", -2147483648)]
StringToInt32_Valid(string jsonValue, int expectedParsedValue)248         public void StringToInt32_Valid(string jsonValue, int expectedParsedValue)
249         {
250             string json = "{ \"singleInt32\": \"" + jsonValue + "\"}";
251             var parsed = TestAllTypes.Parser.ParseJson(json);
252             Assert.AreEqual(expectedParsedValue, parsed.SingleInt32);
253         }
254 
255         [Test]
256         [TestCase("+0")]
257         [TestCase(" 1")]
258         [TestCase("1 ")]
259         [TestCase("00")]
260         [TestCase("-00")]
261         [TestCase("--1")]
262         [TestCase("+1")]
263         [TestCase("1.5")]
264         [TestCase("1e10")]
265         [TestCase("2147483648")]
266         [TestCase("-2147483649")]
StringToInt32_Invalid(string jsonValue)267         public void StringToInt32_Invalid(string jsonValue)
268         {
269             string json = "{ \"singleInt32\": \"" + jsonValue + "\"}";
270             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
271         }
272 
273         [Test]
274         [TestCase("0", 0U)]
275         [TestCase("1", 1U)]
276         [TestCase("4294967295", 4294967295U)]
StringToUInt32_Valid(string jsonValue, uint expectedParsedValue)277         public void StringToUInt32_Valid(string jsonValue, uint expectedParsedValue)
278         {
279             string json = "{ \"singleUint32\": \"" + jsonValue + "\"}";
280             var parsed = TestAllTypes.Parser.ParseJson(json);
281             Assert.AreEqual(expectedParsedValue, parsed.SingleUint32);
282         }
283 
284         // Assume that anything non-bounds-related is covered in the Int32 case
285         [Test]
286         [TestCase("-1")]
287         [TestCase("4294967296")]
StringToUInt32_Invalid(string jsonValue)288         public void StringToUInt32_Invalid(string jsonValue)
289         {
290             string json = "{ \"singleUint32\": \"" + jsonValue + "\"}";
291             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
292         }
293 
294         [Test]
295         [TestCase("0", 0L)]
296         [TestCase("1", 1L)]
297         [TestCase("-1", -1L)]
298         [TestCase("9223372036854775807", 9223372036854775807)]
299         [TestCase("-9223372036854775808", -9223372036854775808)]
StringToInt64_Valid(string jsonValue, long expectedParsedValue)300         public void StringToInt64_Valid(string jsonValue, long expectedParsedValue)
301         {
302             string json = "{ \"singleInt64\": \"" + jsonValue + "\"}";
303             var parsed = TestAllTypes.Parser.ParseJson(json);
304             Assert.AreEqual(expectedParsedValue, parsed.SingleInt64);
305         }
306 
307         // Assume that anything non-bounds-related is covered in the Int32 case
308         [Test]
309         [TestCase("-9223372036854775809")]
310         [TestCase("9223372036854775808")]
StringToInt64_Invalid(string jsonValue)311         public void StringToInt64_Invalid(string jsonValue)
312         {
313             string json = "{ \"singleInt64\": \"" + jsonValue + "\"}";
314             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
315         }
316 
317         [Test]
318         [TestCase("0", 0UL)]
319         [TestCase("1", 1UL)]
320         [TestCase("18446744073709551615", 18446744073709551615)]
StringToUInt64_Valid(string jsonValue, ulong expectedParsedValue)321         public void StringToUInt64_Valid(string jsonValue, ulong expectedParsedValue)
322         {
323             string json = "{ \"singleUint64\": \"" + jsonValue + "\"}";
324             var parsed = TestAllTypes.Parser.ParseJson(json);
325             Assert.AreEqual(expectedParsedValue, parsed.SingleUint64);
326         }
327 
328         // Assume that anything non-bounds-related is covered in the Int32 case
329         [Test]
330         [TestCase("-1")]
331         [TestCase("18446744073709551616")]
StringToUInt64_Invalid(string jsonValue)332         public void StringToUInt64_Invalid(string jsonValue)
333         {
334             string json = "{ \"singleUint64\": \"" + jsonValue + "\"}";
335             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
336         }
337 
338         [Test]
339         [TestCase("0", 0d)]
340         [TestCase("1", 1d)]
341         [TestCase("1.000000", 1d)]
342         [TestCase("1.0000000000000000000000001", 1d)] // We don't notice that we haven't preserved the exact value
343         [TestCase("-1", -1d)]
344         [TestCase("1e1", 10d)]
345         [TestCase("1e01", 10d)] // Leading decimals are allowed in exponents
346         [TestCase("1E1", 10d)] // Either case is fine
347         [TestCase("-1e1", -10d)]
348         [TestCase("1.5e1", 15d)]
349         [TestCase("-1.5e1", -15d)]
350         [TestCase("15e-1", 1.5d)]
351         [TestCase("-15e-1", -1.5d)]
352         [TestCase("1.79769e308", 1.79769e308)]
353         [TestCase("-1.79769e308", -1.79769e308)]
354         [TestCase("Infinity", double.PositiveInfinity)]
355         [TestCase("-Infinity", double.NegativeInfinity)]
356         [TestCase("NaN", double.NaN)]
StringToDouble_Valid(string jsonValue, double expectedParsedValue)357         public void StringToDouble_Valid(string jsonValue, double expectedParsedValue)
358         {
359             string json = "{ \"singleDouble\": \"" + jsonValue + "\"}";
360             var parsed = TestAllTypes.Parser.ParseJson(json);
361             Assert.AreEqual(expectedParsedValue, parsed.SingleDouble);
362         }
363 
364         [Test]
365         [TestCase("1.7977e308")]
366         [TestCase("-1.7977e308")]
367         [TestCase("1e309")]
368         [TestCase("1,0")]
369         [TestCase("1.0.0")]
370         [TestCase("+1")]
371         [TestCase("00")]
372         [TestCase("01")]
373         [TestCase("-00")]
374         [TestCase("-01")]
375         [TestCase("--1")]
376         [TestCase(" Infinity")]
377         [TestCase(" -Infinity")]
378         [TestCase("NaN ")]
379         [TestCase("Infinity ")]
380         [TestCase("-Infinity ")]
381         [TestCase(" NaN")]
382         [TestCase("INFINITY")]
383         [TestCase("nan")]
384         [TestCase("\u00BD")] // 1/2 as a single Unicode character. Just sanity checking...
StringToDouble_Invalid(string jsonValue)385         public void StringToDouble_Invalid(string jsonValue)
386         {
387             string json = "{ \"singleDouble\": \"" + jsonValue + "\"}";
388             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
389         }
390 
391         [Test]
392         [TestCase("0", 0f)]
393         [TestCase("1", 1f)]
394         [TestCase("1.000000", 1f)]
395         [TestCase("-1", -1f)]
396         [TestCase("3.402823e38", 3.402823e38f)]
397         [TestCase("-3.402823e38", -3.402823e38f)]
398         [TestCase("1.5e1", 15f)]
399         [TestCase("15e-1", 1.5f)]
StringToFloat_Valid(string jsonValue, float expectedParsedValue)400         public void StringToFloat_Valid(string jsonValue, float expectedParsedValue)
401         {
402             string json = "{ \"singleFloat\": \"" + jsonValue + "\"}";
403             var parsed = TestAllTypes.Parser.ParseJson(json);
404             Assert.AreEqual(expectedParsedValue, parsed.SingleFloat);
405         }
406 
407         [Test]
408         [TestCase("3.402824e38")]
409         [TestCase("-3.402824e38")]
410         [TestCase("1,0")]
411         [TestCase("1.0.0")]
412         [TestCase("+1")]
413         [TestCase("00")]
414         [TestCase("--1")]
StringToFloat_Invalid(string jsonValue)415         public void StringToFloat_Invalid(string jsonValue)
416         {
417             string json = "{ \"singleFloat\": \"" + jsonValue + "\"}";
418             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
419         }
420 
421         [Test]
422         [TestCase("0", 0)]
423         [TestCase("-0", 0)] // Not entirely clear whether we intend to allow this...
424         [TestCase("1", 1)]
425         [TestCase("-1", -1)]
426         [TestCase("2147483647", 2147483647)]
427         [TestCase("-2147483648", -2147483648)]
428         [TestCase("1e1", 10)]
429         [TestCase("-1e1", -10)]
430         [TestCase("10.00", 10)]
431         [TestCase("-10.00", -10)]
NumberToInt32_Valid(string jsonValue, int expectedParsedValue)432         public void NumberToInt32_Valid(string jsonValue, int expectedParsedValue)
433         {
434             string json = "{ \"singleInt32\": " + jsonValue + "}";
435             var parsed = TestAllTypes.Parser.ParseJson(json);
436             Assert.AreEqual(expectedParsedValue, parsed.SingleInt32);
437         }
438 
439         [Test]
440         [TestCase("+0", typeof(InvalidJsonException))]
441         [TestCase("00", typeof(InvalidJsonException))]
442         [TestCase("-00", typeof(InvalidJsonException))]
443         [TestCase("--1", typeof(InvalidJsonException))]
444         [TestCase("+1", typeof(InvalidJsonException))]
445         [TestCase("1.5", typeof(InvalidProtocolBufferException))]
446         // Value is out of range
447         [TestCase("1e10", typeof(InvalidProtocolBufferException))]
448         [TestCase("2147483648", typeof(InvalidProtocolBufferException))]
449         [TestCase("-2147483649", typeof(InvalidProtocolBufferException))]
NumberToInt32_Invalid(string jsonValue, System.Type expectedExceptionType)450         public void NumberToInt32_Invalid(string jsonValue, System.Type expectedExceptionType)
451         {
452             string json = "{ \"singleInt32\": " + jsonValue + "}";
453             Assert.Throws(expectedExceptionType, () => TestAllTypes.Parser.ParseJson(json));
454         }
455 
456         [Test]
457         [TestCase("0", 0U)]
458         [TestCase("1", 1U)]
459         [TestCase("4294967295", 4294967295U)]
NumberToUInt32_Valid(string jsonValue, uint expectedParsedValue)460         public void NumberToUInt32_Valid(string jsonValue, uint expectedParsedValue)
461         {
462             string json = "{ \"singleUint32\": " + jsonValue + "}";
463             var parsed = TestAllTypes.Parser.ParseJson(json);
464             Assert.AreEqual(expectedParsedValue, parsed.SingleUint32);
465         }
466 
467         // Assume that anything non-bounds-related is covered in the Int32 case
468         [Test]
469         [TestCase("-1")]
470         [TestCase("4294967296")]
NumberToUInt32_Invalid(string jsonValue)471         public void NumberToUInt32_Invalid(string jsonValue)
472         {
473             string json = "{ \"singleUint32\": " + jsonValue + "}";
474             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
475         }
476 
477         [Test]
478         [TestCase("0", 0L)]
479         [TestCase("1", 1L)]
480         [TestCase("-1", -1L)]
481         // long.MaxValue isn't actually representable as a double. This string value is the highest
482         // representable value which isn't greater than long.MaxValue.
483         [TestCase("9223372036854774784", 9223372036854774784)]
484         [TestCase("-9223372036854775808", -9223372036854775808)]
NumberToInt64_Valid(string jsonValue, long expectedParsedValue)485         public void NumberToInt64_Valid(string jsonValue, long expectedParsedValue)
486         {
487             string json = "{ \"singleInt64\": " + jsonValue + "}";
488             var parsed = TestAllTypes.Parser.ParseJson(json);
489             Assert.AreEqual(expectedParsedValue, parsed.SingleInt64);
490         }
491 
492         // Assume that anything non-bounds-related is covered in the Int32 case
493         [Test]
494         [TestCase("9223372036854775808")]
495         // Theoretical bound would be -9223372036854775809, but when that is parsed to a double
496         // we end up with the exact value of long.MinValue due to lack of precision. The value here
497         // is the "next double down".
498         [TestCase("-9223372036854780000")]
NumberToInt64_Invalid(string jsonValue)499         public void NumberToInt64_Invalid(string jsonValue)
500         {
501             string json = "{ \"singleInt64\": " + jsonValue + "}";
502             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
503         }
504 
505         [Test]
506         [TestCase("0", 0UL)]
507         [TestCase("1", 1UL)]
508         // ulong.MaxValue isn't representable as a double. This value is the largest double within
509         // the range of ulong.
510         [TestCase("18446744073709549568", 18446744073709549568UL)]
NumberToUInt64_Valid(string jsonValue, ulong expectedParsedValue)511         public void NumberToUInt64_Valid(string jsonValue, ulong expectedParsedValue)
512         {
513             string json = "{ \"singleUint64\": " + jsonValue + "}";
514             var parsed = TestAllTypes.Parser.ParseJson(json);
515             Assert.AreEqual(expectedParsedValue, parsed.SingleUint64);
516         }
517 
518         // Assume that anything non-bounds-related is covered in the Int32 case
519         [Test]
520         [TestCase("-1")]
521         [TestCase("18446744073709551616")]
NumberToUInt64_Invalid(string jsonValue)522         public void NumberToUInt64_Invalid(string jsonValue)
523         {
524             string json = "{ \"singleUint64\": " + jsonValue + "}";
525             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
526         }
527 
528         [Test]
529         [TestCase("0", 0d)]
530         [TestCase("1", 1d)]
531         [TestCase("1.000000", 1d)]
532         [TestCase("1.0000000000000000000000001", 1d)] // We don't notice that we haven't preserved the exact value
533         [TestCase("-1", -1d)]
534         [TestCase("1e1", 10d)]
535         [TestCase("1e01", 10d)] // Leading decimals are allowed in exponents
536         [TestCase("1E1", 10d)] // Either case is fine
537         [TestCase("-1e1", -10d)]
538         [TestCase("1.5e1", 15d)]
539         [TestCase("-1.5e1", -15d)]
540         [TestCase("15e-1", 1.5d)]
541         [TestCase("-15e-1", -1.5d)]
542         [TestCase("1.79769e308", 1.79769e308)]
543         [TestCase("-1.79769e308", -1.79769e308)]
NumberToDouble_Valid(string jsonValue, double expectedParsedValue)544         public void NumberToDouble_Valid(string jsonValue, double expectedParsedValue)
545         {
546             string json = "{ \"singleDouble\": " + jsonValue + "}";
547             var parsed = TestAllTypes.Parser.ParseJson(json);
548             Assert.AreEqual(expectedParsedValue, parsed.SingleDouble);
549         }
550 
551         [Test]
552         [TestCase("1.7977e308")]
553         [TestCase("-1.7977e308")]
554         [TestCase("1e309")]
555         [TestCase("1,0")]
556         [TestCase("1.0.0")]
557         [TestCase("+1")]
558         [TestCase("00")]
559         [TestCase("--1")]
560         [TestCase("\u00BD")] // 1/2 as a single Unicode character. Just sanity checking...
NumberToDouble_Invalid(string jsonValue)561         public void NumberToDouble_Invalid(string jsonValue)
562         {
563             string json = "{ \"singleDouble\": " + jsonValue + "}";
564             Assert.Throws<InvalidJsonException>(() => TestAllTypes.Parser.ParseJson(json));
565         }
566 
567         [Test]
568         [TestCase("0", 0f)]
569         [TestCase("1", 1f)]
570         [TestCase("1.000000", 1f)]
571         [TestCase("-1", -1f)]
572         [TestCase("3.402823e38", 3.402823e38f)]
573         [TestCase("-3.402823e38", -3.402823e38f)]
574         [TestCase("1.5e1", 15f)]
575         [TestCase("15e-1", 1.5f)]
NumberToFloat_Valid(string jsonValue, float expectedParsedValue)576         public void NumberToFloat_Valid(string jsonValue, float expectedParsedValue)
577         {
578             string json = "{ \"singleFloat\": " + jsonValue + "}";
579             var parsed = TestAllTypes.Parser.ParseJson(json);
580             Assert.AreEqual(expectedParsedValue, parsed.SingleFloat);
581         }
582 
583         [Test]
584         [TestCase("3.402824e38", typeof(InvalidProtocolBufferException))]
585         [TestCase("-3.402824e38", typeof(InvalidProtocolBufferException))]
586         [TestCase("1,0", typeof(InvalidJsonException))]
587         [TestCase("1.0.0", typeof(InvalidJsonException))]
588         [TestCase("+1", typeof(InvalidJsonException))]
589         [TestCase("00", typeof(InvalidJsonException))]
590         [TestCase("--1", typeof(InvalidJsonException))]
NumberToFloat_Invalid(string jsonValue, System.Type expectedExceptionType)591         public void NumberToFloat_Invalid(string jsonValue, System.Type expectedExceptionType)
592         {
593             string json = "{ \"singleFloat\": " + jsonValue + "}";
594             Assert.Throws(expectedExceptionType, () => TestAllTypes.Parser.ParseJson(json));
595         }
596 
597         // The simplest way of testing that the value has parsed correctly is to reformat it,
598         // as we trust the formatting. In many cases that will give the same result as the input,
599         // so in those cases we accept an expectedFormatted value of null. Sometimes the results
600         // will be different though, due to a different number of digits being provided.
601         [Test]
602         // Z offset
603         [TestCase("2015-10-09T14:46:23.123456789Z", null)]
604         [TestCase("2015-10-09T14:46:23.123456Z", null)]
605         [TestCase("2015-10-09T14:46:23.123Z", null)]
606         [TestCase("2015-10-09T14:46:23Z", null)]
607         [TestCase("2015-10-09T14:46:23.123456000Z", "2015-10-09T14:46:23.123456Z")]
608         [TestCase("2015-10-09T14:46:23.1234560Z", "2015-10-09T14:46:23.123456Z")]
609         [TestCase("2015-10-09T14:46:23.123000000Z", "2015-10-09T14:46:23.123Z")]
610         [TestCase("2015-10-09T14:46:23.1230Z", "2015-10-09T14:46:23.123Z")]
611         [TestCase("2015-10-09T14:46:23.00Z", "2015-10-09T14:46:23Z")]
612 
613         // +00:00 offset
614         [TestCase("2015-10-09T14:46:23.123456789+00:00", "2015-10-09T14:46:23.123456789Z")]
615         [TestCase("2015-10-09T14:46:23.123456+00:00", "2015-10-09T14:46:23.123456Z")]
616         [TestCase("2015-10-09T14:46:23.123+00:00", "2015-10-09T14:46:23.123Z")]
617         [TestCase("2015-10-09T14:46:23+00:00", "2015-10-09T14:46:23Z")]
618         [TestCase("2015-10-09T14:46:23.123456000+00:00", "2015-10-09T14:46:23.123456Z")]
619         [TestCase("2015-10-09T14:46:23.1234560+00:00", "2015-10-09T14:46:23.123456Z")]
620         [TestCase("2015-10-09T14:46:23.123000000+00:00", "2015-10-09T14:46:23.123Z")]
621         [TestCase("2015-10-09T14:46:23.1230+00:00", "2015-10-09T14:46:23.123Z")]
622         [TestCase("2015-10-09T14:46:23.00+00:00", "2015-10-09T14:46:23Z")]
623 
624         // Other offsets (assume by now that the subsecond handling is okay)
625         [TestCase("2015-10-09T15:46:23.123456789+01:00", "2015-10-09T14:46:23.123456789Z")]
626         [TestCase("2015-10-09T13:46:23.123456789-01:00", "2015-10-09T14:46:23.123456789Z")]
627         [TestCase("2015-10-09T15:16:23.123456789+00:30", "2015-10-09T14:46:23.123456789Z")]
628         [TestCase("2015-10-09T14:16:23.123456789-00:30", "2015-10-09T14:46:23.123456789Z")]
629         [TestCase("2015-10-09T16:31:23.123456789+01:45", "2015-10-09T14:46:23.123456789Z")]
630         [TestCase("2015-10-09T13:01:23.123456789-01:45", "2015-10-09T14:46:23.123456789Z")]
631         [TestCase("2015-10-10T08:46:23.123456789+18:00", "2015-10-09T14:46:23.123456789Z")]
632         [TestCase("2015-10-08T20:46:23.123456789-18:00", "2015-10-09T14:46:23.123456789Z")]
633 
634         // Leap years and min/max
635         [TestCase("2016-02-29T14:46:23.123456789Z", null)]
636         [TestCase("2000-02-29T14:46:23.123456789Z", null)]
637         [TestCase("0001-01-01T00:00:00Z", null)]
638         [TestCase("9999-12-31T23:59:59.999999999Z", null)]
Timestamp_Valid(string jsonValue, string expectedFormatted)639         public void Timestamp_Valid(string jsonValue, string expectedFormatted)
640         {
641             expectedFormatted = expectedFormatted ?? jsonValue;
642             string json = WrapInQuotes(jsonValue);
643             var parsed = Timestamp.Parser.ParseJson(json);
644             Assert.AreEqual(WrapInQuotes(expectedFormatted), parsed.ToString());
645         }
646 
647         [Test]
648         [TestCase("2015-10-09 14:46:23.123456789Z", Description = "No T between date and time")]
649         [TestCase("2015/10/09T14:46:23.123456789Z", Description = "Wrong date separators")]
650         [TestCase("2015-10-09T14.46.23.123456789Z", Description = "Wrong time separators")]
651         [TestCase("2015-10-09T14:46:23,123456789Z", Description = "Wrong fractional second separators (valid ISO-8601 though)")]
652         [TestCase(" 2015-10-09T14:46:23.123456789Z", Description = "Whitespace at start")]
653         [TestCase("2015-10-09T14:46:23.123456789Z ", Description = "Whitespace at end")]
654         [TestCase("2015-10-09T14:46:23.1234567890", Description = "Too many digits")]
655         [TestCase("2015-10-09T14:46:23.123456789", Description = "No offset")]
656         [TestCase("2015-13-09T14:46:23.123456789Z", Description = "Invalid month")]
657         [TestCase("2015-10-32T14:46:23.123456789Z", Description = "Invalid day")]
658         [TestCase("2015-10-09T24:00:00.000000000Z", Description = "Invalid hour (valid ISO-8601 though)")]
659         [TestCase("2015-10-09T14:60:23.123456789Z", Description = "Invalid minutes")]
660         [TestCase("2015-10-09T14:46:60.123456789Z", Description = "Invalid seconds")]
661         [TestCase("2015-10-09T14:46:23.123456789+18:01", Description = "Offset too large (positive)")]
662         [TestCase("2015-10-09T14:46:23.123456789-18:01", Description = "Offset too large (negative)")]
663         [TestCase("2015-10-09T14:46:23.123456789-00:00", Description = "Local offset (-00:00) makes no sense here")]
664         [TestCase("0001-01-01T00:00:00+00:01", Description = "Value before earliest when offset applied")]
665         [TestCase("9999-12-31T23:59:59.999999999-00:01", Description = "Value after latest when offset applied")]
666         [TestCase("2100-02-29T14:46:23.123456789Z", Description = "Feb 29th on a non-leap-year")]
Timestamp_Invalid(string jsonValue)667         public void Timestamp_Invalid(string jsonValue)
668         {
669             string json = WrapInQuotes(jsonValue);
670             Assert.Throws<InvalidProtocolBufferException>(() => Timestamp.Parser.ParseJson(json));
671         }
672 
673         [Test]
StructValue_Null()674         public void StructValue_Null()
675         {
676             Assert.AreEqual(new Value { NullValue = 0 }, Value.Parser.ParseJson("null"));
677         }
678 
679         [Test]
StructValue_String()680         public void StructValue_String()
681         {
682             Assert.AreEqual(new Value { StringValue = "hi" }, Value.Parser.ParseJson("\"hi\""));
683         }
684 
685         [Test]
StructValue_Bool()686         public void StructValue_Bool()
687         {
688             Assert.AreEqual(new Value { BoolValue = true }, Value.Parser.ParseJson("true"));
689             Assert.AreEqual(new Value { BoolValue = false }, Value.Parser.ParseJson("false"));
690         }
691 
692         [Test]
StructValue_List()693         public void StructValue_List()
694         {
695             Assert.AreEqual(Value.ForList(Value.ForNumber(1), Value.ForString("x")), Value.Parser.ParseJson("[1, \"x\"]"));
696         }
697 
698         [Test]
ParseListValue()699         public void ParseListValue()
700         {
701             Assert.AreEqual(new ListValue { Values = { Value.ForNumber(1), Value.ForString("x") } }, ListValue.Parser.ParseJson("[1, \"x\"]"));
702         }
703 
704         [Test]
StructValue_Struct()705         public void StructValue_Struct()
706         {
707             Assert.AreEqual(
708                 Value.ForStruct(new Struct { Fields = { { "x", Value.ForNumber(1) }, { "y", Value.ForString("z") } } }),
709                 Value.Parser.ParseJson("{ \"x\": 1, \"y\": \"z\" }"));
710         }
711 
712         [Test]
ParseStruct()713         public void ParseStruct()
714         {
715             Assert.AreEqual(new Struct { Fields = { { "x", Value.ForNumber(1) }, { "y", Value.ForString("z") } } },
716                 Struct.Parser.ParseJson("{ \"x\": 1, \"y\": \"z\" }"));
717         }
718 
719         // TODO for duration parsing: upper and lower bounds.
720         // +/- 315576000000 seconds
721 
722         [Test]
723         [TestCase("1.123456789s", null)]
724         [TestCase("1.123456s", null)]
725         [TestCase("1.123s", null)]
726         [TestCase("1.12300s", "1.123s")]
727         [TestCase("1.12345s", "1.123450s")]
728         [TestCase("1s", null)]
729         [TestCase("-1.123456789s", null)]
730         [TestCase("-1.123456s", null)]
731         [TestCase("-1.123s", null)]
732         [TestCase("-1s", null)]
733         [TestCase("0.123s", null)]
734         [TestCase("-0.123s", null)]
735         [TestCase("123456.123s", null)]
736         [TestCase("-123456.123s", null)]
737         // Upper and lower bounds
738         [TestCase("315576000000s", null)]
739         [TestCase("-315576000000s", null)]
Duration_Valid(string jsonValue, string expectedFormatted)740         public void Duration_Valid(string jsonValue, string expectedFormatted)
741         {
742             expectedFormatted = expectedFormatted ?? jsonValue;
743             string json = WrapInQuotes(jsonValue);
744             var parsed = Duration.Parser.ParseJson(json);
745             Assert.AreEqual(WrapInQuotes(expectedFormatted), parsed.ToString());
746         }
747 
748         // The simplest way of testing that the value has parsed correctly is to reformat it,
749         // as we trust the formatting. In many cases that will give the same result as the input,
750         // so in those cases we accept an expectedFormatted value of null. Sometimes the results
751         // will be different though, due to a different number of digits being provided.
752         [Test]
753         [TestCase("1.1234567890s", Description = "Too many digits")]
754         [TestCase("1.123456789", Description = "No suffix")]
755         [TestCase("1.123456789ss", Description = "Too much suffix")]
756         [TestCase("1.123456789S", Description = "Upper case suffix")]
757         [TestCase("+1.123456789s", Description = "Leading +")]
758         [TestCase(".123456789s", Description = "No integer before the fraction")]
759         [TestCase("1,123456789s", Description = "Comma as decimal separator")]
760         [TestCase("1x1.123456789s", Description = "Non-digit in integer part")]
761         [TestCase("1.1x3456789s", Description = "Non-digit in fractional part")]
762         [TestCase(" 1.123456789s", Description = "Whitespace before fraction")]
763         [TestCase("1.123456789s ", Description = "Whitespace after value")]
764         [TestCase("01.123456789s", Description = "Leading zero (positive)")]
765         [TestCase("-01.123456789s", Description = "Leading zero (negative)")]
766         [TestCase("--0.123456789s", Description = "Double minus sign")]
767         // Violate upper/lower bounds in various ways
768         [TestCase("315576000001s", Description = "Integer part too large")]
769         [TestCase("3155760000000s", Description = "Integer part too long (positive)")]
770         [TestCase("-3155760000000s", Description = "Integer part too long (negative)")]
Duration_Invalid(string jsonValue)771         public void Duration_Invalid(string jsonValue)
772         {
773             string json = WrapInQuotes(jsonValue);
774             Assert.Throws<InvalidProtocolBufferException>(() => Duration.Parser.ParseJson(json));
775         }
776 
777         // Not as many tests for field masks as I'd like; more to be added when we have more
778         // detailed specifications.
779 
780         [Test]
781         [TestCase("")]
782         [TestCase("foo", "foo")]
783         [TestCase("foo,bar", "foo", "bar")]
784         [TestCase("foo.bar", "foo.bar")]
785         [TestCase("fooBar", "foo_bar")]
786         [TestCase("fooBar.bazQux", "foo_bar.baz_qux")]
FieldMask_Valid(string jsonValue, params string[] expectedPaths)787         public void FieldMask_Valid(string jsonValue, params string[] expectedPaths)
788         {
789             string json = WrapInQuotes(jsonValue);
790             var parsed = FieldMask.Parser.ParseJson(json);
791             CollectionAssert.AreEqual(expectedPaths, parsed.Paths);
792         }
793 
794         [Test]
795         [TestCase("foo_bar")]
FieldMask_Invalid(string jsonValue)796         public void FieldMask_Invalid(string jsonValue)
797         {
798             string json = WrapInQuotes(jsonValue);
799             Assert.Throws<InvalidProtocolBufferException>(() => FieldMask.Parser.ParseJson(json));
800         }
801 
802         [Test]
Any_RegularMessage()803         public void Any_RegularMessage()
804         {
805             var registry = TypeRegistry.FromMessages(TestAllTypes.Descriptor);
806             var formatter = new JsonFormatter(new JsonFormatter.Settings(false, TypeRegistry.FromMessages(TestAllTypes.Descriptor)));
807             var message = new TestAllTypes { SingleInt32 = 10, SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 } };
808             var original = Any.Pack(message);
809             var json = formatter.Format(original); // This is tested in JsonFormatterTest
810             var parser = new JsonParser(new JsonParser.Settings(10, registry));
811             Assert.AreEqual(original, parser.Parse<Any>(json));
812             string valueFirstJson = "{ \"singleInt32\": 10, \"singleNestedMessage\": { \"bb\": 20 }, \"@type\": \"type.googleapis.com/protobuf_unittest.TestAllTypes\" }";
813             Assert.AreEqual(original, parser.Parse<Any>(valueFirstJson));
814         }
815 
816         [Test]
Any_CustomPrefix()817         public void Any_CustomPrefix()
818         {
819             var registry = TypeRegistry.FromMessages(TestAllTypes.Descriptor);
820             var message = new TestAllTypes { SingleInt32 = 10 };
821             var original = Any.Pack(message, "custom.prefix/middle-part");
822             var parser = new JsonParser(new JsonParser.Settings(10, registry));
823             string json = "{ \"@type\": \"custom.prefix/middle-part/protobuf_unittest.TestAllTypes\", \"singleInt32\": 10 }";
824             Assert.AreEqual(original, parser.Parse<Any>(json));
825         }
826 
827         [Test]
Any_UnknownType()828         public void Any_UnknownType()
829         {
830             string json = "{ \"@type\": \"type.googleapis.com/bogus\" }";
831             Assert.Throws<InvalidOperationException>(() => Any.Parser.ParseJson(json));
832         }
833 
834         [Test]
Any_NoTypeUrl()835         public void Any_NoTypeUrl()
836         {
837             string json = "{ \"foo\": \"bar\" }";
838             Assert.Throws<InvalidProtocolBufferException>(() => Any.Parser.ParseJson(json));
839         }
840 
841         [Test]
Any_WellKnownType()842         public void Any_WellKnownType()
843         {
844             var registry = TypeRegistry.FromMessages(Timestamp.Descriptor);
845             var formatter = new JsonFormatter(new JsonFormatter.Settings(false, registry));
846             var timestamp = new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimestamp();
847             var original = Any.Pack(timestamp);
848             var json = formatter.Format(original); // This is tested in JsonFormatterTest
849             var parser = new JsonParser(new JsonParser.Settings(10, registry));
850             Assert.AreEqual(original, parser.Parse<Any>(json));
851             string valueFirstJson = "{ \"value\": \"1673-06-19T12:34:56Z\", \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\" }";
852             Assert.AreEqual(original, parser.Parse<Any>(valueFirstJson));
853         }
854 
855         [Test]
Any_Nested()856         public void Any_Nested()
857         {
858             var registry = TypeRegistry.FromMessages(TestWellKnownTypes.Descriptor, TestAllTypes.Descriptor);
859             var formatter = new JsonFormatter(new JsonFormatter.Settings(false, registry));
860             var parser = new JsonParser(new JsonParser.Settings(10, registry));
861             var doubleNestedMessage = new TestAllTypes { SingleInt32 = 20 };
862             var nestedMessage = Any.Pack(doubleNestedMessage);
863             var message = new TestWellKnownTypes { AnyField = Any.Pack(nestedMessage) };
864             var json = formatter.Format(message);
865             // Use the descriptor-based parser just for a change.
866             Assert.AreEqual(message, parser.Parse(json, TestWellKnownTypes.Descriptor));
867         }
868 
869         [Test]
DataAfterObject()870         public void DataAfterObject()
871         {
872             string json = "{} 10";
873             Assert.Throws<InvalidJsonException>(() => TestAllTypes.Parser.ParseJson(json));
874         }
875 
876         /// <summary>
877         /// JSON equivalent to <see cref="CodedInputStreamTest.MaliciousRecursion"/>
878         /// </summary>
879         [Test]
MaliciousRecursion()880         public void MaliciousRecursion()
881         {
882             string data64 = CodedInputStreamTest.MakeRecursiveMessage(64).ToString();
883             string data65 = CodedInputStreamTest.MakeRecursiveMessage(65).ToString();
884 
885             var parser64 = new JsonParser(new JsonParser.Settings(64));
886             CodedInputStreamTest.AssertMessageDepth(parser64.Parse<TestRecursiveMessage>(data64), 64);
887             Assert.Throws<InvalidProtocolBufferException>(() => parser64.Parse<TestRecursiveMessage>(data65));
888 
889             var parser63 = new JsonParser(new JsonParser.Settings(63));
890             Assert.Throws<InvalidProtocolBufferException>(() => parser63.Parse<TestRecursiveMessage>(data64));
891         }
892 
893         [Test]
894         [TestCase("AQI")]
895         [TestCase("_-==")]
Bytes_InvalidBase64(string badBase64)896         public void Bytes_InvalidBase64(string badBase64)
897         {
898             string json = "{ \"singleBytes\": \"" + badBase64 + "\" }";
899             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
900         }
901 
902         [Test]
903         [TestCase("\"FOREIGN_BAR\"", ForeignEnum.ForeignBar)]
904         [TestCase("5", ForeignEnum.ForeignBar)]
905         [TestCase("100", (ForeignEnum)100)]
EnumValid(string value, ForeignEnum expectedValue)906         public void EnumValid(string value, ForeignEnum expectedValue)
907         {
908             string json = "{ \"singleForeignEnum\": " + value + " }";
909             var parsed = TestAllTypes.Parser.ParseJson(json);
910             Assert.AreEqual(new TestAllTypes { SingleForeignEnum = expectedValue }, parsed);
911         }
912 
913         [Test]
914         [TestCase("\"NOT_A_VALID_VALUE\"")]
915         [TestCase("5.5")]
Enum_Invalid(string value)916         public void Enum_Invalid(string value)
917         {
918             string json = "{ \"singleForeignEnum\": " + value + " }";
919             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
920         }
921 
922         [Test]
OneofDuplicate_Invalid()923         public void OneofDuplicate_Invalid()
924         {
925             string json = "{ \"oneofString\": \"x\", \"oneofUint32\": 10 }";
926             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
927         }
928 
929         /// <summary>
930         /// Various tests use strings which have quotes round them for parsing or as the result
931         /// of formatting, but without those quotes being specified in the tests (for the sake of readability).
932         /// This method simply returns the input, wrapped in double quotes.
933         /// </summary>
WrapInQuotes(string text)934         internal static string WrapInQuotes(string text)
935         {
936             return '"' + text + '"';
937         }
938     }
939 }