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
33using Google.Protobuf.Reflection;
34using Google.Protobuf.TestProtos;
35using Google.Protobuf.WellKnownTypes;
36using NUnit.Framework;
37using System;
38
39namespace Google.Protobuf
40{
41    /// <summary>
42    /// Unit tests for JSON parsing.
43    /// </summary>
44    public class JsonParserTest
45    {
46        // Sanity smoke test
47        [Test]
48        public void AllTypesRoundtrip()
49        {
50            AssertRoundtrip(SampleMessages.CreateFullTestAllTypes());
51        }
52
53        [Test]
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")]
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]
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]
83        public void SourceContextRoundtrip()
84        {
85            AssertRoundtrip(new SourceContext { FileName = "foo.proto" });
86        }
87
88        [Test]
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]
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]
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)]
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]
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]
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]
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]
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]
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 }")]
212        public void MapField_NullValueProhibited(string json)
213        {
214            Assert.Throws<InvalidProtocolBufferException>(() => TestMap.Parser.ParseJson(json));
215        }
216
217        [Test]
218        public void MapField_NullOverallValueAllowed()
219        {
220            string json = "{ \"mapInt32Int32\": null }";
221            Assert.AreEqual(new TestMap(), TestMap.Parser.ParseJson(json));
222        }
223
224        [Test]
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)]
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")]
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)]
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")]
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)]
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")]
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)]
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")]
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)]
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...
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)]
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")]
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)]
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))]
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)]
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")]
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)]
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")]
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)]
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")]
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)]
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...
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)]
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))]
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)]
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")]
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]
674        public void StructValue_Null()
675        {
676            Assert.AreEqual(new Value { NullValue = 0 }, Value.Parser.ParseJson("null"));
677        }
678
679        [Test]
680        public void StructValue_String()
681        {
682            Assert.AreEqual(new Value { StringValue = "hi" }, Value.Parser.ParseJson("\"hi\""));
683        }
684
685        [Test]
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]
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]
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]
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]
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)]
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)")]
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")]
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")]
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]
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]
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]
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]
835        public void Any_NoTypeUrl()
836        {
837            string json = "{ \"foo\": \"bar\" }";
838            Assert.Throws<InvalidProtocolBufferException>(() => Any.Parser.ParseJson(json));
839        }
840
841        [Test]
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]
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]
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]
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("_-==")]
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)]
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")]
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]
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>
934        internal static string WrapInQuotes(string text)
935        {
936            return '"' + text + '"';
937        }
938    }
939}