diff options
Diffstat (limited to 'csharp/src')
-rw-r--r-- | csharp/src/Google.Protobuf.Test/JsonParserTest.cs | 44 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs | 4 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/Google.Protobuf.csproj | 1 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/InvalidJsonException.cs | 53 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/JsonParser.cs | 11 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/JsonTokenizer.cs | 42 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/MessageParser.cs | 2 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs | 15 |
8 files changed, 126 insertions, 46 deletions
diff --git a/csharp/src/Google.Protobuf.Test/JsonParserTest.cs b/csharp/src/Google.Protobuf.Test/JsonParserTest.cs index cb138f53..c48b151d 100644 --- a/csharp/src/Google.Protobuf.Test/JsonParserTest.cs +++ b/csharp/src/Google.Protobuf.Test/JsonParserTest.cs @@ -370,19 +370,19 @@ namespace Google.Protobuf } [Test] - [TestCase("+0")] - [TestCase("00")] - [TestCase("-00")] - [TestCase("--1")] - [TestCase("+1")] - [TestCase("1.5", Ignore = true, Reason = "Desired behaviour unclear")] - [TestCase("1e10")] - [TestCase("2147483648")] - [TestCase("-2147483649")] - public void NumberToInt32_Invalid(string jsonValue) + [TestCase("+0", typeof(InvalidJsonException))] + [TestCase("00", typeof(InvalidJsonException))] + [TestCase("-00", typeof(InvalidJsonException))] + [TestCase("--1", typeof(InvalidJsonException))] + [TestCase("+1", typeof(InvalidJsonException))] + [TestCase("1.5", typeof(InvalidProtocolBufferException), Ignore = true, Reason = "Desired behaviour unclear")] + [TestCase("1e10", typeof(InvalidProtocolBufferException))] + [TestCase("2147483648", typeof(InvalidProtocolBufferException))] + [TestCase("-2147483649", typeof(InvalidProtocolBufferException))] + public void NumberToInt32_Invalid(string jsonValue, System.Type expectedExceptionType) { string json = "{ \"singleInt32\": " + jsonValue + "}"; - Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json)); + Assert.Throws(expectedExceptionType, () => TestAllTypes.Parser.ParseJson(json)); } [Test] @@ -486,7 +486,7 @@ namespace Google.Protobuf public void NumberToDouble_Invalid(string jsonValue) { string json = "{ \"singleDouble\": " + jsonValue + "}"; - Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json)); + Assert.Throws<InvalidJsonException>(() => TestAllTypes.Parser.ParseJson(json)); } [Test] @@ -506,17 +506,17 @@ namespace Google.Protobuf } [Test] - [TestCase("3.402824e38")] - [TestCase("-3.402824e38")] - [TestCase("1,0")] - [TestCase("1.0.0")] - [TestCase("+1")] - [TestCase("00")] - [TestCase("--1")] - public void NumberToFloat_Invalid(string jsonValue) + [TestCase("3.402824e38", typeof(InvalidProtocolBufferException))] + [TestCase("-3.402824e38", typeof(InvalidProtocolBufferException))] + [TestCase("1,0", typeof(InvalidJsonException))] + [TestCase("1.0.0", typeof(InvalidJsonException))] + [TestCase("+1", typeof(InvalidJsonException))] + [TestCase("00", typeof(InvalidJsonException))] + [TestCase("--1", typeof(InvalidJsonException))] + public void NumberToFloat_Invalid(string jsonValue, System.Type expectedExceptionType) { string json = "{ \"singleFloat\": " + jsonValue + "}"; - Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json)); + Assert.Throws(expectedExceptionType, () => TestAllTypes.Parser.ParseJson(json)); } // The simplest way of testing that the value has parsed correctly is to reformat it, @@ -721,7 +721,7 @@ namespace Google.Protobuf public void DataAfterObject() { string json = "{} 10"; - Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json)); + Assert.Throws<InvalidJsonException>(() => TestAllTypes.Parser.ParseJson(json)); } /// <summary> diff --git a/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs b/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs index 5ca5d721..a38efeed 100644 --- a/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs +++ b/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs @@ -280,7 +280,7 @@ namespace Google.Protobuf { Assert.IsNotNull(tokenizer.Next()); } - Assert.Throws<InvalidProtocolBufferException>(() => tokenizer.Next()); + Assert.Throws<InvalidJsonException>(() => tokenizer.Next()); } [Test] @@ -403,7 +403,7 @@ namespace Google.Protobuf } Assert.AreEqual(expectedTokens[i], actualToken); } - Assert.Throws<InvalidProtocolBufferException>(() => tokenizer.Next()); + Assert.Throws<InvalidJsonException>(() => tokenizer.Next()); } } } diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj index 00399438..24fe7746 100644 --- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj +++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj @@ -84,6 +84,7 @@ <Compile Include="FieldCodec.cs" />
<Compile Include="FrameworkPortability.cs" />
<Compile Include="IDeepCloneable.cs" />
+ <Compile Include="InvalidJsonException.cs" />
<Compile Include="JsonFormatter.cs" />
<Compile Include="JsonParser.cs" />
<Compile Include="JsonToken.cs" />
diff --git a/csharp/src/Google.Protobuf/InvalidJsonException.cs b/csharp/src/Google.Protobuf/InvalidJsonException.cs new file mode 100644 index 00000000..b5434201 --- /dev/null +++ b/csharp/src/Google.Protobuf/InvalidJsonException.cs @@ -0,0 +1,53 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using System.IO; + +namespace Google.Protobuf +{ + /// <summary> + /// Thrown when an attempt is made to parse invalid JSON, e.g. using + /// a non-string property key, or including a redundant comma. Parsing a protocol buffer + /// message represented in JSON using <see cref="JsonParser"/> can throw both this + /// exception and <see cref="InvalidProtocolBufferException"/> depending on the situation. This + /// exception is only thrown for "pure JSON" errors, whereas <c>InvalidProtocolBufferException</c> + /// is thrown when the JSON may be valid in and of itself, but cannot be parsed as a protocol buffer + /// message. + /// </summary> + public sealed class InvalidJsonException : IOException + { + internal InvalidJsonException(string message) + : base(message) + { + } + } +}
\ No newline at end of file diff --git a/csharp/src/Google.Protobuf/JsonParser.cs b/csharp/src/Google.Protobuf/JsonParser.cs index 4cbbd89d..c34f84fb 100644 --- a/csharp/src/Google.Protobuf/JsonParser.cs +++ b/csharp/src/Google.Protobuf/JsonParser.cs @@ -37,7 +37,6 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -167,11 +166,7 @@ namespace Google.Protobuf throw new InvalidProtocolBufferException("Expected an object"); } var descriptor = message.Descriptor; - // TODO: Make this more efficient, e.g. by building it once in the descriptor. - // Additionally, we need to consider whether to parse field names in their original proto form, - // and any overrides in the descriptor. But yes, all of this should be in the descriptor somehow... - // the descriptor can expose the dictionary. - var jsonFieldMap = descriptor.Fields.InDeclarationOrder().ToDictionary(field => JsonFormatter.ToCamelCase(field.Name)); + var jsonFieldMap = descriptor.Fields.ByJsonName(); while (true) { token = tokenizer.Next(); @@ -340,6 +335,8 @@ namespace Google.Protobuf /// </summary> /// <typeparam name="T">The type of message to create.</typeparam> /// <param name="json">The JSON to parse.</param> + /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception> + /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception> public T Parse<T>(string json) where T : IMessage, new() { return Parse<T>(new StringReader(json)); @@ -350,6 +347,8 @@ namespace Google.Protobuf /// </summary> /// <typeparam name="T">The type of message to create.</typeparam> /// <param name="jsonReader">Reader providing the JSON to parse.</param> + /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception> + /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception> public T Parse<T>(TextReader jsonReader) where T : IMessage, new() { T message = new T(); diff --git a/csharp/src/Google.Protobuf/JsonTokenizer.cs b/csharp/src/Google.Protobuf/JsonTokenizer.cs index ba031886..6589427a 100644 --- a/csharp/src/Google.Protobuf/JsonTokenizer.cs +++ b/csharp/src/Google.Protobuf/JsonTokenizer.cs @@ -105,6 +105,7 @@ namespace Google.Protobuf /// </remarks> /// <returns>The next token in the stream. This is never null.</returns> /// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception> + /// <exception cref="InvalidJsonException">The input text does not comply with RFC 7159</exception> internal JsonToken Next() { if (bufferedToken != null) @@ -209,7 +210,7 @@ namespace Google.Protobuf ValidateAndModifyStateForValue("Invalid state to read a number token: "); return JsonToken.Value(number); default: - throw new InvalidProtocolBufferException("Invalid first character of token: " + next.Value); + throw new InvalidJsonException("Invalid first character of token: " + next.Value); } } } @@ -218,7 +219,7 @@ namespace Google.Protobuf { if ((validStates & state) == 0) { - throw new InvalidProtocolBufferException(errorPrefix + state); + throw reader.CreateException(errorPrefix + state); } } @@ -234,13 +235,13 @@ namespace Google.Protobuf char c = reader.ReadOrFail("Unexpected end of text while reading string"); if (c < ' ') { - throw new InvalidProtocolBufferException(string.Format(CultureInfo.InvariantCulture, "Invalid character in string literal: U+{0:x4}", (int) c)); + throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in string literal: U+{0:x4}", (int) c)); } if (c == '"') { if (haveHighSurrogate) { - throw new InvalidProtocolBufferException("Invalid use of surrogate pair code units"); + throw reader.CreateException("Invalid use of surrogate pair code units"); } return value.ToString(); } @@ -253,7 +254,7 @@ namespace Google.Protobuf // followed by an escaped low surrogate or vice versa... and that couldn't even be represented in UTF-8. if (haveHighSurrogate != char.IsLowSurrogate(c)) { - throw new InvalidProtocolBufferException("Invalid use of surrogate pair code units"); + throw reader.CreateException("Invalid use of surrogate pair code units"); } haveHighSurrogate = char.IsHighSurrogate(c); value.Append(c); @@ -287,7 +288,7 @@ namespace Google.Protobuf case 'u': return ReadUnicodeEscape(); default: - throw new InvalidProtocolBufferException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c)); + throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c)); } } @@ -315,7 +316,7 @@ namespace Google.Protobuf } else { - throw new InvalidProtocolBufferException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c)); + throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c)); } result = (result << 4) + nybble; } @@ -333,11 +334,11 @@ namespace Google.Protobuf char? next = reader.Read(); if (next == null) { - throw new InvalidProtocolBufferException("Unexpected end of text while reading literal token " + text); + throw reader.CreateException("Unexpected end of text while reading literal token " + text); } if (next.Value != text[i]) { - throw new InvalidProtocolBufferException("Unexpected character while reading literal token " + text); + throw reader.CreateException("Unexpected character while reading literal token " + text); } } } @@ -381,7 +382,7 @@ namespace Google.Protobuf } catch (OverflowException) { - throw new InvalidProtocolBufferException("Numeric value out of range: " + builder); + throw reader.CreateException("Numeric value out of range: " + builder); } } @@ -390,14 +391,14 @@ namespace Google.Protobuf char first = reader.ReadOrFail("Invalid numeric literal"); if (first < '0' || first > '9') { - throw new InvalidProtocolBufferException("Invalid numeric literal"); + throw reader.CreateException("Invalid numeric literal"); } builder.Append(first); int digitCount; char? next = ConsumeDigits(builder, out digitCount); if (first == '0' && digitCount != 0) { - throw new InvalidProtocolBufferException("Invalid numeric literal: leading 0 for non-zero value."); + throw reader.CreateException("Invalid numeric literal: leading 0 for non-zero value."); } return next; } @@ -409,7 +410,7 @@ namespace Google.Protobuf char? next = ConsumeDigits(builder, out digitCount); if (digitCount == 0) { - throw new InvalidProtocolBufferException("Invalid numeric literal: fraction with no trailing digits"); + throw reader.CreateException("Invalid numeric literal: fraction with no trailing digits"); } return next; } @@ -420,7 +421,7 @@ namespace Google.Protobuf char? next = reader.Read(); if (next == null) { - throw new InvalidProtocolBufferException("Invalid numeric literal: exponent with no trailing digits"); + throw reader.CreateException("Invalid numeric literal: exponent with no trailing digits"); } if (next == '-' || next == '+') { @@ -434,7 +435,7 @@ namespace Google.Protobuf next = ConsumeDigits(builder, out digitCount); if (digitCount == 0) { - throw new InvalidProtocolBufferException("Invalid numeric literal: exponent without value"); + throw reader.CreateException("Invalid numeric literal: exponent without value"); } return next; } @@ -642,7 +643,7 @@ namespace Google.Protobuf char? next = Read(); if (next == null) { - throw new InvalidProtocolBufferException(messageOnFailure); + throw CreateException(messageOnFailure); } return next.Value; } @@ -655,6 +656,15 @@ namespace Google.Protobuf } nextChar = c; } + + /// <summary> + /// Creates a new exception appropriate for the current state of the reader. + /// </summary> + internal InvalidJsonException CreateException(string message) + { + // TODO: Keep track of and use the location. + return new InvalidJsonException(message); + } } } } diff --git a/csharp/src/Google.Protobuf/MessageParser.cs b/csharp/src/Google.Protobuf/MessageParser.cs index 70c52ba6..8f2717c5 100644 --- a/csharp/src/Google.Protobuf/MessageParser.cs +++ b/csharp/src/Google.Protobuf/MessageParser.cs @@ -148,6 +148,8 @@ namespace Google.Protobuf /// </summary> /// <param name="json">The JSON to parse.</param> /// <returns>The parsed message.</returns> + /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception> + /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception> public T ParseJson(string json) { T message = factory(); diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs index 82901f1b..e599998e 100644 --- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs @@ -62,6 +62,7 @@ namespace Google.Protobuf.Reflection private readonly IList<EnumDescriptor> enumTypes; private readonly IList<FieldDescriptor> fieldsInDeclarationOrder; private readonly IList<FieldDescriptor> fieldsInNumberOrder; + private readonly IDictionary<string, FieldDescriptor> jsonFieldMap; private readonly FieldCollection fields; private readonly IList<OneofDescriptor> oneofs; // CLR representation of the type described by this descriptor, if any. @@ -95,6 +96,8 @@ namespace Google.Protobuf.Reflection (field, index) => new FieldDescriptor(field, file, this, index, generatedCodeInfo == null ? null : generatedCodeInfo.PropertyNames[index])); fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray()); + // TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.) + jsonFieldMap = new ReadOnlyDictionary<string, FieldDescriptor>(fieldsInNumberOrder.ToDictionary(field => JsonFormatter.ToCamelCase(field.Name))); file.DescriptorPool.AddSymbol(this); fields = new FieldCollection(this); } @@ -255,6 +258,18 @@ namespace Google.Protobuf.Reflection return messageDescriptor.fieldsInNumberOrder; } + // TODO: consider making this public in the future. (Being conservative for now...) + + /// <value> + /// Returns a read-only dictionary mapping the field names in this message as they're used + /// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c> + /// in the message would result in an entry with a key <c>fooBar</c>. + /// </value> + internal IDictionary<string, FieldDescriptor> ByJsonName() + { + return messageDescriptor.jsonFieldMap; + } + /// <summary> /// Retrieves the descriptor for the field with the given number. /// </summary> |