aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcsharptest <roger@csharptest.net>2011-09-30 10:25:36 -0500
committerrogerk <devnull@localhost>2011-09-30 10:25:36 -0500
commit247c75344ee9eb84f961f1950ad2a9c6c43c617c (patch)
tree7f0cf0575fd57063f371c7b1987437ddeb992b49
parentb6a1457211bc82e5a6faa10eaba9e77e50ba5d0b (diff)
parent06963f90c110f016e963e0a29063a21b9f26eff9 (diff)
downloadprotobuf-247c75344ee9eb84f961f1950ad2a9c6c43c617c.tar.gz
protobuf-247c75344ee9eb84f961f1950ad2a9c6c43c617c.tar.bz2
protobuf-247c75344ee9eb84f961f1950ad2a9c6c43c617c.zip
merged issue-24
-rw-r--r--src/ProtocolBuffers.Serialization/AbstractReader.cs16
-rw-r--r--src/ProtocolBuffers.Serialization/AbstractTextReader.cs4
-rw-r--r--src/ProtocolBuffers.Serialization/AbstractWriter.cs35
-rw-r--r--src/ProtocolBuffers.Serialization/DictionaryReader.cs14
-rw-r--r--src/ProtocolBuffers.Serialization/DictionaryWriter.cs15
-rw-r--r--src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs162
-rw-r--r--src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs153
-rw-r--r--src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs167
-rw-r--r--src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs34
-rw-r--r--src/ProtocolBuffers.Serialization/JsonFormatReader.cs27
-rw-r--r--src/ProtocolBuffers.Serialization/JsonFormatWriter.cs28
-rw-r--r--src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj4
-rw-r--r--src/ProtocolBuffers.Serialization/XmlFormatReader.cs145
-rw-r--r--src/ProtocolBuffers.Serialization/XmlFormatWriter.cs77
-rw-r--r--src/ProtocolBuffers.Test/Compatibility/JsonCompatibilityTests.cs18
-rw-r--r--src/ProtocolBuffers.Test/Compatibility/XmlCompatibilityTests.cs21
-rw-r--r--src/ProtocolBuffers.Test/Properties/AssemblyInfo.cs2
-rw-r--r--src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj3
-rw-r--r--src/ProtocolBuffers.Test/TestMimeMessageFormats.cs264
-rw-r--r--src/ProtocolBuffers.Test/TestReaderForUrlEncoded.cs84
-rw-r--r--src/ProtocolBuffers.Test/TestRpcForMimeTypes.cs386
-rw-r--r--src/ProtocolBuffers.Test/TestWriterFormatJson.cs86
-rw-r--r--src/ProtocolBuffers.Test/TestWriterFormatXml.cs121
-rw-r--r--src/ProtocolBuffers/CodedInputStream.cs3
-rw-r--r--src/ProtocolBuffers/CodedOutputStream.cs3
-rw-r--r--src/ProtocolBuffers/ICodedInputStream.cs18
-rw-r--r--src/ProtocolBuffers/ICodedOutputStream.cs18
27 files changed, 1795 insertions, 113 deletions
diff --git a/src/ProtocolBuffers.Serialization/AbstractReader.cs b/src/ProtocolBuffers.Serialization/AbstractReader.cs
index f54c2707..f3e6fd6f 100644
--- a/src/ProtocolBuffers.Serialization/AbstractReader.cs
+++ b/src/ProtocolBuffers.Serialization/AbstractReader.cs
@@ -17,12 +17,6 @@ namespace Google.ProtocolBuffers.Serialization
/// <summary> Constructs a new reader </summary>
protected AbstractReader() { MaxDepth = DefaultMaxDepth; }
- /// <summary> Constructs a new child reader </summary>
- protected AbstractReader(AbstractReader copyFrom)
- {
- _depth = copyFrom._depth + 1;
- MaxDepth = copyFrom.MaxDepth;
- }
/// <summary> Gets or sets the maximum recursion depth allowed </summary>
public int MaxDepth { get; set; }
@@ -112,6 +106,16 @@ namespace Google.ProtocolBuffers.Serialization
/// Merges the input stream into the provided IBuilderLite
/// </summary>
protected abstract bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry);
+
+ /// <summary>
+ /// Reads the root-message preamble specific to this formatter
+ /// </summary>
+ public abstract void ReadMessageStart();
+
+ /// <summary>
+ /// Reads the root-message close specific to this formatter
+ /// </summary>
+ public abstract void ReadMessageEnd();
/// <summary>
/// Merges the input stream into the provided IBuilderLite
diff --git a/src/ProtocolBuffers.Serialization/AbstractTextReader.cs b/src/ProtocolBuffers.Serialization/AbstractTextReader.cs
index 59b9057b..ddfa6436 100644
--- a/src/ProtocolBuffers.Serialization/AbstractTextReader.cs
+++ b/src/ProtocolBuffers.Serialization/AbstractTextReader.cs
@@ -11,10 +11,6 @@ namespace Google.ProtocolBuffers.Serialization
{
/// <summary> Constructs a new reader </summary>
protected AbstractTextReader() { }
- /// <summary> Constructs a new child reader </summary>
- protected AbstractTextReader(AbstractTextReader copyFrom)
- : base(copyFrom)
- { }
/// <summary>
/// Reads a typed field as a string
diff --git a/src/ProtocolBuffers.Serialization/AbstractWriter.cs b/src/ProtocolBuffers.Serialization/AbstractWriter.cs
index 6592c1dd..1e087ec6 100644
--- a/src/ProtocolBuffers.Serialization/AbstractWriter.cs
+++ b/src/ProtocolBuffers.Serialization/AbstractWriter.cs
@@ -12,36 +12,39 @@ namespace Google.ProtocolBuffers.Serialization
/// <summary>
/// Provides a base class for writers that performs some basic type dispatching
/// </summary>
- public abstract class AbstractWriter : ICodedOutputStream, IDisposable
+ public abstract class AbstractWriter : ICodedOutputStream
{
/// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// Completes any pending write operations
/// </summary>
- public void Dispose()
+ public virtual void Flush()
{
- GC.SuppressFinalize(this);
- Flush();
- Dispose(true);
}
/// <summary>
- /// Completes any pending write operations
+ /// Writes the message to the the formatted stream.
/// </summary>
- public virtual void Flush()
- {
- }
+ public abstract void WriteMessage(IMessageLite message);
/// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// Used to write any nessary root-message preamble. After this call you can call
+ /// IMessageLite.MergeTo(...) and complete the message with a call to WriteMessageEnd().
+ /// These three calls are identical to just calling WriteMessage(message);
/// </summary>
- protected virtual void Dispose(bool disposing)
- {
- }
+ /// <example>
+ /// AbstractWriter writer;
+ /// writer.WriteMessageStart();
+ /// message.WriteTo(writer);
+ /// writer.WriteMessageEnd();
+ /// // ... or, but not both ...
+ /// writer.WriteMessage(message);
+ /// </example>
+ public abstract void WriteMessageStart();
/// <summary>
- /// Writes the message to the the formatted stream.
+ /// Used to complete a root-message previously started with a call to WriteMessageStart()
/// </summary>
- public abstract void WriteMessage(IMessageLite message);
+ public abstract void WriteMessageEnd();
/// <summary>
/// Writes a Boolean value
diff --git a/src/ProtocolBuffers.Serialization/DictionaryReader.cs b/src/ProtocolBuffers.Serialization/DictionaryReader.cs
index cc5c680c..bb83ef9c 100644
--- a/src/ProtocolBuffers.Serialization/DictionaryReader.cs
+++ b/src/ProtocolBuffers.Serialization/DictionaryReader.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using Google.ProtocolBuffers.Descriptors;
@@ -23,6 +23,18 @@ namespace Google.ProtocolBuffers.Serialization
}
/// <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)
diff --git a/src/ProtocolBuffers.Serialization/DictionaryWriter.cs b/src/ProtocolBuffers.Serialization/DictionaryWriter.cs
index 96175a7e..6d823301 100644
--- a/src/ProtocolBuffers.Serialization/DictionaryWriter.cs
+++ b/src/ProtocolBuffers.Serialization/DictionaryWriter.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using Google.ProtocolBuffers.Descriptors;
@@ -53,6 +53,19 @@ namespace Google.ProtocolBuffers.Serialization
message.WriteTo(this);
}
+
+ /// <summary>
+ /// No-op
+ /// </summary>
+ public override void WriteMessageStart()
+ { }
+
+ /// <summary>
+ /// No-op
+ /// </summary>
+ public override void WriteMessageEnd()
+ { }
+
/// <summary>
/// Writes a Boolean value
/// </summary>
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
new file mode 100644
index 00000000..52fff83f
--- /dev/null
+++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
@@ -0,0 +1,153 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Text;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+ /// <summary>
+ /// Extensions and helpers to abstract the reading/writing of messages by a client-specified content type.
+ /// </summary>
+ public static class MessageFormatFactory
+ {
+ /// <summary>
+ /// Constructs an ICodedInputStream from the input stream based on the contentType provided
+ /// </summary>
+ /// <param name="options">Options specific to reading this message and/or content type</param>
+ /// <param name="contentType">The mime type of the input stream content</param>
+ /// <param name="input">The stream to read the message from</param>
+ /// <returns>The ICodedInputStream that can be given to the IBuilder.MergeFrom(...) method</returns>
+ public static ICodedInputStream CreateInputStream(MessageFormatOptions options, string contentType, Stream input)
+ {
+ ICodedInputStream codedInput = ContentTypeToInputStream(contentType, options, input);
+
+ if (codedInput is XmlFormatReader)
+ {
+ XmlFormatReader reader = (XmlFormatReader)codedInput;
+ reader.RootElementName = options.XmlReaderRootElementName;
+ reader.Options = options.XmlReaderOptions;
+ }
+
+ return codedInput;
+ }
+
+ /// <summary>
+ /// Merges the message from the input stream based on the contentType provided
+ /// </summary>
+ /// <typeparam name="TBuilder">A type derived from IBuilderLite</typeparam>
+ /// <param name="builder">An instance of a message builder</param>
+ /// <param name="options">Options specific to reading this message and/or content type</param>
+ /// <param name="contentType">The mime type of the input stream content</param>
+ /// <param name="input">The stream to read the message from</param>
+ /// <returns>The same builder instance that was supplied in the builder parameter</returns>
+ public static TBuilder MergeFrom<TBuilder>(this TBuilder builder, MessageFormatOptions options, string contentType, Stream input) where TBuilder : IBuilderLite
+ {
+ ICodedInputStream codedInput = CreateInputStream(options, contentType, input);
+ codedInput.ReadMessageStart();
+ builder.WeakMergeFrom(codedInput, options.ExtensionRegistry);
+ codedInput.ReadMessageEnd();
+ return builder;
+ }
+
+ /// <summary>
+ /// Writes the message instance to the stream using the content type provided
+ /// </summary>
+ /// <param name="options">Options specific to writing this message and/or content type</param>
+ /// <param name="contentType">The mime type of the content to be written</param>
+ /// <param name="output">The stream to write the message to</param>
+ /// <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)
+ {
+ ICodedOutputStream codedOutput = ContentTypeToOutputStream(contentType, options, output);
+
+ if (codedOutput is JsonFormatWriter)
+ {
+ JsonFormatWriter writer = (JsonFormatWriter)codedOutput;
+ if (options.FormattedOutput)
+ {
+ writer.Formatted();
+ }
+ }
+ else if (codedOutput is XmlFormatWriter)
+ {
+ XmlFormatWriter writer = (XmlFormatWriter)codedOutput;
+ if (options.FormattedOutput)
+ {
+ XmlWriterSettings settings = new XmlWriterSettings()
+ {
+ CheckCharacters = false,
+ NewLineHandling = NewLineHandling.Entitize,
+ OmitXmlDeclaration = true,
+ Encoding = new UTF8Encoding(false),
+ Indent = true,
+ IndentChars = " ",
+ NewLineChars = Environment.NewLine,
+ };
+ // 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;
+ }
+
+ return codedOutput;
+ }
+
+ /// <summary>
+ /// Writes the message instance to the stream using the content type provided
+ /// </summary>
+ /// <param name="message">An instance of a message</param>
+ /// <param name="options">Options specific to writing this message and/or content type</param>
+ /// <param name="contentType">The mime type of the content to be written</param>
+ /// <param name="output">The stream to write the message to</param>
+ public static void WriteTo(this IMessageLite message, MessageFormatOptions options, string contentType, Stream output)
+ {
+ ICodedOutputStream codedOutput = CreateOutputStream(options, contentType, output);
+
+ // Output the appropriate message preamble
+ codedOutput.WriteMessageStart();
+
+ // Write the message content to the output
+ message.WriteTo(codedOutput);
+
+ // Write the closing message fragment
+ codedOutput.WriteMessageEnd();
+ codedOutput.Flush();
+ }
+
+ private static ICodedInputStream ContentTypeToInputStream(string contentType, MessageFormatOptions options, Stream input)
+ {
+ contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
+
+ 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)
+ {
+ contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
+
+ Converter<Stream, ICodedOutputStream> factory;
+ if (!options.MimeOutputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)
+ {
+ if (String.IsNullOrEmpty(options.DefaultContentType) ||
+ !options.MimeOutputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)
+ {
+ 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
new file mode 100644
index 00000000..72d73717
--- /dev/null
+++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
@@ -0,0 +1,167 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.Collections;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+ /// <summary>
+ /// Defines control information for the various formatting used with HTTP services
+ /// </summary>
+ public class MessageFormatOptions
+ {
+ /// <summary>The mime type for xml content</summary>
+ /// <remarks>Other valid xml mime types include: application/binary, application/x-protobuf</remarks>
+ public const string ContentTypeProtoBuffer = "application/vnd.google.protobuf";
+
+ /// <summary>The mime type for xml content</summary>
+ /// <remarks>Other valid xml mime types include: text/xml</remarks>
+ public const string ContentTypeXml = "application/xml";
+
+ /// <summary>The mime type for json content</summary>
+ /// <remarks>
+ /// Other valid json mime types include: application/json, application/x-json,
+ /// application/x-javascript, text/javascript, text/x-javascript, text/x-json, text/json
+ /// </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
+ /// value is not supplied an ArgumentOutOfRangeException exception will be raised.
+ /// </summary>
+ public string DefaultContentType
+ {
+ get { return _defaultContentType ?? String.Empty; }
+ set { _defaultContentType = value; }
+ }
+
+ /// <summary>
+ /// The extension registry to use when reading messages
+ /// </summary>
+ public ExtensionRegistry ExtensionRegistry
+ {
+ get { return _extensionRegistry ?? ExtensionRegistry.Empty; }
+ set { _extensionRegistry = value; }
+ }
+
+ /// <summary>
+ /// The name of the xml root element when reading messages
+ /// </summary>
+ public string XmlReaderRootElementName
+ {
+ get { return _xmlReaderRootElementName ?? XmlFormatReader.DefaultRootElementName; }
+ set { _xmlReaderRootElementName = value; }
+ }
+
+ /// <summary>
+ /// Xml reader options
+ /// </summary>
+ public XmlReaderOptions XmlReaderOptions { get; set; }
+
+ /// <summary>
+ /// True to use formatted output including new-lines and default indentation
+ /// </summary>
+ public bool FormattedOutput { get; set; }
+
+ /// <summary>
+ /// The name of the xml root element when writing messages
+ /// </summary>
+ public string XmlWriterRootElementName
+ {
+ get { return _xmlWriterRootElementName ?? XmlFormatWriter.DefaultRootElementName; }
+ set { _xmlWriterRootElementName = value; }
+ }
+
+ /// <summary>
+ /// Xml writer options
+ /// </summary>
+ public XmlWriterOptions XmlWriterOptions { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs b/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs
new file mode 100644
index 00000000..6177d9db
--- /dev/null
+++ b/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers;
+using System.IO;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+ /// <summary>
+ /// Extensions for the IRpcServerStub
+ /// </summary>
+ public static class ServiceExtensions
+ {
+ /// <summary>
+ /// Used to implement a service endpoint on an HTTP server. This works with services generated with the
+ /// service_generator_type option set to IRPCDISPATCH.
+ /// </summary>
+ /// <param name="stub">The service execution stub</param>
+ /// <param name="methodName">The name of the method being invoked</param>
+ /// <param name="options">optional arguments for the format reader/writer</param>
+ /// <param name="contentType">The mime type for the input stream</param>
+ /// <param name="input">The input stream</param>
+ /// <param name="responseType">The mime type for the output stream</param>
+ /// <param name="output">The output stream</param>
+ public static void HttpCallMethod(this IRpcServerStub stub, string methodName, MessageFormatOptions options,
+ string contentType, Stream input, string responseType, Stream output)
+ {
+ ICodedInputStream codedInput = MessageFormatFactory.CreateInputStream(options, contentType, input);
+ codedInput.ReadMessageStart();
+ IMessageLite response = stub.CallMethod(methodName, codedInput, options.ExtensionRegistry);
+ codedInput.ReadMessageEnd();
+ response.WriteTo(options, responseType, output);
+ }
+ }
+}
diff --git a/src/ProtocolBuffers.Serialization/JsonFormatReader.cs b/src/ProtocolBuffers.Serialization/JsonFormatReader.cs
index d4505d70..7b88cafe 100644
--- a/src/ProtocolBuffers.Serialization/JsonFormatReader.cs
+++ b/src/ProtocolBuffers.Serialization/JsonFormatReader.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
@@ -11,6 +11,7 @@ namespace Google.ProtocolBuffers.Serialization
public class JsonFormatReader : AbstractTextReader
{
private readonly JsonCursor _input;
+ // The expected token that ends the current item, either ']' or '}'
private readonly Stack<int> _stopChar;
private enum ReaderState
@@ -101,17 +102,33 @@ namespace Google.ProtocolBuffers.Serialization
}
/// <summary>
- /// Merges the contents of stream into the provided message builder
+ /// Reads the root-message preamble specific to this formatter
/// </summary>
- public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+ public override void ReadMessageStart()
{
_input.Consume('{');
_stopChar.Push('}');
_state = ReaderState.BeginObject;
- builder.WeakMergeFrom(this, registry);
- _input.Consume((char) _stopChar.Pop());
+ }
+
+ /// <summary>
+ /// Reads the root-message close specific to this formatter
+ /// </summary>
+ public override void ReadMessageEnd()
+ {
+ _input.Consume((char)_stopChar.Pop());
_state = ReaderState.EndValue;
+ }
+
+ /// <summary>
+ /// Merges the contents of stream into the provided message builder
+ /// </summary>
+ public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+ {
+ ReadMessageStart();
+ builder.WeakMergeFrom(this, registry);
+ ReadMessageEnd();
return builder;
}
diff --git a/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs b/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
index d54507cc..d7fd9e7b 100644
--- a/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
+++ b/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
@@ -101,7 +101,7 @@ namespace Google.ProtocolBuffers.Serialization
private class JsonStreamWriter : JsonFormatWriter
{
#if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
- static readonly Encoding Encoding = Encoding.UTF8;
+ static readonly Encoding Encoding = new UTF8Encoding(false);
#else
private static readonly Encoding Encoding = Encoding.ASCII;
#endif
@@ -168,7 +168,9 @@ namespace Google.ProtocolBuffers.Serialization
#endregion
+ //Tracks the writer depth and the array element count at that depth.
private readonly List<int> _counter;
+ //True if the top-level of the writer is an array as opposed to a single message.
private bool _isArray;
/// <summary>
@@ -243,7 +245,7 @@ namespace Google.ProtocolBuffers.Serialization
{
if (_counter.Count == 0)
{
- throw new InvalidOperationException("Missmatched open/close in Json writer.");
+ throw new InvalidOperationException("Mismatched open/close in Json writer.");
}
int index = _counter.Count - 1;
@@ -445,13 +447,31 @@ namespace Google.ProtocolBuffers.Serialization
/// </summary>
public override void WriteMessage(IMessageLite message)
{
+ WriteMessageStart();
+ message.WriteTo(this);
+ WriteMessageEnd();
+ }
+
+ /// <summary>
+ /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
+ /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
+ /// a call to WriteMessageEnd().
+ /// </summary>
+ public override void WriteMessageStart()
+ {
if (_isArray)
{
Seperator();
}
WriteToOutput("{");
_counter.Add(0);
- message.WriteTo(this);
+ }
+
+ /// <summary>
+ /// Used to complete a root-message previously started with a call to WriteMessageStart()
+ /// </summary>
+ public override void WriteMessageEnd()
+ {
_counter.RemoveAt(_counter.Count - 1);
WriteLine("}");
Flush();
diff --git a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
index 0c53577c..972fb149 100644
--- a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
+++ b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
@@ -98,6 +98,10 @@
</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" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="AbstractReader.cs" />
<Compile Include="AbstractTextReader.cs" />
diff --git a/src/ProtocolBuffers.Serialization/XmlFormatReader.cs b/src/ProtocolBuffers.Serialization/XmlFormatReader.cs
index cb2cb2ea..4bf6423c 100644
--- a/src/ProtocolBuffers.Serialization/XmlFormatReader.cs
+++ b/src/ProtocolBuffers.Serialization/XmlFormatReader.cs
@@ -1,7 +1,8 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
+using System.Diagnostics;
namespace Google.ProtocolBuffers.Serialization
{
@@ -14,8 +15,25 @@ namespace Google.ProtocolBuffers.Serialization
{
public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName;
private readonly XmlReader _input;
+ // Tracks the message element for each nested message read
+ private readonly Stack<ElementStackEntry> _elements;
+ // The default element name for ReadMessageStart
private string _rootElementName;
+ private struct ElementStackEntry
+ {
+ public readonly string LocalName;
+ public readonly int Depth;
+ public readonly bool IsEmpty;
+
+ public ElementStackEntry(string localName, int depth, bool isEmpty) : this()
+ {
+ LocalName = localName;
+ IsEmpty = isEmpty;
+ Depth = depth;
+ }
+ }
+
private static XmlReaderSettings DefaultSettings
{
get
@@ -72,21 +90,11 @@ namespace Google.ProtocolBuffers.Serialization
{
_input = input;
_rootElementName = DefaultRootElementName;
+ _elements = new Stack<ElementStackEntry>();
Options = XmlReaderOptions.None;
}
/// <summary>
- /// Constructs the XmlFormatReader with the XmlReader and options
- /// </summary>
- protected XmlFormatReader(XmlFormatReader copyFrom, XmlReader input)
- : base(copyFrom)
- {
- _input = input;
- _rootElementName = copyFrom._rootElementName;
- Options = copyFrom.Options;
- }
-
- /// <summary>
/// Gets or sets the options to use when reading the xml
/// </summary>
public XmlReaderOptions Options { get; set; }
@@ -113,26 +121,61 @@ namespace Google.ProtocolBuffers.Serialization
}
}
- private XmlFormatReader CloneWith(XmlReader rdr)
+ [DebuggerNonUserCode]
+ private static void Assert(bool cond)
{
- XmlFormatReader copy = new XmlFormatReader(this, rdr);
- return copy;
+ if (!cond)
+ {
+ throw new FormatException();
+ }
}
- private void NextElement()
+ /// <summary>
+ /// Reads the root-message preamble specific to this formatter
+ /// </summary>
+ public override void ReadMessageStart()
+ {
+ ReadMessageStart(_rootElementName);
+ }
+
+ /// <summary>
+ /// Reads the root-message preamble specific to this formatter
+ /// </summary>
+ public void ReadMessageStart(string element)
{
while (!_input.IsStartElement() && _input.Read())
{
continue;
}
+ Assert(_input.IsStartElement() && _input.LocalName == element);
+ _elements.Push(new ElementStackEntry(element, _input.Depth, _input.IsEmptyElement));
+ _input.Read();
}
- private static void Assert(bool cond)
+ /// <summary>
+ /// Reads the root-message close specific to this formatter, MUST be called
+ /// on the reader obtained from ReadMessageStart(string element).
+ /// </summary>
+ public override void ReadMessageEnd()
{
- if (!cond)
+ Assert(_elements.Count > 0);
+
+ ElementStackEntry stop = _elements.Peek();
+ while (_input.NodeType != XmlNodeType.EndElement && _input.NodeType != XmlNodeType.Element
+ && _input.Depth > stop.Depth && _input.Read())
{
- throw new FormatException();
+ continue;
+ }
+
+ if (!stop.IsEmpty)
+ {
+ Assert(_input.NodeType == XmlNodeType.EndElement
+ && _input.LocalName == stop.LocalName
+ && _input.Depth == stop.Depth);
+
+ _input.Read();
}
+ _elements.Pop();
}
/// <summary>
@@ -157,9 +200,9 @@ namespace Google.ProtocolBuffers.Serialization
public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
where TBuilder : IBuilderLite
{
- string field;
- Assert(PeekNext(out field) && field == element);
- ReadMessage(builder, registry);
+ ReadMessageStart(element);
+ builder.WeakMergeFrom(this, registry);
+ ReadMessageEnd();
return builder;
}
@@ -172,7 +215,21 @@ namespace Google.ProtocolBuffers.Serialization
/// </remarks>
protected override bool PeekNext(out string field)
{
- NextElement();
+ ElementStackEntry stopNode;
+ if (_elements.Count == 0)
+ {
+ stopNode = new ElementStackEntry(null, _input.Depth - 1, false);
+ }
+ else
+ {
+ stopNode = _elements.Peek();
+ }
+
+ while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read())
+ {
+ continue;
+ }
+
if (_input.IsStartElement())
{
field = _input.LocalName;
@@ -235,20 +292,9 @@ namespace Google.ProtocolBuffers.Serialization
protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
{
Assert(_input.IsStartElement());
-
- if (!_input.IsEmptyElement)
- {
- int depth = _input.Depth;
- XmlReader child = _input.ReadSubtree();
- while (!child.IsStartElement() && child.Read())
- {
- continue;
- }
- child.Read();
- builder.WeakMergeFrom(CloneWith(child), registry);
- Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
- }
- _input.Read();
+ ReadMessageStart(_input.LocalName);
+ builder.WeakMergeFrom(this, registry);
+ ReadMessageEnd();
return true;
}
@@ -270,27 +316,20 @@ namespace Google.ProtocolBuffers.Serialization
{
yield return item;
}
- yield break;
}
- if (!_input.IsEmptyElement)
+ else
{
- int depth = _input.Depth;
- XmlReader child = _input.ReadSubtree();
-
- while (!child.IsStartElement() && child.Read())
+ string found;
+ ReadMessageStart(field);
+ if (PeekNext(out found) && found == "item")
{
- continue;
- }
- child.Read();
-
- foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
- {
- yield return item;
+ foreach (string item in NonNestedArrayItems("item"))
+ {
+ yield return item;
+ }
}
- Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
+ ReadMessageEnd();
}
- _input.Read();
- yield break;
}
}
} \ No newline at end of file
diff --git a/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs b/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs
index fd36c1de..4bd27562 100644
--- a/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs
+++ b/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.IO;
using System.Text;
@@ -14,10 +14,14 @@ namespace Google.ProtocolBuffers.Serialization
/// </summary>
public class XmlFormatWriter : AbstractTextWriter
{
+ private static readonly Encoding DefaultEncoding = new UTF8Encoding(false);
public const string DefaultRootElementName = "root";
- private const int NestedArrayFlag = 0x0001;
+
private readonly XmlWriter _output;
+ // The default element name used for WriteMessageStart
private string _rootElementName;
+ // Used to assert matching WriteMessageStart/WriteMessageEnd calls
+ private int _messageOpenCount;
private static XmlWriterSettings DefaultSettings(Encoding encoding)
{
@@ -43,7 +47,7 @@ namespace Google.ProtocolBuffers.Serialization
/// </summary>
public static XmlFormatWriter CreateInstance(Stream output)
{
- return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(Encoding.UTF8)));
+ return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(DefaultEncoding)));
}
/// <summary>
@@ -65,21 +69,11 @@ namespace Google.ProtocolBuffers.Serialization
protected XmlFormatWriter(XmlWriter output)
{
_output = output;
+ _messageOpenCount = 0;
_rootElementName = DefaultRootElementName;
}
/// <summary>
- /// Closes the underlying XmlTextWriter
- /// </summary>
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- _output.Close();
- }
- }
-
- /// <summary>
/// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
/// </summary>
public string RootElementName
@@ -112,17 +106,30 @@ namespace Google.ProtocolBuffers.Serialization
}
/// <summary>
- /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
+ /// Completes any pending write operations
/// </summary>
- public override void WriteMessage(IMessageLite message)
+ public override void Flush()
{
- WriteMessage(_rootElementName, message);
+ _output.Flush();
+ base.Flush();
}
/// <summary>
- /// Writes a message as an element with the given name
+ /// Used to write the root-message preamble, in xml this is open element for RootElementName,
+ /// by default "&lt;root&gt;". After this call you can call IMessageLite.MergeTo(...) and
+ /// complete the message with a call to WriteMessageEnd().
/// </summary>
- public void WriteMessage(string elementName, IMessageLite message)
+ public override void WriteMessageStart()
+ {
+ WriteMessageStart(_rootElementName);
+ }
+
+ /// <summary>
+ /// Used to write the root-message preamble, in xml this is open element for elementName.
+ /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
+ /// a call to WriteMessageEnd().
+ /// </summary>
+ public void WriteMessageStart(string elementName)
{
if (TestOption(XmlWriterOptions.OutputJsonTypes))
{
@@ -133,10 +140,40 @@ namespace Google.ProtocolBuffers.Serialization
{
_output.WriteStartElement(elementName);
}
+ _messageOpenCount++;
+ }
+
+ /// <summary>
+ /// Used to complete a root-message previously started with a call to WriteMessageStart()
+ /// </summary>
+ public override void WriteMessageEnd()
+ {
+ if (_messageOpenCount <= 0)
+ {
+ throw new InvalidOperationException();
+ }
- message.WriteTo(this);
_output.WriteEndElement();
_output.Flush();
+ _messageOpenCount--;
+ }
+
+ /// <summary>
+ /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
+ /// </summary>
+ public override void WriteMessage(IMessageLite message)
+ {
+ WriteMessage(_rootElementName, message);
+ }
+
+ /// <summary>
+ /// Writes a message as an element with the given name
+ /// </summary>
+ public void WriteMessage(string elementName, IMessageLite message)
+ {
+ WriteMessageStart(elementName);
+ message.WriteTo(this);
+ WriteMessageEnd();
}
/// <summary>
diff --git a/src/ProtocolBuffers.Test/Compatibility/JsonCompatibilityTests.cs b/src/ProtocolBuffers.Test/Compatibility/JsonCompatibilityTests.cs
index fb895040..6b368b7d 100644
--- a/src/ProtocolBuffers.Test/Compatibility/JsonCompatibilityTests.cs
+++ b/src/ProtocolBuffers.Test/Compatibility/JsonCompatibilityTests.cs
@@ -12,6 +12,24 @@ namespace Google.ProtocolBuffers.Compatibility
{
StringWriter sw = new StringWriter();
JsonFormatWriter.CreateInstance(sw)
+ .WriteMessage(message);
+ return sw.ToString();
+ }
+
+ protected override TBuilder DeserializeMessage<TMessage, TBuilder>(object message, TBuilder builder, ExtensionRegistry registry)
+ {
+ JsonFormatReader.CreateInstance((string)message).Merge(builder);
+ return builder;
+ }
+ }
+
+ [TestFixture]
+ public class JsonCompatibilityFormattedTests : CompatibilityTests
+ {
+ protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
+ {
+ StringWriter sw = new StringWriter();
+ JsonFormatWriter.CreateInstance(sw)
.Formatted()
.WriteMessage(message);
return sw.ToString();
diff --git a/src/ProtocolBuffers.Test/Compatibility/XmlCompatibilityTests.cs b/src/ProtocolBuffers.Test/Compatibility/XmlCompatibilityTests.cs
index 50ef6f03..62b9456a 100644
--- a/src/ProtocolBuffers.Test/Compatibility/XmlCompatibilityTests.cs
+++ b/src/ProtocolBuffers.Test/Compatibility/XmlCompatibilityTests.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Xml;
using Google.ProtocolBuffers.Serialization;
using Google.ProtocolBuffers.TestProtos;
using NUnit.Framework;
@@ -22,4 +23,24 @@ namespace Google.ProtocolBuffers.Compatibility
return reader.Merge("root", builder, registry);
}
}
+
+ [TestFixture]
+ public class XmlCompatibilityFormattedTests : CompatibilityTests
+ {
+ protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
+ {
+ StringWriter text = new StringWriter();
+ XmlWriter xwtr = XmlWriter.Create(text, new XmlWriterSettings { Indent = true, IndentChars = " " });
+
+ XmlFormatWriter writer = XmlFormatWriter.CreateInstance(xwtr).SetOptions(XmlWriterOptions.OutputNestedArrays);
+ writer.WriteMessage("root", message);
+ return text.ToString();
+ }
+
+ protected override TBuilder DeserializeMessage<TMessage, TBuilder>(object message, TBuilder builder, ExtensionRegistry registry)
+ {
+ XmlFormatReader reader = XmlFormatReader.CreateInstance((string)message).SetOptions(XmlReaderOptions.ReadNestedArrays);
+ return reader.Merge("root", builder, registry);
+ }
+ }
} \ No newline at end of file
diff --git a/src/ProtocolBuffers.Test/Properties/AssemblyInfo.cs b/src/ProtocolBuffers.Test/Properties/AssemblyInfo.cs
index 9c51aca6..5aa14a93 100644
--- a/src/ProtocolBuffers.Test/Properties/AssemblyInfo.cs
+++ b/src/ProtocolBuffers.Test/Properties/AssemblyInfo.cs
@@ -12,7 +12,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ProtocolBuffers.Test")]
-[assembly: AssemblyCopyright("Copyright © 2008")]
+[assembly: AssemblyCopyright("Copyright � 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj
index 83d2ff5b..2633c3cb 100644
--- a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj
+++ b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj
@@ -88,6 +88,8 @@
</Compile>
<Compile Include="Compatibility\TextCompatibilityTests.cs" />
<Compile Include="Compatibility\XmlCompatibilityTests.cs" />
+ <Compile Include="TestRpcForMimeTypes.cs" />
+ <Compile Include="TestReaderForUrlEncoded.cs" />
<Compile Include="CSharpOptionsTest.cs" />
<Compile Include="DeprecatedMemberTest.cs" />
<Compile Include="DescriptorsTest.cs" />
@@ -107,6 +109,7 @@
<Compile Include="SerializableTest.cs" />
<Compile Include="ServiceTest.cs" />
<Compile Include="TestCornerCases.cs" />
+ <Compile Include="TestMimeMessageFormats.cs" />
<Compile Include="TestProtos\UnitTestCSharpOptionsProtoFile.cs" />
<Compile Include="TestProtos\UnitTestCustomOptionsProtoFile.cs" />
<Compile Include="TestProtos\UnitTestEmbedOptimizeForProtoFile.cs" />
diff --git a/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs b/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs
new file mode 100644
index 00000000..0ed8d381
--- /dev/null
+++ b/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs
@@ -0,0 +1,264 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Google.ProtocolBuffers.Serialization;
+using Google.ProtocolBuffers.Serialization.Http;
+using Google.ProtocolBuffers.TestProtos;
+using NUnit.Framework;
+
+namespace Google.ProtocolBuffers
+{
+ [TestFixture]
+ public class TestMimeMessageFormats
+ {
+ // There is a whole host of various json mime types in use around the net, this is the set we accept...
+ readonly IEnumerable<string> JsonTypes = new string[] { "application/json", "application/x-json", "application/x-javascript", "text/javascript", "text/x-javascript", "text/x-json", "text/json" };
+ readonly IEnumerable<string> XmlTypes = new string[] { "text/xml", "application/xml" };
+ readonly IEnumerable<string> ProtobufTypes = new string[] { "application/binary", "application/x-protobuf", "application/vnd.google.protobuf" };
+
+ [Test]
+ public void TestReadJsonMimeTypes()
+ {
+ foreach (string type in JsonTypes)
+ {
+ Assert.IsTrue(
+ MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+ is JsonFormatReader);
+ }
+ Assert.IsTrue(
+ MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null)
+ is JsonFormatReader);
+ }
+ [Test]
+ public void TestWriteJsonMimeTypes()
+ {
+ foreach (string type in JsonTypes)
+ {
+ Assert.IsTrue(
+ MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+ is JsonFormatWriter);
+ }
+ Assert.IsTrue(
+ MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null)
+ is JsonFormatWriter);
+ }
+ [Test]
+ public void TestReadXmlMimeTypes()
+ {
+ foreach (string type in XmlTypes)
+ {
+ Assert.IsTrue(
+ MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+ is XmlFormatReader);
+ }
+ Assert.IsTrue(
+ MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null)
+ is XmlFormatReader);
+ }
+ [Test]
+ public void TestWriteXmlMimeTypes()
+ {
+ foreach (string type in XmlTypes)
+ {
+ Assert.IsTrue(
+ MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+ is XmlFormatWriter);
+ }
+ Assert.IsTrue(
+ MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null)
+ is XmlFormatWriter);
+ }
+ [Test]
+ public void TestReadProtoMimeTypes()
+ {
+ foreach (string type in ProtobufTypes)
+ {
+ Assert.IsTrue(
+ MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+ is CodedInputStream);
+ }
+ Assert.IsTrue(
+ MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null)
+ is CodedInputStream);
+ }
+ [Test]
+ public void TestWriteProtoMimeTypes()
+ {
+ foreach (string type in ProtobufTypes)
+ {
+ Assert.IsTrue(
+ MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+ is CodedOutputStream);
+ }
+ Assert.IsTrue(
+ MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null)
+ is CodedOutputStream);
+ }
+ [Test]
+ public void TestMergeFromJsonType()
+ {
+ TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+ new MessageFormatOptions(), "application/json", new MemoryStream(Encoding.ASCII.GetBytes(
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToJson()
+ )))
+ .Build();
+ Assert.AreEqual("a", msg.Text);
+ Assert.AreEqual(1, msg.Number);
+ }
+ [Test]
+ public void TestMergeFromXmlType()
+ {
+ TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+ new MessageFormatOptions(), "application/xml", new MemoryStream(Encoding.ASCII.GetBytes(
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToXml()
+ )))
+ .Build();
+ Assert.AreEqual("a", msg.Text);
+ Assert.AreEqual(1, msg.Number);
+ }
+ [Test]
+ public void TestMergeFromProtoType()
+ {
+ TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+ new MessageFormatOptions(), "application/vnd.google.protobuf", new MemoryStream(
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray()
+ ))
+ .Build();
+ Assert.AreEqual("a", msg.Text);
+ Assert.AreEqual(1, msg.Number);
+ }
+ [Test]
+ public void TestWriteToJsonType()
+ {
+ MemoryStream ms = new MemoryStream();
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+ .WriteTo(new MessageFormatOptions(), "application/json", ms);
+
+ Assert.AreEqual(@"{""text"":""a"",""number"":1}", Encoding.UTF8.GetString(ms.ToArray()));
+ }
+ [Test]
+ public void TestWriteToXmlType()
+ {
+ MemoryStream ms = new MemoryStream();
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+ .WriteTo(new MessageFormatOptions(), "application/xml", ms);
+
+ Assert.AreEqual("<root><text>a</text><number>1</number></root>", Encoding.UTF8.GetString(ms.ToArray()));
+ }
+ [Test]
+ public void TestWriteToProtoType()
+ {
+ MemoryStream ms = new MemoryStream();
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+ .WriteTo(new MessageFormatOptions(), "application/vnd.google.protobuf", ms);
+
+ byte[] bytes = TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray();
+ Assert.AreEqual(bytes, ms.ToArray());
+ }
+ [Test]
+ public void TestXmlReaderOptions()
+ {
+ MemoryStream ms = new MemoryStream();
+ XmlFormatWriter.CreateInstance(ms)
+ .SetOptions(XmlWriterOptions.OutputNestedArrays)
+ .WriteMessage("my-root-node", TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build());
+ ms.Position = 0;
+
+ MessageFormatOptions options = new MessageFormatOptions()
+ {
+ XmlReaderOptions = XmlReaderOptions.ReadNestedArrays,
+ XmlReaderRootElementName = "my-root-node"
+ };
+
+ TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+ options, "application/xml", ms)
+ .Build();
+
+ Assert.AreEqual("a", msg.Text);
+ Assert.AreEqual(1, msg.NumbersList[0]);
+ Assert.AreEqual(2, msg.NumbersList[1]);
+
+ }
+ [Test]
+ public void TestXmlWriterOptions()
+ {
+ TestXmlMessage message = TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build();
+ MessageFormatOptions options = new MessageFormatOptions()
+ {
+ XmlWriterOptions = XmlWriterOptions.OutputNestedArrays,
+ XmlWriterRootElementName = "root-node"
+ };
+
+ MemoryStream ms = new MemoryStream();
+ message.WriteTo(options, "application/xml", ms);
+ ms.Position = 0;
+
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ XmlFormatReader.CreateInstance(ms)
+ .SetOptions(XmlReaderOptions.ReadNestedArrays)
+ .Merge("root-node", builder);
+
+ Assert.AreEqual("a", builder.Text);
+ Assert.AreEqual(1, builder.NumbersList[0]);
+ Assert.AreEqual(2, builder.NumbersList[1]);
+ }
+ [Test]
+ public void TestJsonFormatted()
+ {
+ MemoryStream ms = new MemoryStream();
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+ .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/json", ms);
+
+ Assert.AreEqual("{\r\n \"text\": \"a\",\r\n \"number\": 1\r\n}", Encoding.UTF8.GetString(ms.ToArray()));
+ }
+ [Test]
+ public void TestXmlFormatted()
+ {
+ MemoryStream ms = new MemoryStream();
+ TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+ .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/xml", ms);
+
+ 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);
+ }
+ }
+}
diff --git a/src/ProtocolBuffers.Test/TestRpcForMimeTypes.cs b/src/ProtocolBuffers.Test/TestRpcForMimeTypes.cs
new file mode 100644
index 00000000..a8d2b163
--- /dev/null
+++ b/src/ProtocolBuffers.Test/TestRpcForMimeTypes.cs
@@ -0,0 +1,386 @@
+#region Copyright notice and license
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://github.com/jskeet/dotnet-protobufs/
+// Original C++/Java/Python code:
+// http://code.google.com/p/protobuf/
+//
+// 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;
+using Google.ProtocolBuffers;
+using Google.ProtocolBuffers.Serialization.Http;
+using Google.ProtocolBuffers.TestProtos;
+using NUnit.Framework;
+using System.IO;
+using Google.ProtocolBuffers.Serialization;
+using System.Text;
+
+namespace Google.ProtocolBuffers
+{
+ /// <summary>
+ /// This class verifies the correct code is generated from unittest_rpc_interop.proto and provides a small demonstration
+ /// of using the new IRpcDispatch to write a client/server
+ /// </summary>
+ [TestFixture]
+ public class TestRpcForMimeTypes
+ {
+ /// <summary>
+ /// A sample implementation of the ISearchService for testing
+ /// </summary>
+ private class ExampleSearchImpl : ISearchService
+ {
+ SearchResponse ISearchService.Search(SearchRequest searchRequest)
+ {
+ if (searchRequest.CriteriaCount == 0)
+ {
+ throw new ArgumentException("No criteria specified.", new InvalidOperationException());
+ }
+ SearchResponse.Builder resp = SearchResponse.CreateBuilder();
+ foreach (string criteria in searchRequest.CriteriaList)
+ {
+ resp.AddResults(
+ SearchResponse.Types.ResultItem.CreateBuilder().SetName(criteria).SetUrl("http://search.com").
+ Build());
+ }
+ return resp.Build();
+ }
+
+ SearchResponse ISearchService.RefineSearch(RefineSearchRequest refineSearchRequest)
+ {
+ SearchResponse.Builder resp = refineSearchRequest.PreviousResults.ToBuilder();
+ foreach (string criteria in refineSearchRequest.CriteriaList)
+ {
+ resp.AddResults(
+ SearchResponse.Types.ResultItem.CreateBuilder().SetName(criteria).SetUrl("http://refine.com").
+ Build());
+ }
+ return resp.Build();
+ }
+ }
+
+ /// <summary>
+ /// An example extraction of the wire protocol
+ /// </summary>
+ private interface IHttpTransfer
+ {
+ void Execute(string method, string contentType, Stream input, string acceptType, Stream output);
+ }
+
+ /// <summary>
+ /// An example of a server responding to a web/http request
+ /// </summary>
+ private class ExampleHttpServer : IHttpTransfer
+ {
+ public readonly MessageFormatOptions Options =
+ new MessageFormatOptions
+ {
+ ExtensionRegistry = ExtensionRegistry.Empty,
+ FormattedOutput = true,
+ XmlReaderOptions = XmlReaderOptions.ReadNestedArrays,
+ XmlReaderRootElementName = "request",
+ XmlWriterOptions = XmlWriterOptions.OutputNestedArrays,
+ XmlWriterRootElementName = "response"
+ };
+
+ private readonly IRpcServerStub _stub;
+
+ public ExampleHttpServer(ISearchService implementation)
+ {
+ //on the server, we create a dispatch to call the appropriate method by name
+ IRpcDispatch dispatch = new SearchService.Dispatch(implementation);
+ //we then wrap that dispatch in a server stub which will deserialize the wire bytes to the message
+ //type appropriate for the method name being invoked.
+ _stub = new SearchService.ServerStub(dispatch);
+ }
+
+ void IHttpTransfer.Execute(string method, string contentType, Stream input, string acceptType, Stream output)
+ {
+ //Extension for: Google.ProtocolBuffers.Serialization.Http.ServiceExtensions.HttpCallMethod(_stub,
+ _stub.HttpCallMethod(
+ method, Options,
+ contentType, input,
+ acceptType, output
+ );
+ }
+ }
+
+ /// <summary>
+ /// An example of a client sending a wire request
+ /// </summary>
+ private class ExampleClient : IRpcDispatch
+ {
+ public readonly MessageFormatOptions Options =
+ new MessageFormatOptions
+ {
+ ExtensionRegistry = ExtensionRegistry.Empty,
+ FormattedOutput = true,
+ XmlReaderOptions = XmlReaderOptions.ReadNestedArrays,
+ XmlReaderRootElementName = "response",
+ XmlWriterOptions = XmlWriterOptions.OutputNestedArrays,
+ XmlWriterRootElementName = "request"
+ };
+
+
+ private readonly IHttpTransfer _wire;
+ private readonly string _mimeType;
+
+ public ExampleClient(IHttpTransfer wire, string mimeType)
+ {
+ _wire = wire;
+ _mimeType = mimeType;
+ }
+
+ TMessage IRpcDispatch.CallMethod<TMessage, TBuilder>(string method, IMessageLite request,
+ IBuilderLite<TMessage, TBuilder> response)
+ {
+ MemoryStream input = new MemoryStream();
+ MemoryStream output = new MemoryStream();
+
+ //Write to _mimeType format
+ request.WriteTo(Options, _mimeType, input);
+
+ input.Position = 0;
+ _wire.Execute(method, _mimeType, input, _mimeType, output);
+
+ //Read from _mimeType format
+ output.Position = 0;
+ response.MergeFrom(Options, _mimeType, output);
+
+ return response.Build();
+ }
+ }
+
+ /// <summary>
+ /// Test sending and recieving messages via text/json
+ /// </summary>
+ [Test]
+ public void TestClientServerWithJsonFormat()
+ {
+ ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+ //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+ IHttpTransfer wire = server;
+
+ ISearchService client = new SearchService(new ExampleClient(wire, "text/json"));
+ //now the client has a real, typed, interface to work with:
+ SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+ Assert.AreEqual(1, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ //The test part of this, call the only other method
+ result =
+ client.RefineSearch(
+ RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+ Assert.AreEqual(2, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ Assert.AreEqual("Refine", result.ResultsList[1].Name);
+ Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+ }
+
+ /// <summary>
+ /// Test sending and recieving messages via text/json
+ /// </summary>
+ [Test]
+ public void TestClientServerWithXmlFormat()
+ {
+ ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+ //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+ IHttpTransfer wire = server;
+
+ ISearchService client = new SearchService(new ExampleClient(wire, "text/xml"));
+ //now the client has a real, typed, interface to work with:
+ SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+ Assert.AreEqual(1, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ //The test part of this, call the only other method
+ result =
+ client.RefineSearch(
+ RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+ Assert.AreEqual(2, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ Assert.AreEqual("Refine", result.ResultsList[1].Name);
+ Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+ }
+
+ /// <summary>
+ /// Test sending and recieving messages via text/json
+ /// </summary>
+ [Test]
+ public void TestClientServerWithProtoFormat()
+ {
+ ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+ //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+ IHttpTransfer wire = server;
+
+ ISearchService client = new SearchService(new ExampleClient(wire, "application/x-protobuf"));
+ //now the client has a real, typed, interface to work with:
+ SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+ Assert.AreEqual(1, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ //The test part of this, call the only other method
+ result =
+ client.RefineSearch(
+ RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+ Assert.AreEqual(2, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ Assert.AreEqual("Refine", result.ResultsList[1].Name);
+ Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+ }
+
+ /// <summary>
+ /// Test sending and recieving messages via text/json
+ /// </summary>
+ [Test]
+ public void TestClientServerWithCustomFormat()
+ {
+ ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+ //Setup our custom mime-type format as the only format supported:
+ server.Options.MimeInputTypes.Clear();
+ server.Options.MimeInputTypes.Add("foo/bar", CodedInputStream.CreateInstance);
+ server.Options.MimeOutputTypes.Clear();
+ server.Options.MimeOutputTypes.Add("foo/bar", CodedOutputStream.CreateInstance);
+
+ //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+ IHttpTransfer wire = server;
+
+ ExampleClient exclient = new ExampleClient(wire, "foo/bar");
+ //Add our custom mime-type format
+ exclient.Options.MimeInputTypes.Add("foo/bar", CodedInputStream.CreateInstance);
+ exclient.Options.MimeOutputTypes.Add("foo/bar", CodedOutputStream.CreateInstance);
+ ISearchService client = new SearchService(exclient);
+
+ //now the client has a real, typed, interface to work with:
+ SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+ Assert.AreEqual(1, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ //The test part of this, call the only other method
+ result =
+ client.RefineSearch(
+ RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+ Assert.AreEqual(2, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ Assert.AreEqual("Refine", result.ResultsList[1].Name);
+ Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+ }
+
+ /// <summary>
+ /// Test sending and recieving messages via text/json
+ /// </summary>
+ [Test]
+ public void TestServerWithUriFormat()
+ {
+ ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+ //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+ IHttpTransfer wire = server;
+
+ MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes("?Criteria=Test&Criteria=Test+of%20URI"));
+ MemoryStream output = new MemoryStream();
+
+ //Call the server
+ wire.Execute("Search",
+ MessageFormatOptions.ContentFormUrlEncoded, input,
+ MessageFormatOptions.ContentTypeProtoBuffer, output
+ );
+
+ SearchResponse result = SearchResponse.ParseFrom(output.ToArray());
+ Assert.AreEqual(2, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+ Assert.AreEqual("Test of URI", result.ResultsList[1].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[1].Url);
+ }
+
+ /// <summary>
+ /// Test sending and recieving messages via text/json
+ /// </summary>
+ [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void TestInvalidMimeType()
+ {
+ ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+ //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+ IHttpTransfer wire = server;
+
+ MemoryStream input = new MemoryStream();
+ MemoryStream output = new MemoryStream();
+
+ //Call the server
+ wire.Execute("Search",
+ "bad/mime", input,
+ MessageFormatOptions.ContentTypeProtoBuffer, output
+ );
+ Assert.Fail();
+ }
+
+ /// <summary>
+ /// Test sending and recieving messages via text/json
+ /// </summary>
+ [Test]
+ public void TestDefaultMimeType()
+ {
+ ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+
+ //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+ IHttpTransfer wire = server;
+
+
+ MemoryStream input = new MemoryStream(new SearchRequest.Builder().AddCriteria("Test").Build().ToByteArray());
+ MemoryStream output = new MemoryStream();
+
+ //With this default set, any invalid/unknown mime-type will be mapped to use that format
+ server.Options.DefaultContentType = MessageFormatOptions.ContentTypeProtoBuffer;
+
+ wire.Execute("Search",
+ "foo", input,
+ "bar", output
+ );
+
+ SearchResponse result = SearchResponse.ParseFrom(output.ToArray());
+ Assert.AreEqual(1, result.ResultsCount);
+ Assert.AreEqual("Test", result.ResultsList[0].Name);
+ Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/ProtocolBuffers.Test/TestWriterFormatJson.cs b/src/ProtocolBuffers.Test/TestWriterFormatJson.cs
index 3f534fc0..1a1a480d 100644
--- a/src/ProtocolBuffers.Test/TestWriterFormatJson.cs
+++ b/src/ProtocolBuffers.Test/TestWriterFormatJson.cs
@@ -10,6 +10,70 @@ namespace Google.ProtocolBuffers
[TestFixture]
public class TestWriterFormatJson
{
+ [Test]
+ public void Example_FromJson()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+
+ builder.MergeFromJson(@"{""valid"":true}");
+
+ TestXmlMessage message = builder.Build();
+ Assert.AreEqual(true, message.Valid);
+ }
+
+ [Test]
+ public void Example_ToJson()
+ {
+ TestXmlMessage message =
+ TestXmlMessage.CreateBuilder()
+ .SetValid(true)
+ .Build();
+
+ string json = message.ToJson();
+
+ Assert.AreEqual(@"{""valid"":true}", json);
+ }
+
+ [Test]
+ public void Example_WriteJsonUsingICodedOutputStream()
+ {
+ TestXmlMessage message =
+ TestXmlMessage.CreateBuilder()
+ .SetValid(true)
+ .Build();
+
+ using (TextWriter output = new StringWriter())
+ {
+ ICodedOutputStream writer = JsonFormatWriter.CreateInstance(output);
+ writer.WriteMessageStart(); //manually begin the message, output is '{'
+
+ writer.Flush();
+ Assert.AreEqual("{", output.ToString());
+
+ ICodedOutputStream stream = writer;
+ message.WriteTo(stream); //write the message normally
+
+ writer.Flush();
+ Assert.AreEqual(@"{""valid"":true", output.ToString());
+
+ writer.WriteMessageEnd(); //manually write the end message '}'
+ Assert.AreEqual(@"{""valid"":true}", output.ToString());
+ }
+ }
+
+ [Test]
+ public void Example_ReadJsonUsingICodedInputStream()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ ICodedInputStream reader = JsonFormatReader.CreateInstance(@"{""valid"":true}");
+
+ reader.ReadMessageStart(); //manually read the begin the message '{'
+
+ builder.MergeFrom(reader); //write the message normally
+
+ reader.ReadMessageEnd(); //manually read the end message '}'
+ }
+
protected string Content;
[System.Diagnostics.DebuggerNonUserCode]
protected void FormatterAssert<TMessage>(TMessage message, params string[] expecting) where TMessage : IMessageLite
@@ -337,6 +401,28 @@ namespace Google.ProtocolBuffers
Assert.AreEqual(3, ordinal);
Assert.AreEqual(3, builder.TextlinesCount);
}
+ [Test]
+ public void TestReadWriteJsonWithoutRoot()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build();
+
+ string Json;
+ using (StringWriter sw = new StringWriter())
+ {
+ ICodedOutputStream output = JsonFormatWriter.CreateInstance(sw);
+
+ message.WriteTo(output);
+ output.Flush();
+ Json = sw.ToString();
+ }
+ Assert.AreEqual(@"""text"":""abc"",""number"":123", Json);
+
+ ICodedInputStream input = JsonFormatReader.CreateInstance(Json);
+ TestXmlMessage copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build();
+
+ Assert.AreEqual(message, copy);
+ }
[Test,ExpectedException(typeof(RecursionLimitExceededException))]
public void TestRecursiveLimit()
{
diff --git a/src/ProtocolBuffers.Test/TestWriterFormatXml.cs b/src/ProtocolBuffers.Test/TestWriterFormatXml.cs
index 2ffca9fb..a52d04e1 100644
--- a/src/ProtocolBuffers.Test/TestWriterFormatXml.cs
+++ b/src/ProtocolBuffers.Test/TestWriterFormatXml.cs
@@ -13,6 +13,65 @@ namespace Google.ProtocolBuffers
public class TestWriterFormatXml
{
[Test]
+ public void Example_FromXml()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+
+ XmlReader rdr = XmlReader.Create(new StringReader(@"<root><valid>true</valid></root>"));
+ builder.MergeFromXml(rdr);
+
+ TestXmlMessage message = builder.Build();
+ Assert.AreEqual(true, message.Valid);
+ }
+
+ [Test]
+ public void Example_ToXml()
+ {
+ TestXmlMessage message =
+ TestXmlMessage.CreateBuilder()
+ .SetValid(true)
+ .Build();
+
+ string Xml = message.ToXml();
+
+ Assert.AreEqual(@"<root><valid>true</valid></root>", Xml);
+ }
+
+ [Test]
+ public void Example_WriteXmlUsingICodedOutputStream()
+ {
+ TestXmlMessage message =
+ TestXmlMessage.CreateBuilder()
+ .SetValid(true)
+ .Build();
+
+ using (TextWriter output = new StringWriter())
+ {
+ ICodedOutputStream writer = XmlFormatWriter.CreateInstance(output);
+ writer.WriteMessageStart(); //manually begin the message, output is '{'
+
+ ICodedOutputStream stream = writer;
+ message.WriteTo(stream); //write the message normally
+
+ writer.WriteMessageEnd(); //manually write the end message '}'
+ Assert.AreEqual(@"<root><valid>true</valid></root>", output.ToString());
+ }
+ }
+
+ [Test]
+ public void Example_ReadXmlUsingICodedInputStream()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root><valid>true</valid></root>");
+
+ reader.ReadMessageStart(); //manually read the begin the message '{'
+
+ builder.MergeFrom(reader); //read the message normally
+
+ reader.ReadMessageEnd(); //manually read the end message '}'
+ }
+
+ [Test]
public void TestToXmlParseFromXml()
{
TestAllTypes msg = new TestAllTypes.Builder().SetDefaultBool(true).Build();
@@ -135,7 +194,9 @@ namespace Google.ProtocolBuffers
.Build();
StringWriter sw = new StringWriter();
- XmlFormatWriter.CreateInstance(sw).WriteMessage("root", message);
+ XmlWriter xwtr = XmlWriter.Create(sw, new XmlWriterSettings {Indent = true, IndentChars = " "});
+
+ XmlFormatWriter.CreateInstance(xwtr).WriteMessage("root", message);
string xml = sw.ToString();
@@ -162,7 +223,9 @@ namespace Google.ProtocolBuffers
.Build();
StringWriter sw = new StringWriter();
- XmlFormatWriter.CreateInstance(sw)
+ XmlWriter xwtr = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, IndentChars = " " });
+
+ XmlFormatWriter.CreateInstance(xwtr)
.SetOptions(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputEnumValues)
.WriteMessage("root", message);
@@ -324,6 +387,60 @@ namespace Google.ProtocolBuffers
TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder(), registry).Build();
Assert.AreEqual(message, copy);
}
+ [Test]
+ public void TestXmlReadEmptyRoot()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root/>");
+
+ reader.ReadMessageStart(); //manually read the begin the message '{'
+
+ builder.MergeFrom(reader); //write the message normally
+
+ reader.ReadMessageEnd(); //manually read the end message '}'
+ }
+
+ [Test]
+ public void TestXmlReadEmptyChild()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root><text /></root>");
+
+ reader.ReadMessageStart(); //manually read the begin the message '{'
+
+ builder.MergeFrom(reader); //write the message normally
+ Assert.IsTrue(builder.HasText);
+ Assert.AreEqual(String.Empty, builder.Text);
+ }
+
+ [Test]
+ public void TestXmlReadWriteWithoutRoot()
+ {
+ TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+ TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build();
+
+ string xml;
+ using (StringWriter sw = new StringWriter())
+ {
+ ICodedOutputStream output = XmlFormatWriter.CreateInstance(
+ XmlWriter.Create(sw, new XmlWriterSettings() { ConformanceLevel = ConformanceLevel.Fragment }));
+
+ message.WriteTo(output);
+ output.Flush();
+ xml = sw.ToString();
+ }
+ Assert.AreEqual("<text>abc</text><number>123</number>", xml);
+
+ TestXmlMessage copy;
+ using (XmlReader xr = XmlReader.Create(new StringReader(xml), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Fragment }))
+ {
+ ICodedInputStream input = XmlFormatReader.CreateInstance(xr);
+ copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build();
+ }
+
+ Assert.AreEqual(message, copy);
+ }
+
[Test, ExpectedException(typeof(RecursionLimitExceededException))]
public void TestRecursiveLimit()
{
diff --git a/src/ProtocolBuffers/CodedInputStream.cs b/src/ProtocolBuffers/CodedInputStream.cs
index 509b4164..35ebba92 100644
--- a/src/ProtocolBuffers/CodedInputStream.cs
+++ b/src/ProtocolBuffers/CodedInputStream.cs
@@ -144,6 +144,9 @@ namespace Google.ProtocolBuffers
#endregion
+ void ICodedInputStream.ReadMessageStart() { }
+ void ICodedInputStream.ReadMessageEnd() { }
+
#region Validation
/// <summary>
diff --git a/src/ProtocolBuffers/CodedOutputStream.cs b/src/ProtocolBuffers/CodedOutputStream.cs
index 09099ee3..b9c7b587 100644
--- a/src/ProtocolBuffers/CodedOutputStream.cs
+++ b/src/ProtocolBuffers/CodedOutputStream.cs
@@ -125,6 +125,9 @@ namespace Google.ProtocolBuffers
}
#endregion
+
+ void ICodedOutputStream.WriteMessageStart() { }
+ void ICodedOutputStream.WriteMessageEnd() { Flush(); }
#region Writing of unknown fields
diff --git a/src/ProtocolBuffers/ICodedInputStream.cs b/src/ProtocolBuffers/ICodedInputStream.cs
index 1d9d26e3..a3c0f30b 100644
--- a/src/ProtocolBuffers/ICodedInputStream.cs
+++ b/src/ProtocolBuffers/ICodedInputStream.cs
@@ -46,6 +46,24 @@ namespace Google.ProtocolBuffers
public interface ICodedInputStream
{
/// <summary>
+ /// Reads any message initialization data expected from the input stream
+ /// </summary>
+ /// <remarks>
+ /// This is primarily used by text formats and unnecessary for protobuffers' own
+ /// binary format. The API for MessageStart/End was added for consistent handling
+ /// of output streams regardless of the actual writer implementation.
+ /// </remarks>
+ void ReadMessageStart();
+ /// <summary>
+ /// Reads any message finalization data expected from the input stream
+ /// </summary>
+ /// <remarks>
+ /// This is primarily used by text formats and unnecessary for protobuffers' own
+ /// binary format. The API for MessageStart/End was added for consistent handling
+ /// of output streams regardless of the actual writer implementation.
+ /// </remarks>
+ void ReadMessageEnd();
+ /// <summary>
/// Attempt to read a field tag, returning false if we have reached the end
/// of the input data.
/// </summary>
diff --git a/src/ProtocolBuffers/ICodedOutputStream.cs b/src/ProtocolBuffers/ICodedOutputStream.cs
index 861d3fec..8619508a 100644
--- a/src/ProtocolBuffers/ICodedOutputStream.cs
+++ b/src/ProtocolBuffers/ICodedOutputStream.cs
@@ -52,6 +52,24 @@ namespace Google.ProtocolBuffers
public interface ICodedOutputStream
{
/// <summary>
+ /// Writes any message initialization data needed to the output stream
+ /// </summary>
+ /// <remarks>
+ /// This is primarily used by text formats and unnecessary for protobuffers' own
+ /// binary format. The API for MessageStart/End was added for consistent handling
+ /// of output streams regardless of the actual writer implementation.
+ /// </remarks>
+ void WriteMessageStart();
+ /// <summary>
+ /// Writes any message finalization data needed to the output stream
+ /// </summary>
+ /// <remarks>
+ /// This is primarily used by text formats and unnecessary for protobuffers' own
+ /// binary format. The API for MessageStart/End was added for consistent handling
+ /// of output streams regardless of the actual writer implementation.
+ /// </remarks>
+ void WriteMessageEnd();
+ /// <summary>
/// Indicates that all temporary buffers be written to the final output.
/// </summary>
void Flush();