diff options
author | csharptest <roger@csharptest.net> | 2011-09-16 10:55:10 -0500 |
---|---|---|
committer | rogerk <devnull@localhost> | 2011-09-16 10:55:10 -0500 |
commit | 2cf6e1b07749b98ff0191a51fa76f1fb3d970724 (patch) | |
tree | 10f57217f70ab02310fcca90b19849e208313eef | |
parent | c86b504409f0b1b57764fd42376de28b13968a8f (diff) | |
download | protobuf-2cf6e1b07749b98ff0191a51fa76f1fb3d970724.tar.gz protobuf-2cf6e1b07749b98ff0191a51fa76f1fb3d970724.tar.bz2 protobuf-2cf6e1b07749b98ff0191a51fa76f1fb3d970724.zip |
Last change for http support, adding a simple reader for query strings and/or
url-encoded form data to support restful apis. Also exposed the mime to reader
and mime to writer via dictionaries in the MessageFormatOptions structure.
7 files changed, 411 insertions, 71 deletions
diff --git a/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs b/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs new file mode 100644 index 00000000..508d76a9 --- /dev/null +++ b/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs @@ -0,0 +1,162 @@ +using System;
+using System.IO;
+using System.Text;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+ /// <summary>
+ /// Allows reading messages from a name/value dictionary
+ /// </summary>
+ public class FormUrlEncodedReader : AbstractTextReader
+ {
+ private readonly TextReader _input;
+ private string _fieldName, _fieldValue;
+ private bool _ready;
+
+ /// <summary>
+ /// Creates a dictionary reader from an enumeration of KeyValuePair data, like an IDictionary
+ /// </summary>
+ FormUrlEncodedReader(TextReader input)
+ {
+ _input = input;
+ int ch = input.Peek();
+ if (ch == '?')
+ {
+ input.Read();
+ }
+ _ready = ReadNext();
+ }
+
+ #region CreateInstance overloads
+ /// <summary>
+ /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+ /// </summary>
+ public static FormUrlEncodedReader CreateInstance(Stream stream)
+ {
+ return new FormUrlEncodedReader(new StreamReader(stream, Encoding.UTF8, false));
+ }
+
+ /// <summary>
+ /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+ /// </summary>
+ public static FormUrlEncodedReader CreateInstance(byte[] bytes)
+ {
+ return new FormUrlEncodedReader(new StreamReader(new MemoryStream(bytes, false), Encoding.UTF8, false));
+ }
+
+ /// <summary>
+ /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+ /// </summary>
+ public static FormUrlEncodedReader CreateInstance(string text)
+ {
+ return new FormUrlEncodedReader(new StringReader(text));
+ }
+
+ /// <summary>
+ /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+ /// </summary>
+ public static FormUrlEncodedReader CreateInstance(TextReader input)
+ {
+ return new FormUrlEncodedReader(input);
+ }
+ #endregion
+
+ private bool ReadNext()
+ {
+ StringBuilder field = new StringBuilder(32);
+ StringBuilder value = new StringBuilder(64);
+ int ch;
+ while (-1 != (ch = _input.Read()) && ch != '=' && ch != '&')
+ {
+ field.Append((char)ch);
+ }
+
+ if (ch != -1 && ch != '&')
+ {
+ while (-1 != (ch = _input.Read()) && ch != '&')
+ {
+ value.Append((char)ch);
+ }
+ }
+
+ _fieldName = field.ToString();
+ _fieldValue = Uri.UnescapeDataString(value.Replace('+', ' ').ToString());
+
+ return !String.IsNullOrEmpty(_fieldName);
+ }
+
+ /// <summary>
+ /// No-op
+ /// </summary>
+ public override void ReadMessageStart()
+ { }
+
+ /// <summary>
+ /// No-op
+ /// </summary>
+ public override void ReadMessageEnd()
+ { }
+
+ /// <summary>
+ /// Merges the contents of stream into the provided message builder
+ /// </summary>
+ public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+ {
+ builder.WeakMergeFrom(this, registry);
+ return builder;
+ }
+
+ /// <summary>
+ /// Causes the reader to skip past this field
+ /// </summary>
+ protected override void Skip()
+ {
+ _ready = ReadNext();
+ }
+
+ /// <summary>
+ /// Peeks at the next field in the input stream and returns what information is available.
+ /// </summary>
+ /// <remarks>
+ /// This may be called multiple times without actually reading the field. Only after the field
+ /// is either read, or skipped, should PeekNext return a different value.
+ /// </remarks>
+ protected override bool PeekNext(out string field)
+ {
+ field = _ready ? _fieldName : null;
+ return field != null;
+ }
+
+ /// <summary>
+ /// Returns true if it was able to read a String from the input
+ /// </summary>
+ protected override bool ReadAsText(ref string value, Type typeInfo)
+ {
+ if (_ready)
+ {
+ value = _fieldValue;
+ _ready = ReadNext();
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// It's unlikely this will work for anything but text data as bytes UTF8 are transformed to text and back to bytes
+ /// </summary>
+ protected override ByteString DecodeBytes(string bytes)
+ { return ByteString.CopyFromUtf8(bytes); }
+
+ /// <summary>
+ /// Not Supported
+ /// </summary>
+ public override bool ReadGroup(IBuilderLite value, ExtensionRegistry registry)
+ { throw new NotSupportedException(); }
+
+ /// <summary>
+ /// Not Supported
+ /// </summary>
+ protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
+ { throw new NotSupportedException(); }
+ }
+}
\ No newline at end of file diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs index 2ee7eb4d..8d389f07 100644 --- a/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs +++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs @@ -19,27 +19,14 @@ namespace Google.ProtocolBuffers.Serialization.Http /// <returns>The ICodedInputStream that can be given to the IBuilder.MergeFrom(...) method</returns>
public static ICodedInputStream CreateInputStream(MessageFormatOptions options, string contentType, Stream input)
{
- FormatType inputType = ContentTypeToFormat(contentType, options.DefaultContentType);
+ ICodedInputStream codedInput = ContentTypeToInputStream(contentType, options, input);
- ICodedInputStream codedInput;
- if (inputType == FormatType.ProtoBuffer)
+ if (codedInput is XmlFormatReader)
{
- codedInput = CodedInputStream.CreateInstance(input);
- }
- else if (inputType == FormatType.Json)
- {
- JsonFormatReader reader = JsonFormatReader.CreateInstance(input);
- codedInput = reader;
- }
- else if (inputType == FormatType.Xml)
- {
- XmlFormatReader reader = XmlFormatReader.CreateInstance(input);
+ XmlFormatReader reader = (XmlFormatReader)codedInput;
reader.RootElementName = options.XmlReaderRootElementName;
reader.Options = options.XmlReaderOptions;
- codedInput = reader;
}
- else
- throw new NotSupportedException();
return codedInput;
}
@@ -69,30 +56,20 @@ namespace Google.ProtocolBuffers.Serialization.Http /// <remarks> If you do not dispose of ICodedOutputStream some formats may yield incomplete output </remarks>
public static ICodedOutputStream CreateOutputStream(MessageFormatOptions options, string contentType, Stream output)
{
- FormatType outputType = ContentTypeToFormat(contentType, options.DefaultContentType);
+ ICodedOutputStream codedOutput = ContentTypeToOutputStream(contentType, options, output);
- ICodedOutputStream codedOutput;
- if (outputType == FormatType.ProtoBuffer)
- {
- codedOutput = CodedOutputStream.CreateInstance(output);
- }
- else if (outputType == FormatType.Json)
+ if (codedOutput is JsonFormatWriter)
{
- JsonFormatWriter writer = JsonFormatWriter.CreateInstance(output);
+ JsonFormatWriter writer = (JsonFormatWriter)codedOutput;
if (options.FormattedOutput)
{
writer.Formatted();
}
- codedOutput = writer;
}
- else if (outputType == FormatType.Xml)
+ else if (codedOutput is XmlFormatWriter)
{
- XmlFormatWriter writer;
- if (!options.FormattedOutput)
- {
- writer = XmlFormatWriter.CreateInstance(output);
- }
- else
+ XmlFormatWriter writer = (XmlFormatWriter)codedOutput;
+ if (options.FormattedOutput)
{
XmlWriterSettings settings = new XmlWriterSettings()
{
@@ -104,14 +81,12 @@ namespace Google.ProtocolBuffers.Serialization.Http IndentChars = " ",
NewLineChars = Environment.NewLine,
};
- writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));
+ // Don't know how else to change xml writer options?
+ codedOutput = writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));
}
writer.RootElementName = options.XmlWriterRootElementName;
writer.Options = options.XmlWriterOptions;
- codedOutput = writer;
}
- else
- throw new NotSupportedException();
return codedOutput;
}
@@ -137,46 +112,39 @@ namespace Google.ProtocolBuffers.Serialization.Http codedOutput.WriteMessageEnd();
}
- enum FormatType { ProtoBuffer, Json, Xml };
+ private static ICodedInputStream ContentTypeToInputStream(string contentType, MessageFormatOptions options, Stream input)
+ {
+ contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
- private static FormatType ContentTypeToFormat(string contentType, string defaultType)
+ Converter<Stream, ICodedInputStream> factory;
+ if(!options.MimeInputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)
+ {
+ if(String.IsNullOrEmpty(options.DefaultContentType) ||
+ !options.MimeInputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)
+ {
+ throw new ArgumentOutOfRangeException("contentType");
+ }
+ }
+
+ return factory(input);
+ }
+
+ private static ICodedOutputStream ContentTypeToOutputStream(string contentType, MessageFormatOptions options, Stream output)
{
- switch ((contentType ?? String.Empty).Split(';')[0].Trim().ToLower())
+ contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
+
+ Converter<Stream, ICodedOutputStream> factory;
+ if (!options.MimeOutputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)
{
- case "application/json":
- case "application/x-json":
- case "application/x-javascript":
- case "text/javascript":
- case "text/x-javascript":
- case "text/x-json":
- case "text/json":
- {
- return FormatType.Json;
- }
-
- case "text/xml":
- case "application/xml":
- {
- return FormatType.Xml;
- }
-
- case "application/binary":
- case "application/x-protobuf":
- case "application/vnd.google.protobuf":
- {
- return FormatType.ProtoBuffer;
- }
-
- case "":
- case null:
- if (!String.IsNullOrEmpty(defaultType))
- {
- return ContentTypeToFormat(defaultType, null);
- }
- break;
+ if (String.IsNullOrEmpty(options.DefaultContentType) ||
+ !options.MimeOutputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)
+ {
+ throw new ArgumentOutOfRangeException("contentType");
+ }
}
- throw new ArgumentOutOfRangeException("contentType");
+ return factory(output);
}
+
}
}
\ No newline at end of file diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs index 02f2ea46..4ee18891 100644 --- a/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs +++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs @@ -1,4 +1,7 @@ using System;
+using System.IO;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.Collections;
namespace Google.ProtocolBuffers.Serialization.Http
{
@@ -22,10 +25,92 @@ namespace Google.ProtocolBuffers.Serialization.Http /// </remarks>
public const string ContentTypeJson = "application/json";
+ /// <summary>The mime type for query strings and x-www-form-urlencoded content</summary>
+ /// <remarks>This mime type is input-only</remarks>
+ public const string ContentFormUrlEncoded = "application/x-www-form-urlencoded";
+
+ /// <summary>
+ /// Default mime-type handling for input
+ /// </summary>
+ private static readonly IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputDefaults =
+ new ReadOnlyDictionary<string, Converter<Stream, ICodedInputStream>>(
+ new Dictionary<string, Converter<Stream, ICodedInputStream>>(StringComparer.OrdinalIgnoreCase)
+ {
+ {"application/json", JsonFormatReader.CreateInstance},
+ {"application/x-json", JsonFormatReader.CreateInstance},
+ {"application/x-javascript", JsonFormatReader.CreateInstance},
+ {"text/javascript", JsonFormatReader.CreateInstance},
+ {"text/x-javascript", JsonFormatReader.CreateInstance},
+ {"text/x-json", JsonFormatReader.CreateInstance},
+ {"text/json", JsonFormatReader.CreateInstance},
+ {"text/xml", XmlFormatReader.CreateInstance},
+ {"application/xml", XmlFormatReader.CreateInstance},
+ {"application/binary", CodedInputStream.CreateInstance},
+ {"application/x-protobuf", CodedInputStream.CreateInstance},
+ {"application/vnd.google.protobuf", CodedInputStream.CreateInstance},
+ {"application/x-www-form-urlencoded", FormUrlEncodedReader.CreateInstance},
+ }
+ );
+
+ /// <summary>
+ /// Default mime-type handling for output
+ /// </summary>
+ private static readonly IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputDefaults =
+ new ReadOnlyDictionary<string, Converter<Stream, ICodedOutputStream>>(
+ new Dictionary<string, Converter<Stream, ICodedOutputStream>>(StringComparer.OrdinalIgnoreCase)
+ {
+ {"application/json", JsonFormatWriter.CreateInstance},
+ {"application/x-json", JsonFormatWriter.CreateInstance},
+ {"application/x-javascript", JsonFormatWriter.CreateInstance},
+ {"text/javascript", JsonFormatWriter.CreateInstance},
+ {"text/x-javascript", JsonFormatWriter.CreateInstance},
+ {"text/x-json", JsonFormatWriter.CreateInstance},
+ {"text/json", JsonFormatWriter.CreateInstance},
+ {"text/xml", XmlFormatWriter.CreateInstance},
+ {"application/xml", XmlFormatWriter.CreateInstance},
+ {"application/binary", CodedOutputStream.CreateInstance},
+ {"application/x-protobuf", CodedOutputStream.CreateInstance},
+ {"application/vnd.google.protobuf", CodedOutputStream.CreateInstance},
+ }
+ );
+
+
+
+
private string _defaultContentType;
private string _xmlReaderRootElementName;
private string _xmlWriterRootElementName;
private ExtensionRegistry _extensionRegistry;
+ private Dictionary<string, Converter<Stream, ICodedInputStream>> _mimeInputTypes;
+ private Dictionary<string, Converter<Stream, ICodedOutputStream>> _mimeOutputTypes;
+
+ /// <summary> Provides access to modify the mime-type input stream construction </summary>
+ public IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputTypes
+ {
+ get
+ {
+ return _mimeInputTypes ??
+ (_mimeInputTypes = new Dictionary<string, Converter<Stream, ICodedInputStream>>(
+ MimeInputDefaults, StringComparer.OrdinalIgnoreCase));
+ }
+ }
+
+ /// <summary> Provides access to modify the mime-type input stream construction </summary>
+ public IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputTypes
+ {
+ get
+ {
+ return _mimeOutputTypes ??
+ (_mimeOutputTypes = new Dictionary<string, Converter<Stream, ICodedOutputStream>>(
+ MimeOutputDefaults, StringComparer.OrdinalIgnoreCase));
+ }
+ }
+
+ internal IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputTypesReadOnly
+ { get { return _mimeInputTypes ?? MimeInputDefaults; } }
+
+ internal IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputTypesReadOnly
+ { get { return _mimeOutputTypes ?? MimeOutputDefaults; } }
/// <summary>
/// The default content type to use if the input type is null or empty. If this
diff --git a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj index fdbbe503..972fb149 100644 --- a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj +++ b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj @@ -98,6 +98,7 @@ </ItemGroup>
<ItemGroup>
<Compile Include="Extensions.cs" />
+ <Compile Include="Http\FormUrlEncodedReader.cs" />
<Compile Include="Http\MessageFormatFactory.cs" />
<Compile Include="Http\MessageFormatOptions.cs" />
<Compile Include="Http\ServiceExtensions.cs" />
diff --git a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj index 549fe88c..e640462c 100644 --- a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj +++ b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj @@ -92,6 +92,7 @@ </Compile>
<Compile Include="Compatibility\TextCompatibilityTests.cs" />
<Compile Include="Compatibility\XmlCompatibilityTests.cs" />
+ <Compile Include="TestReaderForUrlEncoded.cs" />
<Compile Include="CSharpOptionsTest.cs" />
<Compile Include="DescriptorsTest.cs" />
<Compile Include="Descriptors\MessageDescriptorTest.cs" />
diff --git a/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs b/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs index 6f4b7e0f..0ed8d381 100644 --- a/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs +++ b/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs @@ -221,5 +221,44 @@ namespace Google.ProtocolBuffers Assert.AreEqual("<root>\r\n <text>a</text>\r\n <number>1</number>\r\n</root>", Encoding.UTF8.GetString(ms.ToArray()));
}
+
+ [Test]
+ public void TestReadCustomMimeTypes()
+ {
+ var options = new MessageFormatOptions();
+ //Remove existing mime-type mappings
+ options.MimeInputTypes.Clear();
+ //Add our own
+ options.MimeInputTypes.Add("-custom-XML-mime-type-", XmlFormatReader.CreateInstance);
+ Assert.AreEqual(1, options.MimeInputTypes.Count);
+
+ Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToXml()
+ ));
+
+ TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+ options, "-custom-XML-mime-type-", xmlStream)
+ .Build();
+ Assert.AreEqual("a", msg.Text);
+ Assert.AreEqual(1, msg.Number);
+ }
+
+ [Test]
+ public void TestWriteToCustomType()
+ {
+ var options = new MessageFormatOptions();
+ //Remove existing mime-type mappings
+ options.MimeOutputTypes.Clear();
+ //Add our own
+ options.MimeOutputTypes.Add("-custom-XML-mime-type-", XmlFormatWriter.CreateInstance);
+
+ Assert.AreEqual(1, options.MimeOutputTypes.Count);
+
+ MemoryStream ms = new MemoryStream();
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+ .WriteTo(options, "-custom-XML-mime-type-", ms);
+
+ Assert.AreEqual("<root><text>a</text><number>1</number></root>", Encoding.UTF8.GetString(ms.ToArray()));
+ }
}
}
\ No newline at end of file diff --git a/src/ProtocolBuffers.Test/TestReaderForUrlEncoded.cs b/src/ProtocolBuffers.Test/TestReaderForUrlEncoded.cs new file mode 100644 index 00000000..7861e986 --- /dev/null +++ b/src/ProtocolBuffers.Test/TestReaderForUrlEncoded.cs @@ -0,0 +1,84 @@ +using System;
+using System.IO;
+using System.Text;
+using NUnit.Framework;
+using Google.ProtocolBuffers.TestProtos;
+using Google.ProtocolBuffers.Serialization.Http;
+
+namespace Google.ProtocolBuffers
+{
+ [TestFixture]
+ public class TestReaderForUrlEncoded
+ {
+ [Test]
+ public void Example_FromQueryString()
+ {
+ Uri sampleUri = new Uri("http://sample.com/Path/File.ext?text=two+three%20four&valid=true&numbers=1&numbers=2", UriKind.Absolute);
+
+ ICodedInputStream input = FormUrlEncodedReader.CreateInstance(sampleUri.Query);
+
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ builder.MergeFrom(input);
+
+ TestXmlMessage message = builder.Build();
+ Assert.AreEqual(true, message.Valid);
+ Assert.AreEqual("two three four", message.Text);
+ Assert.AreEqual(2, message.NumbersCount);
+ Assert.AreEqual(1, message.NumbersList[0]);
+ Assert.AreEqual(2, message.NumbersList[1]);
+ }
+
+ [Test]
+ public void Example_FromFormData()
+ {
+ Stream rawPost = new MemoryStream(Encoding.UTF8.GetBytes("text=two+three%20four&valid=true&numbers=1&numbers=2"), false);
+
+ ICodedInputStream input = FormUrlEncodedReader.CreateInstance(rawPost);
+
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ builder.MergeFrom(input);
+
+ TestXmlMessage message = builder.Build();
+ Assert.AreEqual(true, message.Valid);
+ Assert.AreEqual("two three four", message.Text);
+ Assert.AreEqual(2, message.NumbersCount);
+ Assert.AreEqual(1, message.NumbersList[0]);
+ Assert.AreEqual(2, message.NumbersList[1]);
+ }
+
+ [Test]
+ public void TestEmptyValues()
+ {
+ ICodedInputStream input = FormUrlEncodedReader.CreateInstance("valid=true&text=&numbers=1");
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ builder.MergeFrom(input);
+
+ Assert.IsTrue(builder.Valid);
+ Assert.IsTrue(builder.HasText);
+ Assert.AreEqual("", builder.Text);
+ Assert.AreEqual(1, builder.NumbersCount);
+ Assert.AreEqual(1, builder.NumbersList[0]);
+ }
+
+ [Test]
+ public void TestNoValue()
+ {
+ ICodedInputStream input = FormUrlEncodedReader.CreateInstance("valid=true&text&numbers=1");
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ builder.MergeFrom(input);
+
+ Assert.IsTrue(builder.Valid);
+ Assert.IsTrue(builder.HasText);
+ Assert.AreEqual("", builder.Text);
+ Assert.AreEqual(1, builder.NumbersCount);
+ Assert.AreEqual(1, builder.NumbersList[0]);
+ }
+
+ [Test, ExpectedException(typeof(NotSupportedException))]
+ public void FormUrlEncodedReaderDoesNotSupportChildren()
+ {
+ ICodedInputStream input = FormUrlEncodedReader.CreateInstance("child=uh0");
+ TestXmlMessage.CreateBuilder().MergeFrom(input);
+ }
+ }
+}
|