Commit 47b7d2c7 authored by Jon Skeet's avatar Jon Skeet Committed by Jon Skeet

Add DiscardUnknownFields support for C#

By default, unknown fields are preserved when parsing. To discard
them, use a parser configured to do so:

var parser = MyMessage.Parser.WithDiscardUnknownFields(true);
parent 9f80df02
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#endregion #endregion
using System; using System;
using System.IO;
using Google.Protobuf.TestProtos; using Google.Protobuf.TestProtos;
using NUnit.Framework; using NUnit.Framework;
...@@ -124,5 +125,52 @@ namespace Google.Protobuf ...@@ -124,5 +125,52 @@ namespace Google.Protobuf
Assert.AreEqual(message.CalculateSize(), otherEmptyMessage.CalculateSize()); Assert.AreEqual(message.CalculateSize(), otherEmptyMessage.CalculateSize());
Assert.AreEqual(message.ToByteArray(), otherEmptyMessage.ToByteArray()); Assert.AreEqual(message.ToByteArray(), otherEmptyMessage.ToByteArray());
} }
[Test]
public void TestDiscardUnknownFields()
{
var message = SampleMessages.CreateFullTestAllTypes();
var goldenEmptyMessage = new TestEmptyMessage();
byte[] data = message.ToByteArray();
int fullSize = message.CalculateSize();
Action<IMessage> assertEmpty = msg =>
{
Assert.AreEqual(0, msg.CalculateSize());
Assert.AreEqual(goldenEmptyMessage, msg);
};
Action<IMessage> assertFull = msg => Assert.AreEqual(fullSize, msg.CalculateSize());
// Test the behavior of the parsers with and without discarding, both generic and non-generic.
MessageParser<TestEmptyMessage> retainingParser1 = TestEmptyMessage.Parser;
MessageParser retainingParser2 = retainingParser1;
MessageParser<TestEmptyMessage> discardingParser1 = retainingParser1.WithDiscardUnknownFields(true);
MessageParser discardingParser2 = retainingParser2.WithDiscardUnknownFields(true);
// Test parse from byte[]
assertFull(retainingParser1.ParseFrom(data));
assertFull(retainingParser2.ParseFrom(data));
assertEmpty(discardingParser1.ParseFrom(data));
assertEmpty(discardingParser2.ParseFrom(data));
// Test parse from byte[] with offset
assertFull(retainingParser1.ParseFrom(data, 0, data.Length));
assertFull(retainingParser2.ParseFrom(data, 0, data.Length));
assertEmpty(discardingParser1.ParseFrom(data, 0, data.Length));
assertEmpty(discardingParser2.ParseFrom(data, 0, data.Length));
// Test parse from CodedInputStream
assertFull(retainingParser1.ParseFrom(new CodedInputStream(data)));
assertFull(retainingParser2.ParseFrom(new CodedInputStream(data)));
assertEmpty(discardingParser1.ParseFrom(new CodedInputStream(data)));
assertEmpty(discardingParser2.ParseFrom(new CodedInputStream(data)));
// Test parse from Stream
assertFull(retainingParser1.ParseFrom(new MemoryStream(data)));
assertFull(retainingParser2.ParseFrom(new MemoryStream(data)));
assertEmpty(discardingParser1.ParseFrom(new MemoryStream(data)));
assertEmpty(discardingParser2.ParseFrom(new MemoryStream(data)));
}
} }
} }
...@@ -267,6 +267,11 @@ namespace Google.Protobuf ...@@ -267,6 +267,11 @@ namespace Google.Protobuf
/// </value> /// </value>
public int RecursionLimit { get { return recursionLimit; } } public int RecursionLimit { get { return recursionLimit; } }
/// <summary>
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
/// </summary>
internal bool DiscardUnknownFields { get; set; }
/// <summary> /// <summary>
/// Disposes of this instance, potentially closing any underlying stream. /// Disposes of this instance, potentially closing any underlying stream.
/// </summary> /// </summary>
......
...@@ -44,14 +44,8 @@ namespace Google.Protobuf ...@@ -44,14 +44,8 @@ namespace Google.Protobuf
/// </summary> /// </summary>
/// <param name="message">The message to merge the data into.</param> /// <param name="message">The message to merge the data into.</param>
/// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param> /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeFrom(this IMessage message, byte[] data) public static void MergeFrom(this IMessage message, byte[] data) =>
{ MergeFrom(message, data, false);
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = new CodedInputStream(data);
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
/// <summary> /// <summary>
/// Merges data from the given byte array slice into an existing message. /// Merges data from the given byte array slice into an existing message.
...@@ -60,42 +54,24 @@ namespace Google.Protobuf ...@@ -60,42 +54,24 @@ namespace Google.Protobuf
/// <param name="data">The data containing the slice to merge, which must be protobuf-encoded binary data.</param> /// <param name="data">The data containing the slice to merge, which must be protobuf-encoded binary data.</param>
/// <param name="offset">The offset of the slice to merge.</param> /// <param name="offset">The offset of the slice to merge.</param>
/// <param name="length">The length of the slice to merge.</param> /// <param name="length">The length of the slice to merge.</param>
public static void MergeFrom(this IMessage message, byte[] data, int offset, int length) public static void MergeFrom(this IMessage message, byte[] data, int offset, int length) =>
{ MergeFrom(message, data, offset, length, false);
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = new CodedInputStream(data, offset, length);
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
/// <summary> /// <summary>
/// Merges data from the given byte string into an existing message. /// Merges data from the given byte string into an existing message.
/// </summary> /// </summary>
/// <param name="message">The message to merge the data into.</param> /// <param name="message">The message to merge the data into.</param>
/// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param> /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeFrom(this IMessage message, ByteString data) public static void MergeFrom(this IMessage message, ByteString data) =>
{ MergeFrom(message, data, false);
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = data.CreateCodedInput();
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
/// <summary> /// <summary>
/// Merges data from the given stream into an existing message. /// Merges data from the given stream into an existing message.
/// </summary> /// </summary>
/// <param name="message">The message to merge the data into.</param> /// <param name="message">The message to merge the data into.</param>
/// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param> /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeFrom(this IMessage message, Stream input) public static void MergeFrom(this IMessage message, Stream input) =>
{ MergeFrom(message, input, false);
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(input, "input");
CodedInputStream codedInput = new CodedInputStream(input);
message.MergeFrom(codedInput);
codedInput.CheckReadEndOfStreamTag();
}
/// <summary> /// <summary>
/// Merges length-delimited data from the given stream into an existing message. /// Merges length-delimited data from the given stream into an existing message.
...@@ -106,14 +82,8 @@ namespace Google.Protobuf ...@@ -106,14 +82,8 @@ namespace Google.Protobuf
/// </remarks> /// </remarks>
/// <param name="message">The message to merge the data into.</param> /// <param name="message">The message to merge the data into.</param>
/// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param> /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeDelimitedFrom(this IMessage message, Stream input) public static void MergeDelimitedFrom(this IMessage message, Stream input) =>
{ MergeDelimitedFrom(message, input, false);
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(input, "input");
int size = (int) CodedInputStream.ReadRawVarint32(input);
Stream limitedStream = new LimitedInputStream(input, size);
message.MergeFrom(limitedStream);
}
/// <summary> /// <summary>
/// Converts the given message into a byte array in protobuf encoding. /// Converts the given message into a byte array in protobuf encoding.
...@@ -169,5 +139,55 @@ namespace Google.Protobuf ...@@ -169,5 +139,55 @@ namespace Google.Protobuf
ProtoPreconditions.CheckNotNull(message, "message"); ProtoPreconditions.CheckNotNull(message, "message");
return ByteString.AttachBytes(message.ToByteArray()); return ByteString.AttachBytes(message.ToByteArray());
} }
// Implementations allowing unknown fields to be discarded.
internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = new CodedInputStream(data);
input.DiscardUnknownFields = discardUnknownFields;
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
internal static void MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = new CodedInputStream(data, offset, length);
input.DiscardUnknownFields = discardUnknownFields;
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
internal static void MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = data.CreateCodedInput();
input.DiscardUnknownFields = discardUnknownFields;
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
internal static void MergeFrom(this IMessage message, Stream input, bool discardUnknownFields)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(input, "input");
CodedInputStream codedInput = new CodedInputStream(input);
codedInput.DiscardUnknownFields = discardUnknownFields;
message.MergeFrom(codedInput);
codedInput.CheckReadEndOfStreamTag();
}
internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(input, "input");
int size = (int) CodedInputStream.ReadRawVarint32(input);
Stream limitedStream = new LimitedInputStream(input, size);
MergeFrom(message, limitedStream, discardUnknownFields);
}
} }
} }
...@@ -42,10 +42,13 @@ namespace Google.Protobuf ...@@ -42,10 +42,13 @@ namespace Google.Protobuf
public class MessageParser public class MessageParser
{ {
private Func<IMessage> factory; private Func<IMessage> factory;
// TODO: When we use a C# 7.1 compiler, make this private protected.
internal bool DiscardUnknownFields { get; }
internal MessageParser(Func<IMessage> factory) internal MessageParser(Func<IMessage> factory, bool discardUnknownFields)
{ {
this.factory = factory; this.factory = factory;
DiscardUnknownFields = discardUnknownFields;
} }
/// <summary> /// <summary>
...@@ -65,7 +68,7 @@ namespace Google.Protobuf ...@@ -65,7 +68,7 @@ namespace Google.Protobuf
public IMessage ParseFrom(byte[] data) public IMessage ParseFrom(byte[] data)
{ {
IMessage message = factory(); IMessage message = factory();
message.MergeFrom(data); message.MergeFrom(data, DiscardUnknownFields);
return message; return message;
} }
...@@ -79,7 +82,7 @@ namespace Google.Protobuf ...@@ -79,7 +82,7 @@ namespace Google.Protobuf
public IMessage ParseFrom(byte[] data, int offset, int length) public IMessage ParseFrom(byte[] data, int offset, int length)
{ {
IMessage message = factory(); IMessage message = factory();
message.MergeFrom(data, offset, length); message.MergeFrom(data, offset, length, DiscardUnknownFields);
return message; return message;
} }
...@@ -91,7 +94,7 @@ namespace Google.Protobuf ...@@ -91,7 +94,7 @@ namespace Google.Protobuf
public IMessage ParseFrom(ByteString data) public IMessage ParseFrom(ByteString data)
{ {
IMessage message = factory(); IMessage message = factory();
message.MergeFrom(data); message.MergeFrom(data, DiscardUnknownFields);
return message; return message;
} }
...@@ -103,7 +106,7 @@ namespace Google.Protobuf ...@@ -103,7 +106,7 @@ namespace Google.Protobuf
public IMessage ParseFrom(Stream input) public IMessage ParseFrom(Stream input)
{ {
IMessage message = factory(); IMessage message = factory();
message.MergeFrom(input); message.MergeFrom(input, DiscardUnknownFields);
return message; return message;
} }
...@@ -119,7 +122,7 @@ namespace Google.Protobuf ...@@ -119,7 +122,7 @@ namespace Google.Protobuf
public IMessage ParseDelimitedFrom(Stream input) public IMessage ParseDelimitedFrom(Stream input)
{ {
IMessage message = factory(); IMessage message = factory();
message.MergeDelimitedFrom(input); message.MergeDelimitedFrom(input, DiscardUnknownFields);
return message; return message;
} }
...@@ -131,7 +134,7 @@ namespace Google.Protobuf ...@@ -131,7 +134,7 @@ namespace Google.Protobuf
public IMessage ParseFrom(CodedInputStream input) public IMessage ParseFrom(CodedInputStream input)
{ {
IMessage message = factory(); IMessage message = factory();
message.MergeFrom(input); MergeFrom(message, input);
return message; return message;
} }
...@@ -148,6 +151,29 @@ namespace Google.Protobuf ...@@ -148,6 +151,29 @@ namespace Google.Protobuf
JsonParser.Default.Merge(message, json); JsonParser.Default.Merge(message, json);
return message; return message;
} }
// TODO: When we're using a C# 7.1 compiler, make this private protected.
internal void MergeFrom(IMessage message, CodedInputStream codedInput)
{
bool originalDiscard = codedInput.DiscardUnknownFields;
try
{
codedInput.DiscardUnknownFields = DiscardUnknownFields;
message.MergeFrom(codedInput);
}
finally
{
codedInput.DiscardUnknownFields = originalDiscard;
}
}
/// <summary>
/// Creates a new message parser which optionally discards unknown fields when parsing.
/// </summary>
/// <param name="discardUnknownFields">Whether or not to discard unknown fields when parsing.</param>
/// <returns>A newly configured message parser.</returns>
public MessageParser WithDiscardUnknownFields(bool discardUnknownFields) =>
new MessageParser(factory, discardUnknownFields);
} }
/// <summary> /// <summary>
...@@ -182,7 +208,11 @@ namespace Google.Protobuf ...@@ -182,7 +208,11 @@ namespace Google.Protobuf
/// to require a parameterless constructor: delegates are significantly faster to execute. /// to require a parameterless constructor: delegates are significantly faster to execute.
/// </remarks> /// </remarks>
/// <param name="factory">Function to invoke when a new, empty message is required.</param> /// <param name="factory">Function to invoke when a new, empty message is required.</param>
public MessageParser(Func<T> factory) : base(() => factory()) public MessageParser(Func<T> factory) : this(factory, false)
{
}
internal MessageParser(Func<T> factory, bool discardUnknownFields) : base(() => factory(), discardUnknownFields)
{ {
this.factory = factory; this.factory = factory;
} }
...@@ -204,7 +234,7 @@ namespace Google.Protobuf ...@@ -204,7 +234,7 @@ namespace Google.Protobuf
public new T ParseFrom(byte[] data) public new T ParseFrom(byte[] data)
{ {
T message = factory(); T message = factory();
message.MergeFrom(data); message.MergeFrom(data, DiscardUnknownFields);
return message; return message;
} }
...@@ -218,7 +248,7 @@ namespace Google.Protobuf ...@@ -218,7 +248,7 @@ namespace Google.Protobuf
public new T ParseFrom(byte[] data, int offset, int length) public new T ParseFrom(byte[] data, int offset, int length)
{ {
T message = factory(); T message = factory();
message.MergeFrom(data, offset, length); message.MergeFrom(data, offset, length, DiscardUnknownFields);
return message; return message;
} }
...@@ -230,7 +260,7 @@ namespace Google.Protobuf ...@@ -230,7 +260,7 @@ namespace Google.Protobuf
public new T ParseFrom(ByteString data) public new T ParseFrom(ByteString data)
{ {
T message = factory(); T message = factory();
message.MergeFrom(data); message.MergeFrom(data, DiscardUnknownFields);
return message; return message;
} }
...@@ -242,7 +272,7 @@ namespace Google.Protobuf ...@@ -242,7 +272,7 @@ namespace Google.Protobuf
public new T ParseFrom(Stream input) public new T ParseFrom(Stream input)
{ {
T message = factory(); T message = factory();
message.MergeFrom(input); message.MergeFrom(input, DiscardUnknownFields);
return message; return message;
} }
...@@ -258,7 +288,7 @@ namespace Google.Protobuf ...@@ -258,7 +288,7 @@ namespace Google.Protobuf
public new T ParseDelimitedFrom(Stream input) public new T ParseDelimitedFrom(Stream input)
{ {
T message = factory(); T message = factory();
message.MergeDelimitedFrom(input); message.MergeDelimitedFrom(input, DiscardUnknownFields);
return message; return message;
} }
...@@ -270,7 +300,7 @@ namespace Google.Protobuf ...@@ -270,7 +300,7 @@ namespace Google.Protobuf
public new T ParseFrom(CodedInputStream input) public new T ParseFrom(CodedInputStream input)
{ {
T message = factory(); T message = factory();
message.MergeFrom(input); MergeFrom(message, input);
return message; return message;
} }
...@@ -287,5 +317,13 @@ namespace Google.Protobuf ...@@ -287,5 +317,13 @@ namespace Google.Protobuf
JsonParser.Default.Merge(message, json); JsonParser.Default.Merge(message, json);
return message; return message;
} }
/// <summary>
/// Creates a new message parser which optionally discards unknown fields when parsing.
/// </summary>
/// <param name="discardUnknownFields">Whether or not to discard unknown fields when parsing.</param>
/// <returns>A newly configured message parser.</returns>
public new MessageParser<T> WithDiscardUnknownFields(bool discardUnknownFields) =>
new MessageParser<T>(factory, discardUnknownFields);
} }
} }
...@@ -230,7 +230,8 @@ namespace Google.Protobuf ...@@ -230,7 +230,8 @@ namespace Google.Protobuf
/// <summary> /// <summary>
/// Create a new UnknownFieldSet if unknownFields is null. /// Create a new UnknownFieldSet if unknownFields is null.
/// Parse a single field from <paramref name="input"/> and merge it /// Parse a single field from <paramref name="input"/> and merge it
/// into unknownFields. /// into unknownFields. If <paramref name="input"/> is configured to discard unknown fields,
/// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped.
/// </summary> /// </summary>
/// <param name="unknownFields">The UnknownFieldSet which need to be merged</param> /// <param name="unknownFields">The UnknownFieldSet which need to be merged</param>
/// <param name="input">The coded input stream containing the field</param> /// <param name="input">The coded input stream containing the field</param>
...@@ -238,6 +239,11 @@ namespace Google.Protobuf ...@@ -238,6 +239,11 @@ namespace Google.Protobuf
public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields, public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields,
CodedInputStream input) CodedInputStream input)
{ {
if (input.DiscardUnknownFields)
{
input.SkipLastField();
return unknownFields;
}
if (unknownFields == null) if (unknownFields == null)
{ {
unknownFields = new UnknownFieldSet(); unknownFields = new UnknownFieldSet();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment