• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  }