aboutsummaryrefslogtreecommitdiff
path: root/csharp/src
diff options
context:
space:
mode:
Diffstat (limited to 'csharp/src')
-rw-r--r--csharp/src/Google.Protobuf.Test/JsonParserTest.cs44
-rw-r--r--csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs4
-rw-r--r--csharp/src/Google.Protobuf/Google.Protobuf.csproj1
-rw-r--r--csharp/src/Google.Protobuf/InvalidJsonException.cs53
-rw-r--r--csharp/src/Google.Protobuf/JsonParser.cs11
-rw-r--r--csharp/src/Google.Protobuf/JsonTokenizer.cs42
-rw-r--r--csharp/src/Google.Protobuf/MessageParser.cs2
-rw-r--r--csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs15
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>