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