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(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)] Wrappers_Standalone(System.Type wrapperType, string json, object expectedValue)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] ExplicitNullValue()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] BytesWrapper_Standalone()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] RepeatedWrappers()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] RepeatedField_NullElementProhibited()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] RepeatedField_NullOverallValueAllowed()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 }")] MapField_NullValueProhibited(string json)209 public void MapField_NullValueProhibited(string json) 210 { 211 Assert.Throws<InvalidProtocolBufferException>(() => TestMap.Parser.ParseJson(json)); 212 } 213 214 [Test] MapField_NullOverallValueAllowed()215 public void MapField_NullOverallValueAllowed() 216 { 217 string json = "{ \"mapInt32Int32\": null }"; 218 Assert.AreEqual(new TestMap(), TestMap.Parser.ParseJson(json)); 219 } 220 221 [Test] IndividualWrapperTypes()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)] StringToInt32_Valid(string jsonValue, int expectedParsedValue)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")] StringToInt32_Invalid(string jsonValue)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)] StringToUInt32_Valid(string jsonValue, uint expectedParsedValue)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")] StringToUInt32_Invalid(string jsonValue)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)] StringToInt64_Valid(string jsonValue, long expectedParsedValue)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")] StringToInt64_Invalid(string jsonValue)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)] StringToUInt64_Valid(string jsonValue, ulong expectedParsedValue)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")] StringToUInt64_Invalid(string jsonValue)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)] StringToDouble_Valid(string jsonValue, double expectedParsedValue)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... StringToDouble_Invalid(string jsonValue)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)] StringToFloat_Valid(string jsonValue, float expectedParsedValue)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")] StringToFloat_Invalid(string jsonValue)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)] NumberToInt32_Valid(string jsonValue, int expectedParsedValue)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))] NumberToInt32_Invalid(string jsonValue, System.Type expectedExceptionType)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)] NumberToUInt32_Valid(string jsonValue, uint expectedParsedValue)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")] NumberToUInt32_Invalid(string jsonValue)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)] NumberToInt64_Valid(string jsonValue, long expectedParsedValue)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")] NumberToInt64_Invalid(string jsonValue)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)] NumberToUInt64_Valid(string jsonValue, ulong expectedParsedValue)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")] NumberToUInt64_Invalid(string jsonValue)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)] NumberToDouble_Valid(string jsonValue, double expectedParsedValue)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... NumberToDouble_Invalid(string jsonValue)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)] NumberToFloat_Valid(string jsonValue, float expectedParsedValue)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))] NumberToFloat_Invalid(string jsonValue, System.Type expectedExceptionType)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)] Timestamp_Valid(string jsonValue, string expectedFormatted)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")] Timestamp_Invalid(string jsonValue)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] StructValue_Null()671 public void StructValue_Null() 672 { 673 Assert.AreEqual(new Value { NullValue = 0 }, Value.Parser.ParseJson("null")); 674 } 675 676 [Test] StructValue_String()677 public void StructValue_String() 678 { 679 Assert.AreEqual(new Value { StringValue = "hi" }, Value.Parser.ParseJson("\"hi\"")); 680 } 681 682 [Test] StructValue_Bool()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] StructValue_List()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] ParseListValue()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] StructValue_Struct()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] ParseStruct()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)] Duration_Valid(string jsonValue, string expectedFormatted)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)")] Duration_Invalid(string jsonValue)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")] FieldMask_Valid(string jsonValue, params string[] expectedPaths)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")] FieldMask_Invalid(string jsonValue)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] Any_RegularMessage()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] Any_CustomPrefix()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] Any_UnknownType()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] Any_NoTypeUrl()832 public void Any_NoTypeUrl() 833 { 834 string json = "{ \"foo\": \"bar\" }"; 835 Assert.Throws<InvalidProtocolBufferException>(() => Any.Parser.ParseJson(json)); 836 } 837 838 [Test] Any_WellKnownType()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] Any_Nested()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] DataAfterObject()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] MaliciousRecursion()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("_-==")] Bytes_InvalidBase64(string badBase64)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)] EnumValid(string value, ForeignEnum expectedValue)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")] Enum_Invalid(string value)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] OneofDuplicate_Invalid()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> WrapInQuotes(string text)931 internal static string WrapInQuotes(string text) 932 { 933 return '"' + text + '"'; 934 } 935 } 936 }