xref: /aosp_15_r20/external/protobuf/csharp/src/Google.Protobuf/JsonParser.cs (revision 1b3f573f81763fcece89efc2b6a5209149e44ab8)
1*1b3f573fSAndroid Build Coastguard Worker #region Copyright notice and license
2*1b3f573fSAndroid Build Coastguard Worker // Protocol Buffers - Google's data interchange format
3*1b3f573fSAndroid Build Coastguard Worker // Copyright 2015 Google Inc.  All rights reserved.
4*1b3f573fSAndroid Build Coastguard Worker // https://developers.google.com/protocol-buffers/
5*1b3f573fSAndroid Build Coastguard Worker //
6*1b3f573fSAndroid Build Coastguard Worker // Redistribution and use in source and binary forms, with or without
7*1b3f573fSAndroid Build Coastguard Worker // modification, are permitted provided that the following conditions are
8*1b3f573fSAndroid Build Coastguard Worker // met:
9*1b3f573fSAndroid Build Coastguard Worker //
10*1b3f573fSAndroid Build Coastguard Worker //     * Redistributions of source code must retain the above copyright
11*1b3f573fSAndroid Build Coastguard Worker // notice, this list of conditions and the following disclaimer.
12*1b3f573fSAndroid Build Coastguard Worker //     * Redistributions in binary form must reproduce the above
13*1b3f573fSAndroid Build Coastguard Worker // copyright notice, this list of conditions and the following disclaimer
14*1b3f573fSAndroid Build Coastguard Worker // in the documentation and/or other materials provided with the
15*1b3f573fSAndroid Build Coastguard Worker // distribution.
16*1b3f573fSAndroid Build Coastguard Worker //     * Neither the name of Google Inc. nor the names of its
17*1b3f573fSAndroid Build Coastguard Worker // contributors may be used to endorse or promote products derived from
18*1b3f573fSAndroid Build Coastguard Worker // this software without specific prior written permission.
19*1b3f573fSAndroid Build Coastguard Worker //
20*1b3f573fSAndroid Build Coastguard Worker // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21*1b3f573fSAndroid Build Coastguard Worker // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22*1b3f573fSAndroid Build Coastguard Worker // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23*1b3f573fSAndroid Build Coastguard Worker // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24*1b3f573fSAndroid Build Coastguard Worker // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25*1b3f573fSAndroid Build Coastguard Worker // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26*1b3f573fSAndroid Build Coastguard Worker // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27*1b3f573fSAndroid Build Coastguard Worker // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28*1b3f573fSAndroid Build Coastguard Worker // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29*1b3f573fSAndroid Build Coastguard Worker // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30*1b3f573fSAndroid Build Coastguard Worker // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31*1b3f573fSAndroid Build Coastguard Worker #endregion
32*1b3f573fSAndroid Build Coastguard Worker 
33*1b3f573fSAndroid Build Coastguard Worker using Google.Protobuf.Reflection;
34*1b3f573fSAndroid Build Coastguard Worker using Google.Protobuf.WellKnownTypes;
35*1b3f573fSAndroid Build Coastguard Worker using System;
36*1b3f573fSAndroid Build Coastguard Worker using System.Collections;
37*1b3f573fSAndroid Build Coastguard Worker using System.Collections.Generic;
38*1b3f573fSAndroid Build Coastguard Worker using System.Globalization;
39*1b3f573fSAndroid Build Coastguard Worker using System.IO;
40*1b3f573fSAndroid Build Coastguard Worker using System.Linq;
41*1b3f573fSAndroid Build Coastguard Worker using System.Text;
42*1b3f573fSAndroid Build Coastguard Worker using System.Text.RegularExpressions;
43*1b3f573fSAndroid Build Coastguard Worker 
44*1b3f573fSAndroid Build Coastguard Worker namespace Google.Protobuf
45*1b3f573fSAndroid Build Coastguard Worker {
46*1b3f573fSAndroid Build Coastguard Worker     /// <summary>
47*1b3f573fSAndroid Build Coastguard Worker     /// Reflection-based converter from JSON to messages.
48*1b3f573fSAndroid Build Coastguard Worker     /// </summary>
49*1b3f573fSAndroid Build Coastguard Worker     /// <remarks>
50*1b3f573fSAndroid Build Coastguard Worker     /// <para>
51*1b3f573fSAndroid Build Coastguard Worker     /// Instances of this class are thread-safe, with no mutable state.
52*1b3f573fSAndroid Build Coastguard Worker     /// </para>
53*1b3f573fSAndroid Build Coastguard Worker     /// <para>
54*1b3f573fSAndroid Build Coastguard Worker     /// This is a simple start to get JSON parsing working. As it's reflection-based,
55*1b3f573fSAndroid Build Coastguard Worker     /// it's not as quick as baking calls into generated messages - but is a simpler implementation.
56*1b3f573fSAndroid Build Coastguard Worker     /// (This code is generally not heavily optimized.)
57*1b3f573fSAndroid Build Coastguard Worker     /// </para>
58*1b3f573fSAndroid Build Coastguard Worker     /// </remarks>
59*1b3f573fSAndroid Build Coastguard Worker     public sealed class JsonParser
60*1b3f573fSAndroid Build Coastguard Worker     {
61*1b3f573fSAndroid Build Coastguard Worker         // Note: using 0-9 instead of \d to ensure no non-ASCII digits.
62*1b3f573fSAndroid Build Coastguard Worker         // This regex isn't a complete validator, but will remove *most* invalid input. We rely on parsing to do the rest.
63*1b3f573fSAndroid Build Coastguard Worker         private static readonly Regex TimestampRegex = new Regex(@"^(?<datetime>[0-9]{4}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-5][0-9])(?<subseconds>\.[0-9]{1,9})?(?<offset>(Z|[+-][0-1][0-9]:[0-5][0-9]))$", FrameworkPortability.CompiledRegexWhereAvailable);
64*1b3f573fSAndroid Build Coastguard Worker         private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", FrameworkPortability.CompiledRegexWhereAvailable);
65*1b3f573fSAndroid Build Coastguard Worker         private static readonly int[] SubsecondScalingFactors = { 0, 100000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
66*1b3f573fSAndroid Build Coastguard Worker         private static readonly char[] FieldMaskPathSeparators = new[] { ',' };
67*1b3f573fSAndroid Build Coastguard Worker         private static readonly EnumDescriptor NullValueDescriptor = StructReflection.Descriptor.EnumTypes.Single(ed => ed.ClrType == typeof(NullValue));
68*1b3f573fSAndroid Build Coastguard Worker 
69*1b3f573fSAndroid Build Coastguard Worker         private static readonly JsonParser defaultInstance = new JsonParser(Settings.Default);
70*1b3f573fSAndroid Build Coastguard Worker 
71*1b3f573fSAndroid Build Coastguard Worker         // TODO: Consider introducing a class containing parse state of the parser, tokenizer and depth. That would simplify these handlers
72*1b3f573fSAndroid Build Coastguard Worker         // and the signatures of various methods.
73*1b3f573fSAndroid Build Coastguard Worker         private static readonly Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
74*1b3f573fSAndroid Build Coastguard Worker             WellKnownTypeHandlers = new Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
75*1b3f573fSAndroid Build Coastguard Worker         {
76*1b3f573fSAndroid Build Coastguard Worker             { Timestamp.Descriptor.FullName, (parser, message, tokenizer) => MergeTimestamp(message, tokenizer.Next()) },
77*1b3f573fSAndroid Build Coastguard Worker             { Duration.Descriptor.FullName, (parser, message, tokenizer) => MergeDuration(message, tokenizer.Next()) },
78*1b3f573fSAndroid Build Coastguard Worker             { Value.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStructValue(message, tokenizer) },
79*1b3f573fSAndroid Build Coastguard Worker             { ListValue.Descriptor.FullName, (parser, message, tokenizer) =>
80*1b3f573fSAndroid Build Coastguard Worker                 parser.MergeRepeatedField(message, message.Descriptor.Fields[ListValue.ValuesFieldNumber], tokenizer) },
81*1b3f573fSAndroid Build Coastguard Worker             { Struct.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStruct(message, tokenizer) },
82*1b3f573fSAndroid Build Coastguard Worker             { Any.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeAny(message, tokenizer) },
83*1b3f573fSAndroid Build Coastguard Worker             { FieldMask.Descriptor.FullName, (parser, message, tokenizer) => MergeFieldMask(message, tokenizer.Next()) },
84*1b3f573fSAndroid Build Coastguard Worker             { Int32Value.Descriptor.FullName, MergeWrapperField },
85*1b3f573fSAndroid Build Coastguard Worker             { Int64Value.Descriptor.FullName, MergeWrapperField },
86*1b3f573fSAndroid Build Coastguard Worker             { UInt32Value.Descriptor.FullName, MergeWrapperField },
87*1b3f573fSAndroid Build Coastguard Worker             { UInt64Value.Descriptor.FullName, MergeWrapperField },
88*1b3f573fSAndroid Build Coastguard Worker             { FloatValue.Descriptor.FullName, MergeWrapperField },
89*1b3f573fSAndroid Build Coastguard Worker             { DoubleValue.Descriptor.FullName, MergeWrapperField },
90*1b3f573fSAndroid Build Coastguard Worker             { BytesValue.Descriptor.FullName, MergeWrapperField },
91*1b3f573fSAndroid Build Coastguard Worker             { StringValue.Descriptor.FullName, MergeWrapperField },
92*1b3f573fSAndroid Build Coastguard Worker             { BoolValue.Descriptor.FullName, MergeWrapperField }
93*1b3f573fSAndroid Build Coastguard Worker         };
94*1b3f573fSAndroid Build Coastguard Worker 
95*1b3f573fSAndroid Build Coastguard Worker         // Convenience method to avoid having to repeat the same code multiple times in the above
96*1b3f573fSAndroid Build Coastguard Worker         // dictionary initialization.
MergeWrapperField(JsonParser parser, IMessage message, JsonTokenizer tokenizer)97*1b3f573fSAndroid Build Coastguard Worker         private static void MergeWrapperField(JsonParser parser, IMessage message, JsonTokenizer tokenizer)
98*1b3f573fSAndroid Build Coastguard Worker         {
99*1b3f573fSAndroid Build Coastguard Worker             parser.MergeField(message, message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber], tokenizer);
100*1b3f573fSAndroid Build Coastguard Worker         }
101*1b3f573fSAndroid Build Coastguard Worker 
102*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
103*1b3f573fSAndroid Build Coastguard Worker         /// Returns a formatter using the default settings.
104*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
105*1b3f573fSAndroid Build Coastguard Worker         public static JsonParser Default { get { return defaultInstance; } }
106*1b3f573fSAndroid Build Coastguard Worker 
107*1b3f573fSAndroid Build Coastguard Worker         private readonly Settings settings;
108*1b3f573fSAndroid Build Coastguard Worker 
109*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
110*1b3f573fSAndroid Build Coastguard Worker         /// Creates a new formatted with the given settings.
111*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
112*1b3f573fSAndroid Build Coastguard Worker         /// <param name="settings">The settings.</param>
JsonParser(Settings settings)113*1b3f573fSAndroid Build Coastguard Worker         public JsonParser(Settings settings)
114*1b3f573fSAndroid Build Coastguard Worker         {
115*1b3f573fSAndroid Build Coastguard Worker             this.settings = ProtoPreconditions.CheckNotNull(settings, nameof(settings));
116*1b3f573fSAndroid Build Coastguard Worker         }
117*1b3f573fSAndroid Build Coastguard Worker 
118*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
119*1b3f573fSAndroid Build Coastguard Worker         /// Parses <paramref name="json"/> and merges the information into the given message.
120*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
121*1b3f573fSAndroid Build Coastguard Worker         /// <param name="message">The message to merge the JSON information into.</param>
122*1b3f573fSAndroid Build Coastguard Worker         /// <param name="json">The JSON to parse.</param>
Merge(IMessage message, string json)123*1b3f573fSAndroid Build Coastguard Worker         internal void Merge(IMessage message, string json)
124*1b3f573fSAndroid Build Coastguard Worker         {
125*1b3f573fSAndroid Build Coastguard Worker             Merge(message, new StringReader(json));
126*1b3f573fSAndroid Build Coastguard Worker         }
127*1b3f573fSAndroid Build Coastguard Worker 
128*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
129*1b3f573fSAndroid Build Coastguard Worker         /// Parses JSON read from <paramref name="jsonReader"/> and merges the information into the given message.
130*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
131*1b3f573fSAndroid Build Coastguard Worker         /// <param name="message">The message to merge the JSON information into.</param>
132*1b3f573fSAndroid Build Coastguard Worker         /// <param name="jsonReader">Reader providing the JSON to parse.</param>
Merge(IMessage message, TextReader jsonReader)133*1b3f573fSAndroid Build Coastguard Worker         internal void Merge(IMessage message, TextReader jsonReader)
134*1b3f573fSAndroid Build Coastguard Worker         {
135*1b3f573fSAndroid Build Coastguard Worker             var tokenizer = JsonTokenizer.FromTextReader(jsonReader);
136*1b3f573fSAndroid Build Coastguard Worker             Merge(message, tokenizer);
137*1b3f573fSAndroid Build Coastguard Worker             var lastToken = tokenizer.Next();
138*1b3f573fSAndroid Build Coastguard Worker             if (lastToken != JsonToken.EndDocument)
139*1b3f573fSAndroid Build Coastguard Worker             {
140*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected end of JSON after object");
141*1b3f573fSAndroid Build Coastguard Worker             }
142*1b3f573fSAndroid Build Coastguard Worker         }
143*1b3f573fSAndroid Build Coastguard Worker 
144*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
145*1b3f573fSAndroid Build Coastguard Worker         /// Merges the given message using data from the given tokenizer. In most cases, the next
146*1b3f573fSAndroid Build Coastguard Worker         /// token should be a "start object" token, but wrapper types and nullity can invalidate
147*1b3f573fSAndroid Build Coastguard Worker         /// that assumption. This is implemented as an LL(1) recursive descent parser over the stream
148*1b3f573fSAndroid Build Coastguard Worker         /// of tokens provided by the tokenizer. This token stream is assumed to be valid JSON, with the
149*1b3f573fSAndroid Build Coastguard Worker         /// tokenizer performing that validation - but not every token stream is valid "protobuf JSON".
150*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
Merge(IMessage message, JsonTokenizer tokenizer)151*1b3f573fSAndroid Build Coastguard Worker         private void Merge(IMessage message, JsonTokenizer tokenizer)
152*1b3f573fSAndroid Build Coastguard Worker         {
153*1b3f573fSAndroid Build Coastguard Worker             if (tokenizer.ObjectDepth > settings.RecursionLimit)
154*1b3f573fSAndroid Build Coastguard Worker             {
155*1b3f573fSAndroid Build Coastguard Worker                 throw InvalidProtocolBufferException.JsonRecursionLimitExceeded();
156*1b3f573fSAndroid Build Coastguard Worker             }
157*1b3f573fSAndroid Build Coastguard Worker             if (message.Descriptor.IsWellKnownType)
158*1b3f573fSAndroid Build Coastguard Worker             {
159*1b3f573fSAndroid Build Coastguard Worker                 Action<JsonParser, IMessage, JsonTokenizer> handler;
160*1b3f573fSAndroid Build Coastguard Worker                 if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullName, out handler))
161*1b3f573fSAndroid Build Coastguard Worker                 {
162*1b3f573fSAndroid Build Coastguard Worker                     handler(this, message, tokenizer);
163*1b3f573fSAndroid Build Coastguard Worker                     return;
164*1b3f573fSAndroid Build Coastguard Worker                 }
165*1b3f573fSAndroid Build Coastguard Worker                 // Well-known types with no special handling continue in the normal way.
166*1b3f573fSAndroid Build Coastguard Worker             }
167*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next();
168*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StartObject)
169*1b3f573fSAndroid Build Coastguard Worker             {
170*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected an object");
171*1b3f573fSAndroid Build Coastguard Worker             }
172*1b3f573fSAndroid Build Coastguard Worker             var descriptor = message.Descriptor;
173*1b3f573fSAndroid Build Coastguard Worker             var jsonFieldMap = descriptor.Fields.ByJsonName();
174*1b3f573fSAndroid Build Coastguard Worker             // All the oneof fields we've already accounted for - we can only see each of them once.
175*1b3f573fSAndroid Build Coastguard Worker             // The set is created lazily to avoid the overhead of creating a set for every message
176*1b3f573fSAndroid Build Coastguard Worker             // we parsed, when oneofs are relatively rare.
177*1b3f573fSAndroid Build Coastguard Worker             HashSet<OneofDescriptor> seenOneofs = null;
178*1b3f573fSAndroid Build Coastguard Worker             while (true)
179*1b3f573fSAndroid Build Coastguard Worker             {
180*1b3f573fSAndroid Build Coastguard Worker                 token = tokenizer.Next();
181*1b3f573fSAndroid Build Coastguard Worker                 if (token.Type == JsonToken.TokenType.EndObject)
182*1b3f573fSAndroid Build Coastguard Worker                 {
183*1b3f573fSAndroid Build Coastguard Worker                     return;
184*1b3f573fSAndroid Build Coastguard Worker                 }
185*1b3f573fSAndroid Build Coastguard Worker                 if (token.Type != JsonToken.TokenType.Name)
186*1b3f573fSAndroid Build Coastguard Worker                 {
187*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidOperationException("Unexpected token type " + token.Type);
188*1b3f573fSAndroid Build Coastguard Worker                 }
189*1b3f573fSAndroid Build Coastguard Worker                 string name = token.StringValue;
190*1b3f573fSAndroid Build Coastguard Worker                 FieldDescriptor field;
191*1b3f573fSAndroid Build Coastguard Worker                 if (jsonFieldMap.TryGetValue(name, out field))
192*1b3f573fSAndroid Build Coastguard Worker                 {
193*1b3f573fSAndroid Build Coastguard Worker                     if (field.ContainingOneof != null)
194*1b3f573fSAndroid Build Coastguard Worker                     {
195*1b3f573fSAndroid Build Coastguard Worker                         if (seenOneofs == null)
196*1b3f573fSAndroid Build Coastguard Worker                         {
197*1b3f573fSAndroid Build Coastguard Worker                             seenOneofs = new HashSet<OneofDescriptor>();
198*1b3f573fSAndroid Build Coastguard Worker                         }
199*1b3f573fSAndroid Build Coastguard Worker                         if (!seenOneofs.Add(field.ContainingOneof))
200*1b3f573fSAndroid Build Coastguard Worker                         {
201*1b3f573fSAndroid Build Coastguard Worker                             throw new InvalidProtocolBufferException($"Multiple values specified for oneof {field.ContainingOneof.Name}");
202*1b3f573fSAndroid Build Coastguard Worker                         }
203*1b3f573fSAndroid Build Coastguard Worker                     }
204*1b3f573fSAndroid Build Coastguard Worker                     MergeField(message, field, tokenizer);
205*1b3f573fSAndroid Build Coastguard Worker                 }
206*1b3f573fSAndroid Build Coastguard Worker                 else
207*1b3f573fSAndroid Build Coastguard Worker                 {
208*1b3f573fSAndroid Build Coastguard Worker                     if (settings.IgnoreUnknownFields)
209*1b3f573fSAndroid Build Coastguard Worker                     {
210*1b3f573fSAndroid Build Coastguard Worker                         tokenizer.SkipValue();
211*1b3f573fSAndroid Build Coastguard Worker                     }
212*1b3f573fSAndroid Build Coastguard Worker                     else
213*1b3f573fSAndroid Build Coastguard Worker                     {
214*1b3f573fSAndroid Build Coastguard Worker                         throw new InvalidProtocolBufferException("Unknown field: " + name);
215*1b3f573fSAndroid Build Coastguard Worker                     }
216*1b3f573fSAndroid Build Coastguard Worker                 }
217*1b3f573fSAndroid Build Coastguard Worker             }
218*1b3f573fSAndroid Build Coastguard Worker         }
219*1b3f573fSAndroid Build Coastguard Worker 
MergeField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)220*1b3f573fSAndroid Build Coastguard Worker         private void MergeField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
221*1b3f573fSAndroid Build Coastguard Worker         {
222*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next();
223*1b3f573fSAndroid Build Coastguard Worker             if (token.Type == JsonToken.TokenType.Null)
224*1b3f573fSAndroid Build Coastguard Worker             {
225*1b3f573fSAndroid Build Coastguard Worker                 // Clear the field if we see a null token, unless it's for a singular field of type
226*1b3f573fSAndroid Build Coastguard Worker                 // google.protobuf.Value or google.protobuf.NullValue.
227*1b3f573fSAndroid Build Coastguard Worker                 // Note: different from Java API, which just ignores it.
228*1b3f573fSAndroid Build Coastguard Worker                 // TODO: Bring it more in line? Discuss...
229*1b3f573fSAndroid Build Coastguard Worker                 if (field.IsMap || field.IsRepeated ||
230*1b3f573fSAndroid Build Coastguard Worker                     !(IsGoogleProtobufValueField(field) || IsGoogleProtobufNullValueField(field)))
231*1b3f573fSAndroid Build Coastguard Worker                 {
232*1b3f573fSAndroid Build Coastguard Worker                     field.Accessor.Clear(message);
233*1b3f573fSAndroid Build Coastguard Worker                     return;
234*1b3f573fSAndroid Build Coastguard Worker                 }
235*1b3f573fSAndroid Build Coastguard Worker             }
236*1b3f573fSAndroid Build Coastguard Worker             tokenizer.PushBack(token);
237*1b3f573fSAndroid Build Coastguard Worker 
238*1b3f573fSAndroid Build Coastguard Worker             if (field.IsMap)
239*1b3f573fSAndroid Build Coastguard Worker             {
240*1b3f573fSAndroid Build Coastguard Worker                 MergeMapField(message, field, tokenizer);
241*1b3f573fSAndroid Build Coastguard Worker             }
242*1b3f573fSAndroid Build Coastguard Worker             else if (field.IsRepeated)
243*1b3f573fSAndroid Build Coastguard Worker             {
244*1b3f573fSAndroid Build Coastguard Worker                 MergeRepeatedField(message, field, tokenizer);
245*1b3f573fSAndroid Build Coastguard Worker             }
246*1b3f573fSAndroid Build Coastguard Worker             else
247*1b3f573fSAndroid Build Coastguard Worker             {
248*1b3f573fSAndroid Build Coastguard Worker                 var value = ParseSingleValue(field, tokenizer);
249*1b3f573fSAndroid Build Coastguard Worker                 field.Accessor.SetValue(message, value);
250*1b3f573fSAndroid Build Coastguard Worker             }
251*1b3f573fSAndroid Build Coastguard Worker         }
252*1b3f573fSAndroid Build Coastguard Worker 
MergeRepeatedField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)253*1b3f573fSAndroid Build Coastguard Worker         private void MergeRepeatedField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
254*1b3f573fSAndroid Build Coastguard Worker         {
255*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next();
256*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StartArray)
257*1b3f573fSAndroid Build Coastguard Worker             {
258*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Repeated field value was not an array. Token type: " + token.Type);
259*1b3f573fSAndroid Build Coastguard Worker             }
260*1b3f573fSAndroid Build Coastguard Worker 
261*1b3f573fSAndroid Build Coastguard Worker             IList list = (IList) field.Accessor.GetValue(message);
262*1b3f573fSAndroid Build Coastguard Worker             while (true)
263*1b3f573fSAndroid Build Coastguard Worker             {
264*1b3f573fSAndroid Build Coastguard Worker                 token = tokenizer.Next();
265*1b3f573fSAndroid Build Coastguard Worker                 if (token.Type == JsonToken.TokenType.EndArray)
266*1b3f573fSAndroid Build Coastguard Worker                 {
267*1b3f573fSAndroid Build Coastguard Worker                     return;
268*1b3f573fSAndroid Build Coastguard Worker                 }
269*1b3f573fSAndroid Build Coastguard Worker                 tokenizer.PushBack(token);
270*1b3f573fSAndroid Build Coastguard Worker                 object value = ParseSingleValue(field, tokenizer);
271*1b3f573fSAndroid Build Coastguard Worker                 if (value == null)
272*1b3f573fSAndroid Build Coastguard Worker                 {
273*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException("Repeated field elements cannot be null");
274*1b3f573fSAndroid Build Coastguard Worker                 }
275*1b3f573fSAndroid Build Coastguard Worker                 list.Add(value);
276*1b3f573fSAndroid Build Coastguard Worker             }
277*1b3f573fSAndroid Build Coastguard Worker         }
278*1b3f573fSAndroid Build Coastguard Worker 
MergeMapField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)279*1b3f573fSAndroid Build Coastguard Worker         private void MergeMapField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
280*1b3f573fSAndroid Build Coastguard Worker         {
281*1b3f573fSAndroid Build Coastguard Worker             // Map fields are always objects, even if the values are well-known types: ParseSingleValue handles those.
282*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next();
283*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StartObject)
284*1b3f573fSAndroid Build Coastguard Worker             {
285*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected an object to populate a map");
286*1b3f573fSAndroid Build Coastguard Worker             }
287*1b3f573fSAndroid Build Coastguard Worker 
288*1b3f573fSAndroid Build Coastguard Worker             var type = field.MessageType;
289*1b3f573fSAndroid Build Coastguard Worker             var keyField = type.FindFieldByNumber(1);
290*1b3f573fSAndroid Build Coastguard Worker             var valueField = type.FindFieldByNumber(2);
291*1b3f573fSAndroid Build Coastguard Worker             if (keyField == null || valueField == null)
292*1b3f573fSAndroid Build Coastguard Worker             {
293*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Invalid map field: " + field.FullName);
294*1b3f573fSAndroid Build Coastguard Worker             }
295*1b3f573fSAndroid Build Coastguard Worker             IDictionary dictionary = (IDictionary) field.Accessor.GetValue(message);
296*1b3f573fSAndroid Build Coastguard Worker 
297*1b3f573fSAndroid Build Coastguard Worker             while (true)
298*1b3f573fSAndroid Build Coastguard Worker             {
299*1b3f573fSAndroid Build Coastguard Worker                 token = tokenizer.Next();
300*1b3f573fSAndroid Build Coastguard Worker                 if (token.Type == JsonToken.TokenType.EndObject)
301*1b3f573fSAndroid Build Coastguard Worker                 {
302*1b3f573fSAndroid Build Coastguard Worker                     return;
303*1b3f573fSAndroid Build Coastguard Worker                 }
304*1b3f573fSAndroid Build Coastguard Worker                 object key = ParseMapKey(keyField, token.StringValue);
305*1b3f573fSAndroid Build Coastguard Worker                 object value = ParseSingleValue(valueField, tokenizer);
306*1b3f573fSAndroid Build Coastguard Worker                 if (value == null)
307*1b3f573fSAndroid Build Coastguard Worker                 {
308*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException("Map values must not be null");
309*1b3f573fSAndroid Build Coastguard Worker                 }
310*1b3f573fSAndroid Build Coastguard Worker                 dictionary[key] = value;
311*1b3f573fSAndroid Build Coastguard Worker             }
312*1b3f573fSAndroid Build Coastguard Worker         }
313*1b3f573fSAndroid Build Coastguard Worker 
IsGoogleProtobufValueField(FieldDescriptor field)314*1b3f573fSAndroid Build Coastguard Worker         private static bool IsGoogleProtobufValueField(FieldDescriptor field)
315*1b3f573fSAndroid Build Coastguard Worker         {
316*1b3f573fSAndroid Build Coastguard Worker             return field.FieldType == FieldType.Message &&
317*1b3f573fSAndroid Build Coastguard Worker                 field.MessageType.FullName == Value.Descriptor.FullName;
318*1b3f573fSAndroid Build Coastguard Worker         }
319*1b3f573fSAndroid Build Coastguard Worker 
IsGoogleProtobufNullValueField(FieldDescriptor field)320*1b3f573fSAndroid Build Coastguard Worker         private static bool IsGoogleProtobufNullValueField(FieldDescriptor field)
321*1b3f573fSAndroid Build Coastguard Worker         {
322*1b3f573fSAndroid Build Coastguard Worker             return field.FieldType == FieldType.Enum &&
323*1b3f573fSAndroid Build Coastguard Worker                 field.EnumType.FullName == NullValueDescriptor.FullName;
324*1b3f573fSAndroid Build Coastguard Worker         }
325*1b3f573fSAndroid Build Coastguard Worker 
ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)326*1b3f573fSAndroid Build Coastguard Worker         private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
327*1b3f573fSAndroid Build Coastguard Worker         {
328*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next();
329*1b3f573fSAndroid Build Coastguard Worker             if (token.Type == JsonToken.TokenType.Null)
330*1b3f573fSAndroid Build Coastguard Worker             {
331*1b3f573fSAndroid Build Coastguard Worker                 // TODO: In order to support dynamic messages, we should really build this up
332*1b3f573fSAndroid Build Coastguard Worker                 // dynamically.
333*1b3f573fSAndroid Build Coastguard Worker                 if (IsGoogleProtobufValueField(field))
334*1b3f573fSAndroid Build Coastguard Worker                 {
335*1b3f573fSAndroid Build Coastguard Worker                     return Value.ForNull();
336*1b3f573fSAndroid Build Coastguard Worker                 }
337*1b3f573fSAndroid Build Coastguard Worker                 if (IsGoogleProtobufNullValueField(field))
338*1b3f573fSAndroid Build Coastguard Worker                 {
339*1b3f573fSAndroid Build Coastguard Worker                     return NullValue.NullValue;
340*1b3f573fSAndroid Build Coastguard Worker                 }
341*1b3f573fSAndroid Build Coastguard Worker                 return null;
342*1b3f573fSAndroid Build Coastguard Worker             }
343*1b3f573fSAndroid Build Coastguard Worker 
344*1b3f573fSAndroid Build Coastguard Worker             var fieldType = field.FieldType;
345*1b3f573fSAndroid Build Coastguard Worker             if (fieldType == FieldType.Message)
346*1b3f573fSAndroid Build Coastguard Worker             {
347*1b3f573fSAndroid Build Coastguard Worker                 // Parse wrapper types as their constituent types.
348*1b3f573fSAndroid Build Coastguard Worker                 // TODO: What does this mean for null?
349*1b3f573fSAndroid Build Coastguard Worker                 if (field.MessageType.IsWrapperType)
350*1b3f573fSAndroid Build Coastguard Worker                 {
351*1b3f573fSAndroid Build Coastguard Worker                     field = field.MessageType.Fields[WrappersReflection.WrapperValueFieldNumber];
352*1b3f573fSAndroid Build Coastguard Worker                     fieldType = field.FieldType;
353*1b3f573fSAndroid Build Coastguard Worker                 }
354*1b3f573fSAndroid Build Coastguard Worker                 else
355*1b3f573fSAndroid Build Coastguard Worker                 {
356*1b3f573fSAndroid Build Coastguard Worker                     // TODO: Merge the current value in message? (Public API currently doesn't make this relevant as we don't expose merging.)
357*1b3f573fSAndroid Build Coastguard Worker                     tokenizer.PushBack(token);
358*1b3f573fSAndroid Build Coastguard Worker                     IMessage subMessage = NewMessageForField(field);
359*1b3f573fSAndroid Build Coastguard Worker                     Merge(subMessage, tokenizer);
360*1b3f573fSAndroid Build Coastguard Worker                     return subMessage;
361*1b3f573fSAndroid Build Coastguard Worker                 }
362*1b3f573fSAndroid Build Coastguard Worker             }
363*1b3f573fSAndroid Build Coastguard Worker 
364*1b3f573fSAndroid Build Coastguard Worker             switch (token.Type)
365*1b3f573fSAndroid Build Coastguard Worker             {
366*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.True:
367*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.False:
368*1b3f573fSAndroid Build Coastguard Worker                     if (fieldType == FieldType.Bool)
369*1b3f573fSAndroid Build Coastguard Worker                     {
370*1b3f573fSAndroid Build Coastguard Worker                         return token.Type == JsonToken.TokenType.True;
371*1b3f573fSAndroid Build Coastguard Worker                     }
372*1b3f573fSAndroid Build Coastguard Worker                     // Fall through to "we don't support this type for this case"; could duplicate the behaviour of the default
373*1b3f573fSAndroid Build Coastguard Worker                     // case instead, but this way we'd only need to change one place.
374*1b3f573fSAndroid Build Coastguard Worker                     goto default;
375*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.StringValue:
376*1b3f573fSAndroid Build Coastguard Worker                     return ParseSingleStringValue(field, token.StringValue);
377*1b3f573fSAndroid Build Coastguard Worker                 // Note: not passing the number value itself here, as we may end up storing the string value in the token too.
378*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.Number:
379*1b3f573fSAndroid Build Coastguard Worker                     return ParseSingleNumberValue(field, token);
380*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.Null:
381*1b3f573fSAndroid Build Coastguard Worker                     throw new NotImplementedException("Haven't worked out what to do for null yet");
382*1b3f573fSAndroid Build Coastguard Worker                 default:
383*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException("Unsupported JSON token type " + token.Type + " for field type " + fieldType);
384*1b3f573fSAndroid Build Coastguard Worker             }
385*1b3f573fSAndroid Build Coastguard Worker         }
386*1b3f573fSAndroid Build Coastguard Worker 
387*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
388*1b3f573fSAndroid Build Coastguard Worker         /// Parses <paramref name="json"/> into a new message.
389*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
390*1b3f573fSAndroid Build Coastguard Worker         /// <typeparam name="T">The type of message to create.</typeparam>
391*1b3f573fSAndroid Build Coastguard Worker         /// <param name="json">The JSON to parse.</param>
392*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
393*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
394*1b3f573fSAndroid Build Coastguard Worker         public T Parse<T>(string json) where T : IMessage, new()
395*1b3f573fSAndroid Build Coastguard Worker         {
396*1b3f573fSAndroid Build Coastguard Worker             ProtoPreconditions.CheckNotNull(json, nameof(json));
397*1b3f573fSAndroid Build Coastguard Worker             return Parse<T>(new StringReader(json));
398*1b3f573fSAndroid Build Coastguard Worker         }
399*1b3f573fSAndroid Build Coastguard Worker 
400*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
401*1b3f573fSAndroid Build Coastguard Worker         /// Parses JSON read from <paramref name="jsonReader"/> into a new message.
402*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
403*1b3f573fSAndroid Build Coastguard Worker         /// <typeparam name="T">The type of message to create.</typeparam>
404*1b3f573fSAndroid Build Coastguard Worker         /// <param name="jsonReader">Reader providing the JSON to parse.</param>
405*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
406*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
407*1b3f573fSAndroid Build Coastguard Worker         public T Parse<T>(TextReader jsonReader) where T : IMessage, new()
408*1b3f573fSAndroid Build Coastguard Worker         {
409*1b3f573fSAndroid Build Coastguard Worker             ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader));
410*1b3f573fSAndroid Build Coastguard Worker             T message = new T();
411*1b3f573fSAndroid Build Coastguard Worker             Merge(message, jsonReader);
412*1b3f573fSAndroid Build Coastguard Worker             return message;
413*1b3f573fSAndroid Build Coastguard Worker         }
414*1b3f573fSAndroid Build Coastguard Worker 
415*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
416*1b3f573fSAndroid Build Coastguard Worker         /// Parses <paramref name="json"/> into a new message.
417*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
418*1b3f573fSAndroid Build Coastguard Worker         /// <param name="json">The JSON to parse.</param>
419*1b3f573fSAndroid Build Coastguard Worker         /// <param name="descriptor">Descriptor of message type to parse.</param>
420*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
421*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
Parse(string json, MessageDescriptor descriptor)422*1b3f573fSAndroid Build Coastguard Worker         public IMessage Parse(string json, MessageDescriptor descriptor)
423*1b3f573fSAndroid Build Coastguard Worker         {
424*1b3f573fSAndroid Build Coastguard Worker             ProtoPreconditions.CheckNotNull(json, nameof(json));
425*1b3f573fSAndroid Build Coastguard Worker             ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor));
426*1b3f573fSAndroid Build Coastguard Worker             return Parse(new StringReader(json), descriptor);
427*1b3f573fSAndroid Build Coastguard Worker         }
428*1b3f573fSAndroid Build Coastguard Worker 
429*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
430*1b3f573fSAndroid Build Coastguard Worker         /// Parses JSON read from <paramref name="jsonReader"/> into a new message.
431*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
432*1b3f573fSAndroid Build Coastguard Worker         /// <param name="jsonReader">Reader providing the JSON to parse.</param>
433*1b3f573fSAndroid Build Coastguard Worker         /// <param name="descriptor">Descriptor of message type to parse.</param>
434*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
435*1b3f573fSAndroid Build Coastguard Worker         /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
Parse(TextReader jsonReader, MessageDescriptor descriptor)436*1b3f573fSAndroid Build Coastguard Worker         public IMessage Parse(TextReader jsonReader, MessageDescriptor descriptor)
437*1b3f573fSAndroid Build Coastguard Worker         {
438*1b3f573fSAndroid Build Coastguard Worker             ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader));
439*1b3f573fSAndroid Build Coastguard Worker             ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor));
440*1b3f573fSAndroid Build Coastguard Worker             IMessage message = descriptor.Parser.CreateTemplate();
441*1b3f573fSAndroid Build Coastguard Worker             Merge(message, jsonReader);
442*1b3f573fSAndroid Build Coastguard Worker             return message;
443*1b3f573fSAndroid Build Coastguard Worker         }
444*1b3f573fSAndroid Build Coastguard Worker 
MergeStructValue(IMessage message, JsonTokenizer tokenizer)445*1b3f573fSAndroid Build Coastguard Worker         private void MergeStructValue(IMessage message, JsonTokenizer tokenizer)
446*1b3f573fSAndroid Build Coastguard Worker         {
447*1b3f573fSAndroid Build Coastguard Worker             var firstToken = tokenizer.Next();
448*1b3f573fSAndroid Build Coastguard Worker             var fields = message.Descriptor.Fields;
449*1b3f573fSAndroid Build Coastguard Worker             switch (firstToken.Type)
450*1b3f573fSAndroid Build Coastguard Worker             {
451*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.Null:
452*1b3f573fSAndroid Build Coastguard Worker                     fields[Value.NullValueFieldNumber].Accessor.SetValue(message, 0);
453*1b3f573fSAndroid Build Coastguard Worker                     return;
454*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.StringValue:
455*1b3f573fSAndroid Build Coastguard Worker                     fields[Value.StringValueFieldNumber].Accessor.SetValue(message, firstToken.StringValue);
456*1b3f573fSAndroid Build Coastguard Worker                     return;
457*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.Number:
458*1b3f573fSAndroid Build Coastguard Worker                     fields[Value.NumberValueFieldNumber].Accessor.SetValue(message, firstToken.NumberValue);
459*1b3f573fSAndroid Build Coastguard Worker                     return;
460*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.False:
461*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.True:
462*1b3f573fSAndroid Build Coastguard Worker                     fields[Value.BoolValueFieldNumber].Accessor.SetValue(message, firstToken.Type == JsonToken.TokenType.True);
463*1b3f573fSAndroid Build Coastguard Worker                     return;
464*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.StartObject:
465*1b3f573fSAndroid Build Coastguard Worker                     {
466*1b3f573fSAndroid Build Coastguard Worker                         var field = fields[Value.StructValueFieldNumber];
467*1b3f573fSAndroid Build Coastguard Worker                         var structMessage = NewMessageForField(field);
468*1b3f573fSAndroid Build Coastguard Worker                         tokenizer.PushBack(firstToken);
469*1b3f573fSAndroid Build Coastguard Worker                         Merge(structMessage, tokenizer);
470*1b3f573fSAndroid Build Coastguard Worker                         field.Accessor.SetValue(message, structMessage);
471*1b3f573fSAndroid Build Coastguard Worker                         return;
472*1b3f573fSAndroid Build Coastguard Worker                     }
473*1b3f573fSAndroid Build Coastguard Worker                 case JsonToken.TokenType.StartArray:
474*1b3f573fSAndroid Build Coastguard Worker                     {
475*1b3f573fSAndroid Build Coastguard Worker                         var field = fields[Value.ListValueFieldNumber];
476*1b3f573fSAndroid Build Coastguard Worker                         var list = NewMessageForField(field);
477*1b3f573fSAndroid Build Coastguard Worker                         tokenizer.PushBack(firstToken);
478*1b3f573fSAndroid Build Coastguard Worker                         Merge(list, tokenizer);
479*1b3f573fSAndroid Build Coastguard Worker                         field.Accessor.SetValue(message, list);
480*1b3f573fSAndroid Build Coastguard Worker                         return;
481*1b3f573fSAndroid Build Coastguard Worker                     }
482*1b3f573fSAndroid Build Coastguard Worker                 default:
483*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidOperationException("Unexpected token type: " + firstToken.Type);
484*1b3f573fSAndroid Build Coastguard Worker             }
485*1b3f573fSAndroid Build Coastguard Worker         }
486*1b3f573fSAndroid Build Coastguard Worker 
MergeStruct(IMessage message, JsonTokenizer tokenizer)487*1b3f573fSAndroid Build Coastguard Worker         private void MergeStruct(IMessage message, JsonTokenizer tokenizer)
488*1b3f573fSAndroid Build Coastguard Worker         {
489*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next();
490*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StartObject)
491*1b3f573fSAndroid Build Coastguard Worker             {
492*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected object value for Struct");
493*1b3f573fSAndroid Build Coastguard Worker             }
494*1b3f573fSAndroid Build Coastguard Worker             tokenizer.PushBack(token);
495*1b3f573fSAndroid Build Coastguard Worker 
496*1b3f573fSAndroid Build Coastguard Worker             var field = message.Descriptor.Fields[Struct.FieldsFieldNumber];
497*1b3f573fSAndroid Build Coastguard Worker             MergeMapField(message, field, tokenizer);
498*1b3f573fSAndroid Build Coastguard Worker         }
499*1b3f573fSAndroid Build Coastguard Worker 
MergeAny(IMessage message, JsonTokenizer tokenizer)500*1b3f573fSAndroid Build Coastguard Worker         private void MergeAny(IMessage message, JsonTokenizer tokenizer)
501*1b3f573fSAndroid Build Coastguard Worker         {
502*1b3f573fSAndroid Build Coastguard Worker             // Record the token stream until we see the @type property. At that point, we can take the value, consult
503*1b3f573fSAndroid Build Coastguard Worker             // the type registry for the relevant message, and replay the stream, omitting the @type property.
504*1b3f573fSAndroid Build Coastguard Worker             var tokens = new List<JsonToken>();
505*1b3f573fSAndroid Build Coastguard Worker 
506*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next();
507*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StartObject)
508*1b3f573fSAndroid Build Coastguard Worker             {
509*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected object value for Any");
510*1b3f573fSAndroid Build Coastguard Worker             }
511*1b3f573fSAndroid Build Coastguard Worker             int typeUrlObjectDepth = tokenizer.ObjectDepth;
512*1b3f573fSAndroid Build Coastguard Worker 
513*1b3f573fSAndroid Build Coastguard Worker             // The check for the property depth protects us from nested Any values which occur before the type URL
514*1b3f573fSAndroid Build Coastguard Worker             // for *this* Any.
515*1b3f573fSAndroid Build Coastguard Worker             while (token.Type != JsonToken.TokenType.Name ||
516*1b3f573fSAndroid Build Coastguard Worker                 token.StringValue != JsonFormatter.AnyTypeUrlField ||
517*1b3f573fSAndroid Build Coastguard Worker                 tokenizer.ObjectDepth != typeUrlObjectDepth)
518*1b3f573fSAndroid Build Coastguard Worker             {
519*1b3f573fSAndroid Build Coastguard Worker                 tokens.Add(token);
520*1b3f573fSAndroid Build Coastguard Worker                 token = tokenizer.Next();
521*1b3f573fSAndroid Build Coastguard Worker 
522*1b3f573fSAndroid Build Coastguard Worker                 if (tokenizer.ObjectDepth < typeUrlObjectDepth)
523*1b3f573fSAndroid Build Coastguard Worker                 {
524*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException("Any message with no @type");
525*1b3f573fSAndroid Build Coastguard Worker                 }
526*1b3f573fSAndroid Build Coastguard Worker             }
527*1b3f573fSAndroid Build Coastguard Worker 
528*1b3f573fSAndroid Build Coastguard Worker             // Don't add the @type property or its value to the recorded token list
529*1b3f573fSAndroid Build Coastguard Worker             token = tokenizer.Next();
530*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StringValue)
531*1b3f573fSAndroid Build Coastguard Worker             {
532*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected string value for Any.@type");
533*1b3f573fSAndroid Build Coastguard Worker             }
534*1b3f573fSAndroid Build Coastguard Worker             string typeUrl = token.StringValue;
535*1b3f573fSAndroid Build Coastguard Worker             string typeName = Any.GetTypeName(typeUrl);
536*1b3f573fSAndroid Build Coastguard Worker 
537*1b3f573fSAndroid Build Coastguard Worker             MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
538*1b3f573fSAndroid Build Coastguard Worker             if (descriptor == null)
539*1b3f573fSAndroid Build Coastguard Worker             {
540*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
541*1b3f573fSAndroid Build Coastguard Worker             }
542*1b3f573fSAndroid Build Coastguard Worker 
543*1b3f573fSAndroid Build Coastguard Worker             // Now replay the token stream we've already read and anything that remains of the object, just parsing it
544*1b3f573fSAndroid Build Coastguard Worker             // as normal. Our original tokenizer should end up at the end of the object.
545*1b3f573fSAndroid Build Coastguard Worker             var replay = JsonTokenizer.FromReplayedTokens(tokens, tokenizer);
546*1b3f573fSAndroid Build Coastguard Worker             var body = descriptor.Parser.CreateTemplate();
547*1b3f573fSAndroid Build Coastguard Worker             if (descriptor.IsWellKnownType)
548*1b3f573fSAndroid Build Coastguard Worker             {
549*1b3f573fSAndroid Build Coastguard Worker                 MergeWellKnownTypeAnyBody(body, replay);
550*1b3f573fSAndroid Build Coastguard Worker             }
551*1b3f573fSAndroid Build Coastguard Worker             else
552*1b3f573fSAndroid Build Coastguard Worker             {
553*1b3f573fSAndroid Build Coastguard Worker                 Merge(body, replay);
554*1b3f573fSAndroid Build Coastguard Worker             }
555*1b3f573fSAndroid Build Coastguard Worker             var data = body.ToByteString();
556*1b3f573fSAndroid Build Coastguard Worker 
557*1b3f573fSAndroid Build Coastguard Worker             // Now that we have the message data, we can pack it into an Any (the message received as a parameter).
558*1b3f573fSAndroid Build Coastguard Worker             message.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.SetValue(message, typeUrl);
559*1b3f573fSAndroid Build Coastguard Worker             message.Descriptor.Fields[Any.ValueFieldNumber].Accessor.SetValue(message, data);
560*1b3f573fSAndroid Build Coastguard Worker         }
561*1b3f573fSAndroid Build Coastguard Worker 
562*1b3f573fSAndroid Build Coastguard Worker         // Well-known types end up in a property called "value" in the JSON. As there's no longer a @type property
563*1b3f573fSAndroid Build Coastguard Worker         // in the given JSON token stream, we should *only* have tokens of start-object, name("value"), the value
564*1b3f573fSAndroid Build Coastguard Worker         // itself, and then end-object.
MergeWellKnownTypeAnyBody(IMessage body, JsonTokenizer tokenizer)565*1b3f573fSAndroid Build Coastguard Worker         private void MergeWellKnownTypeAnyBody(IMessage body, JsonTokenizer tokenizer)
566*1b3f573fSAndroid Build Coastguard Worker         {
567*1b3f573fSAndroid Build Coastguard Worker             var token = tokenizer.Next(); // Definitely start-object; checked in previous method
568*1b3f573fSAndroid Build Coastguard Worker             token = tokenizer.Next();
569*1b3f573fSAndroid Build Coastguard Worker             // TODO: What about an absent Int32Value, for example?
570*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.Name || token.StringValue != JsonFormatter.AnyWellKnownTypeValueField)
571*1b3f573fSAndroid Build Coastguard Worker             {
572*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Expected '{JsonFormatter.AnyWellKnownTypeValueField}' property for well-known type Any body");
573*1b3f573fSAndroid Build Coastguard Worker             }
574*1b3f573fSAndroid Build Coastguard Worker             Merge(body, tokenizer);
575*1b3f573fSAndroid Build Coastguard Worker             token = tokenizer.Next();
576*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.EndObject)
577*1b3f573fSAndroid Build Coastguard Worker             {
578*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Expected end-object token after @type/value for well-known type");
579*1b3f573fSAndroid Build Coastguard Worker             }
580*1b3f573fSAndroid Build Coastguard Worker         }
581*1b3f573fSAndroid Build Coastguard Worker 
582*1b3f573fSAndroid Build Coastguard Worker         #region Utility methods which don't depend on the state (or settings) of the parser.
ParseMapKey(FieldDescriptor field, string keyText)583*1b3f573fSAndroid Build Coastguard Worker         private static object ParseMapKey(FieldDescriptor field, string keyText)
584*1b3f573fSAndroid Build Coastguard Worker         {
585*1b3f573fSAndroid Build Coastguard Worker             switch (field.FieldType)
586*1b3f573fSAndroid Build Coastguard Worker             {
587*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Bool:
588*1b3f573fSAndroid Build Coastguard Worker                     if (keyText == "true")
589*1b3f573fSAndroid Build Coastguard Worker                     {
590*1b3f573fSAndroid Build Coastguard Worker                         return true;
591*1b3f573fSAndroid Build Coastguard Worker                     }
592*1b3f573fSAndroid Build Coastguard Worker                     if (keyText == "false")
593*1b3f573fSAndroid Build Coastguard Worker                     {
594*1b3f573fSAndroid Build Coastguard Worker                         return false;
595*1b3f573fSAndroid Build Coastguard Worker                     }
596*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException("Invalid string for bool map key: " + keyText);
597*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.String:
598*1b3f573fSAndroid Build Coastguard Worker                     return keyText;
599*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Int32:
600*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SInt32:
601*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SFixed32:
602*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(keyText, int.Parse);
603*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.UInt32:
604*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Fixed32:
605*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(keyText, uint.Parse);
606*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Int64:
607*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SInt64:
608*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SFixed64:
609*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(keyText, long.Parse);
610*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.UInt64:
611*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Fixed64:
612*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(keyText, ulong.Parse);
613*1b3f573fSAndroid Build Coastguard Worker                 default:
614*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException("Invalid field type for map: " + field.FieldType);
615*1b3f573fSAndroid Build Coastguard Worker             }
616*1b3f573fSAndroid Build Coastguard Worker         }
617*1b3f573fSAndroid Build Coastguard Worker 
618*1b3f573fSAndroid Build Coastguard Worker         private static object ParseSingleNumberValue(FieldDescriptor field, JsonToken token)
619*1b3f573fSAndroid Build Coastguard Worker         {
620*1b3f573fSAndroid Build Coastguard Worker             double value = token.NumberValue;
621*1b3f573fSAndroid Build Coastguard Worker             checked
622*1b3f573fSAndroid Build Coastguard Worker             {
623*1b3f573fSAndroid Build Coastguard Worker                 try
624*1b3f573fSAndroid Build Coastguard Worker                 {
625*1b3f573fSAndroid Build Coastguard Worker                     switch (field.FieldType)
626*1b3f573fSAndroid Build Coastguard Worker                     {
627*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.Int32:
628*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.SInt32:
629*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.SFixed32:
630*1b3f573fSAndroid Build Coastguard Worker                             CheckInteger(value);
631*1b3f573fSAndroid Build Coastguard Worker                             return (int) value;
632*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.UInt32:
633*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.Fixed32:
634*1b3f573fSAndroid Build Coastguard Worker                             CheckInteger(value);
635*1b3f573fSAndroid Build Coastguard Worker                             return (uint) value;
636*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.Int64:
637*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.SInt64:
638*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.SFixed64:
639*1b3f573fSAndroid Build Coastguard Worker                             CheckInteger(value);
640*1b3f573fSAndroid Build Coastguard Worker                             return (long) value;
641*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.UInt64:
642*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.Fixed64:
643*1b3f573fSAndroid Build Coastguard Worker                             CheckInteger(value);
644*1b3f573fSAndroid Build Coastguard Worker                             return (ulong) value;
645*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.Double:
646*1b3f573fSAndroid Build Coastguard Worker                             return value;
647*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.Float:
648*1b3f573fSAndroid Build Coastguard Worker                             if (double.IsNaN(value))
649*1b3f573fSAndroid Build Coastguard Worker                             {
650*1b3f573fSAndroid Build Coastguard Worker                                 return float.NaN;
651*1b3f573fSAndroid Build Coastguard Worker                             }
652*1b3f573fSAndroid Build Coastguard Worker                             if (value > float.MaxValue || value < float.MinValue)
653*1b3f573fSAndroid Build Coastguard Worker                             {
654*1b3f573fSAndroid Build Coastguard Worker                                 if (double.IsPositiveInfinity(value))
655*1b3f573fSAndroid Build Coastguard Worker                                 {
656*1b3f573fSAndroid Build Coastguard Worker                                     return float.PositiveInfinity;
657*1b3f573fSAndroid Build Coastguard Worker                                 }
658*1b3f573fSAndroid Build Coastguard Worker                                 if (double.IsNegativeInfinity(value))
659*1b3f573fSAndroid Build Coastguard Worker                                 {
660*1b3f573fSAndroid Build Coastguard Worker                                     return float.NegativeInfinity;
661*1b3f573fSAndroid Build Coastguard Worker                                 }
662*1b3f573fSAndroid Build Coastguard Worker                                 throw new InvalidProtocolBufferException($"Value out of range: {value}");
663*1b3f573fSAndroid Build Coastguard Worker                             }
664*1b3f573fSAndroid Build Coastguard Worker                             return (float) value;
665*1b3f573fSAndroid Build Coastguard Worker                         case FieldType.Enum:
666*1b3f573fSAndroid Build Coastguard Worker                             CheckInteger(value);
667*1b3f573fSAndroid Build Coastguard Worker                             // Just return it as an int, and let the CLR convert it.
668*1b3f573fSAndroid Build Coastguard Worker                             // Note that we deliberately don't check that it's a known value.
669*1b3f573fSAndroid Build Coastguard Worker                             return (int) value;
670*1b3f573fSAndroid Build Coastguard Worker                         default:
671*1b3f573fSAndroid Build Coastguard Worker                             throw new InvalidProtocolBufferException($"Unsupported conversion from JSON number for field type {field.FieldType}");
672*1b3f573fSAndroid Build Coastguard Worker                     }
673*1b3f573fSAndroid Build Coastguard Worker                 }
674*1b3f573fSAndroid Build Coastguard Worker                 catch (OverflowException)
675*1b3f573fSAndroid Build Coastguard Worker                 {
676*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException($"Value out of range: {value}");
677*1b3f573fSAndroid Build Coastguard Worker                 }
678*1b3f573fSAndroid Build Coastguard Worker             }
679*1b3f573fSAndroid Build Coastguard Worker         }
680*1b3f573fSAndroid Build Coastguard Worker 
681*1b3f573fSAndroid Build Coastguard Worker         private static void CheckInteger(double value)
682*1b3f573fSAndroid Build Coastguard Worker         {
683*1b3f573fSAndroid Build Coastguard Worker             if (double.IsInfinity(value) || double.IsNaN(value))
684*1b3f573fSAndroid Build Coastguard Worker             {
685*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Value not an integer: {value}");
686*1b3f573fSAndroid Build Coastguard Worker             }
687*1b3f573fSAndroid Build Coastguard Worker             if (value != Math.Floor(value))
688*1b3f573fSAndroid Build Coastguard Worker             {
689*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Value not an integer: {value}");
690*1b3f573fSAndroid Build Coastguard Worker             }
691*1b3f573fSAndroid Build Coastguard Worker         }
692*1b3f573fSAndroid Build Coastguard Worker 
693*1b3f573fSAndroid Build Coastguard Worker         private static object ParseSingleStringValue(FieldDescriptor field, string text)
694*1b3f573fSAndroid Build Coastguard Worker         {
695*1b3f573fSAndroid Build Coastguard Worker             switch (field.FieldType)
696*1b3f573fSAndroid Build Coastguard Worker             {
697*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.String:
698*1b3f573fSAndroid Build Coastguard Worker                     return text;
699*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Bytes:
700*1b3f573fSAndroid Build Coastguard Worker                     try
701*1b3f573fSAndroid Build Coastguard Worker                     {
702*1b3f573fSAndroid Build Coastguard Worker                         return ByteString.FromBase64(text);
703*1b3f573fSAndroid Build Coastguard Worker                     }
704*1b3f573fSAndroid Build Coastguard Worker                     catch (FormatException e)
705*1b3f573fSAndroid Build Coastguard Worker                     {
706*1b3f573fSAndroid Build Coastguard Worker                         throw InvalidProtocolBufferException.InvalidBase64(e);
707*1b3f573fSAndroid Build Coastguard Worker                     }
708*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Int32:
709*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SInt32:
710*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SFixed32:
711*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(text, int.Parse);
712*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.UInt32:
713*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Fixed32:
714*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(text, uint.Parse);
715*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Int64:
716*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SInt64:
717*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.SFixed64:
718*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(text, long.Parse);
719*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.UInt64:
720*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Fixed64:
721*1b3f573fSAndroid Build Coastguard Worker                     return ParseNumericString(text, ulong.Parse);
722*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Double:
723*1b3f573fSAndroid Build Coastguard Worker                     double d = ParseNumericString(text, double.Parse);
724*1b3f573fSAndroid Build Coastguard Worker                     ValidateInfinityAndNan(text, double.IsPositiveInfinity(d), double.IsNegativeInfinity(d), double.IsNaN(d));
725*1b3f573fSAndroid Build Coastguard Worker                     return d;
726*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Float:
727*1b3f573fSAndroid Build Coastguard Worker                     float f = ParseNumericString(text, float.Parse);
728*1b3f573fSAndroid Build Coastguard Worker                     ValidateInfinityAndNan(text, float.IsPositiveInfinity(f), float.IsNegativeInfinity(f), float.IsNaN(f));
729*1b3f573fSAndroid Build Coastguard Worker                     return f;
730*1b3f573fSAndroid Build Coastguard Worker                 case FieldType.Enum:
731*1b3f573fSAndroid Build Coastguard Worker                     var enumValue = field.EnumType.FindValueByName(text);
732*1b3f573fSAndroid Build Coastguard Worker                     if (enumValue == null)
733*1b3f573fSAndroid Build Coastguard Worker                     {
734*1b3f573fSAndroid Build Coastguard Worker                         throw new InvalidProtocolBufferException($"Invalid enum value: {text} for enum type: {field.EnumType.FullName}");
735*1b3f573fSAndroid Build Coastguard Worker                     }
736*1b3f573fSAndroid Build Coastguard Worker                     // Just return it as an int, and let the CLR convert it.
737*1b3f573fSAndroid Build Coastguard Worker                     return enumValue.Number;
738*1b3f573fSAndroid Build Coastguard Worker                 default:
739*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException($"Unsupported conversion from JSON string for field type {field.FieldType}");
740*1b3f573fSAndroid Build Coastguard Worker             }
741*1b3f573fSAndroid Build Coastguard Worker         }
742*1b3f573fSAndroid Build Coastguard Worker 
743*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
744*1b3f573fSAndroid Build Coastguard Worker         /// Creates a new instance of the message type for the given field.
745*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
746*1b3f573fSAndroid Build Coastguard Worker         private static IMessage NewMessageForField(FieldDescriptor field)
747*1b3f573fSAndroid Build Coastguard Worker         {
748*1b3f573fSAndroid Build Coastguard Worker             return field.MessageType.Parser.CreateTemplate();
749*1b3f573fSAndroid Build Coastguard Worker         }
750*1b3f573fSAndroid Build Coastguard Worker 
751*1b3f573fSAndroid Build Coastguard Worker         private static T ParseNumericString<T>(string text, Func<string, NumberStyles, IFormatProvider, T> parser)
752*1b3f573fSAndroid Build Coastguard Worker         {
753*1b3f573fSAndroid Build Coastguard Worker             // Can't prohibit this with NumberStyles.
754*1b3f573fSAndroid Build Coastguard Worker             if (text.StartsWith("+"))
755*1b3f573fSAndroid Build Coastguard Worker             {
756*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
757*1b3f573fSAndroid Build Coastguard Worker             }
758*1b3f573fSAndroid Build Coastguard Worker             if (text.StartsWith("0") && text.Length > 1)
759*1b3f573fSAndroid Build Coastguard Worker             {
760*1b3f573fSAndroid Build Coastguard Worker                 if (text[1] >= '0' && text[1] <= '9')
761*1b3f573fSAndroid Build Coastguard Worker                 {
762*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
763*1b3f573fSAndroid Build Coastguard Worker                 }
764*1b3f573fSAndroid Build Coastguard Worker             }
765*1b3f573fSAndroid Build Coastguard Worker             else if (text.StartsWith("-0") && text.Length > 2)
766*1b3f573fSAndroid Build Coastguard Worker             {
767*1b3f573fSAndroid Build Coastguard Worker                 if (text[2] >= '0' && text[2] <= '9')
768*1b3f573fSAndroid Build Coastguard Worker                 {
769*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
770*1b3f573fSAndroid Build Coastguard Worker                 }
771*1b3f573fSAndroid Build Coastguard Worker             }
772*1b3f573fSAndroid Build Coastguard Worker             try
773*1b3f573fSAndroid Build Coastguard Worker             {
774*1b3f573fSAndroid Build Coastguard Worker                 return parser(text, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture);
775*1b3f573fSAndroid Build Coastguard Worker             }
776*1b3f573fSAndroid Build Coastguard Worker             catch (FormatException)
777*1b3f573fSAndroid Build Coastguard Worker             {
778*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Invalid numeric value for type: {text}");
779*1b3f573fSAndroid Build Coastguard Worker             }
780*1b3f573fSAndroid Build Coastguard Worker             catch (OverflowException)
781*1b3f573fSAndroid Build Coastguard Worker             {
782*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Value out of range: {text}");
783*1b3f573fSAndroid Build Coastguard Worker             }
784*1b3f573fSAndroid Build Coastguard Worker         }
785*1b3f573fSAndroid Build Coastguard Worker 
786*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
787*1b3f573fSAndroid Build Coastguard Worker         /// Checks that any infinite/NaN values originated from the correct text.
788*1b3f573fSAndroid Build Coastguard Worker         /// This corrects the lenient whitespace handling of double.Parse/float.Parse, as well as the
789*1b3f573fSAndroid Build Coastguard Worker         /// way that Mono parses out-of-range values as infinity.
790*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
ValidateInfinityAndNan(string text, bool isPositiveInfinity, bool isNegativeInfinity, bool isNaN)791*1b3f573fSAndroid Build Coastguard Worker         private static void ValidateInfinityAndNan(string text, bool isPositiveInfinity, bool isNegativeInfinity, bool isNaN)
792*1b3f573fSAndroid Build Coastguard Worker         {
793*1b3f573fSAndroid Build Coastguard Worker             if ((isPositiveInfinity && text != "Infinity") ||
794*1b3f573fSAndroid Build Coastguard Worker                 (isNegativeInfinity && text != "-Infinity") ||
795*1b3f573fSAndroid Build Coastguard Worker                 (isNaN && text != "NaN"))
796*1b3f573fSAndroid Build Coastguard Worker             {
797*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
798*1b3f573fSAndroid Build Coastguard Worker             }
799*1b3f573fSAndroid Build Coastguard Worker         }
800*1b3f573fSAndroid Build Coastguard Worker 
MergeTimestamp(IMessage message, JsonToken token)801*1b3f573fSAndroid Build Coastguard Worker         private static void MergeTimestamp(IMessage message, JsonToken token)
802*1b3f573fSAndroid Build Coastguard Worker         {
803*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StringValue)
804*1b3f573fSAndroid Build Coastguard Worker             {
805*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected string value for Timestamp");
806*1b3f573fSAndroid Build Coastguard Worker             }
807*1b3f573fSAndroid Build Coastguard Worker             var match = TimestampRegex.Match(token.StringValue);
808*1b3f573fSAndroid Build Coastguard Worker             if (!match.Success)
809*1b3f573fSAndroid Build Coastguard Worker             {
810*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Invalid Timestamp value: {token.StringValue}");
811*1b3f573fSAndroid Build Coastguard Worker             }
812*1b3f573fSAndroid Build Coastguard Worker             var dateTime = match.Groups["datetime"].Value;
813*1b3f573fSAndroid Build Coastguard Worker             var subseconds = match.Groups["subseconds"].Value;
814*1b3f573fSAndroid Build Coastguard Worker             var offset = match.Groups["offset"].Value;
815*1b3f573fSAndroid Build Coastguard Worker 
816*1b3f573fSAndroid Build Coastguard Worker             try
817*1b3f573fSAndroid Build Coastguard Worker             {
818*1b3f573fSAndroid Build Coastguard Worker                 DateTime parsed = DateTime.ParseExact(
819*1b3f573fSAndroid Build Coastguard Worker                     dateTime,
820*1b3f573fSAndroid Build Coastguard Worker                     "yyyy-MM-dd'T'HH:mm:ss",
821*1b3f573fSAndroid Build Coastguard Worker                     CultureInfo.InvariantCulture,
822*1b3f573fSAndroid Build Coastguard Worker                     DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
823*1b3f573fSAndroid Build Coastguard Worker                 // TODO: It would be nice not to have to create all these objects... easy to optimize later though.
824*1b3f573fSAndroid Build Coastguard Worker                 Timestamp timestamp = Timestamp.FromDateTime(parsed);
825*1b3f573fSAndroid Build Coastguard Worker                 int nanosToAdd = 0;
826*1b3f573fSAndroid Build Coastguard Worker                 if (subseconds != "")
827*1b3f573fSAndroid Build Coastguard Worker                 {
828*1b3f573fSAndroid Build Coastguard Worker                     // This should always work, as we've got 1-9 digits.
829*1b3f573fSAndroid Build Coastguard Worker                     int parsedFraction = int.Parse(subseconds.Substring(1), CultureInfo.InvariantCulture);
830*1b3f573fSAndroid Build Coastguard Worker                     nanosToAdd = parsedFraction * SubsecondScalingFactors[subseconds.Length];
831*1b3f573fSAndroid Build Coastguard Worker                 }
832*1b3f573fSAndroid Build Coastguard Worker                 int secondsToAdd = 0;
833*1b3f573fSAndroid Build Coastguard Worker                 if (offset != "Z")
834*1b3f573fSAndroid Build Coastguard Worker                 {
835*1b3f573fSAndroid Build Coastguard Worker                     // This is the amount we need to *subtract* from the local time to get to UTC - hence - => +1 and vice versa.
836*1b3f573fSAndroid Build Coastguard Worker                     int sign = offset[0] == '-' ? 1 : -1;
837*1b3f573fSAndroid Build Coastguard Worker                     int hours = int.Parse(offset.Substring(1, 2), CultureInfo.InvariantCulture);
838*1b3f573fSAndroid Build Coastguard Worker                     int minutes = int.Parse(offset.Substring(4, 2));
839*1b3f573fSAndroid Build Coastguard Worker                     int totalMinutes = hours * 60 + minutes;
840*1b3f573fSAndroid Build Coastguard Worker                     if (totalMinutes > 18 * 60)
841*1b3f573fSAndroid Build Coastguard Worker                     {
842*1b3f573fSAndroid Build Coastguard Worker                         throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
843*1b3f573fSAndroid Build Coastguard Worker                     }
844*1b3f573fSAndroid Build Coastguard Worker                     if (totalMinutes == 0 && sign == 1)
845*1b3f573fSAndroid Build Coastguard Worker                     {
846*1b3f573fSAndroid Build Coastguard Worker                         // This is an offset of -00:00, which means "unknown local offset". It makes no sense for a timestamp.
847*1b3f573fSAndroid Build Coastguard Worker                         throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
848*1b3f573fSAndroid Build Coastguard Worker                     }
849*1b3f573fSAndroid Build Coastguard Worker                     // We need to *subtract* the offset from local time to get UTC.
850*1b3f573fSAndroid Build Coastguard Worker                     secondsToAdd = sign * totalMinutes * 60;
851*1b3f573fSAndroid Build Coastguard Worker                 }
852*1b3f573fSAndroid Build Coastguard Worker                 // Ensure we've got the right signs. Currently unnecessary, but easy to do.
853*1b3f573fSAndroid Build Coastguard Worker                 if (secondsToAdd < 0 && nanosToAdd > 0)
854*1b3f573fSAndroid Build Coastguard Worker                 {
855*1b3f573fSAndroid Build Coastguard Worker                     secondsToAdd++;
856*1b3f573fSAndroid Build Coastguard Worker                     nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond;
857*1b3f573fSAndroid Build Coastguard Worker                 }
858*1b3f573fSAndroid Build Coastguard Worker                 if (secondsToAdd != 0 || nanosToAdd != 0)
859*1b3f573fSAndroid Build Coastguard Worker                 {
860*1b3f573fSAndroid Build Coastguard Worker                     timestamp += new Duration { Nanos = nanosToAdd, Seconds = secondsToAdd };
861*1b3f573fSAndroid Build Coastguard Worker                     // The resulting timestamp after offset change would be out of our expected range. Currently the Timestamp message doesn't validate this
862*1b3f573fSAndroid Build Coastguard Worker                     // anywhere, but we shouldn't parse it.
863*1b3f573fSAndroid Build Coastguard Worker                     if (timestamp.Seconds < Timestamp.UnixSecondsAtBclMinValue || timestamp.Seconds > Timestamp.UnixSecondsAtBclMaxValue)
864*1b3f573fSAndroid Build Coastguard Worker                     {
865*1b3f573fSAndroid Build Coastguard Worker                         throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
866*1b3f573fSAndroid Build Coastguard Worker                     }
867*1b3f573fSAndroid Build Coastguard Worker                 }
868*1b3f573fSAndroid Build Coastguard Worker                 message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.SetValue(message, timestamp.Seconds);
869*1b3f573fSAndroid Build Coastguard Worker                 message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.SetValue(message, timestamp.Nanos);
870*1b3f573fSAndroid Build Coastguard Worker             }
871*1b3f573fSAndroid Build Coastguard Worker             catch (FormatException)
872*1b3f573fSAndroid Build Coastguard Worker             {
873*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
874*1b3f573fSAndroid Build Coastguard Worker             }
875*1b3f573fSAndroid Build Coastguard Worker         }
876*1b3f573fSAndroid Build Coastguard Worker 
MergeDuration(IMessage message, JsonToken token)877*1b3f573fSAndroid Build Coastguard Worker         private static void MergeDuration(IMessage message, JsonToken token)
878*1b3f573fSAndroid Build Coastguard Worker         {
879*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StringValue)
880*1b3f573fSAndroid Build Coastguard Worker             {
881*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected string value for Duration");
882*1b3f573fSAndroid Build Coastguard Worker             }
883*1b3f573fSAndroid Build Coastguard Worker             var match = DurationRegex.Match(token.StringValue);
884*1b3f573fSAndroid Build Coastguard Worker             if (!match.Success)
885*1b3f573fSAndroid Build Coastguard Worker             {
886*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
887*1b3f573fSAndroid Build Coastguard Worker             }
888*1b3f573fSAndroid Build Coastguard Worker             var sign = match.Groups["sign"].Value;
889*1b3f573fSAndroid Build Coastguard Worker             var secondsText = match.Groups["int"].Value;
890*1b3f573fSAndroid Build Coastguard Worker             // Prohibit leading insignficant zeroes
891*1b3f573fSAndroid Build Coastguard Worker             if (secondsText[0] == '0' && secondsText.Length > 1)
892*1b3f573fSAndroid Build Coastguard Worker             {
893*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
894*1b3f573fSAndroid Build Coastguard Worker             }
895*1b3f573fSAndroid Build Coastguard Worker             var subseconds = match.Groups["subseconds"].Value;
896*1b3f573fSAndroid Build Coastguard Worker             var multiplier = sign == "-" ? -1 : 1;
897*1b3f573fSAndroid Build Coastguard Worker 
898*1b3f573fSAndroid Build Coastguard Worker             try
899*1b3f573fSAndroid Build Coastguard Worker             {
900*1b3f573fSAndroid Build Coastguard Worker                 long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture) * multiplier;
901*1b3f573fSAndroid Build Coastguard Worker                 int nanos = 0;
902*1b3f573fSAndroid Build Coastguard Worker                 if (subseconds != "")
903*1b3f573fSAndroid Build Coastguard Worker                 {
904*1b3f573fSAndroid Build Coastguard Worker                     // This should always work, as we've got 1-9 digits.
905*1b3f573fSAndroid Build Coastguard Worker                     int parsedFraction = int.Parse(subseconds.Substring(1));
906*1b3f573fSAndroid Build Coastguard Worker                     nanos = parsedFraction * SubsecondScalingFactors[subseconds.Length] * multiplier;
907*1b3f573fSAndroid Build Coastguard Worker                 }
908*1b3f573fSAndroid Build Coastguard Worker                 if (!Duration.IsNormalized(seconds, nanos))
909*1b3f573fSAndroid Build Coastguard Worker                 {
910*1b3f573fSAndroid Build Coastguard Worker                     throw new InvalidProtocolBufferException($"Invalid Duration value: {token.StringValue}");
911*1b3f573fSAndroid Build Coastguard Worker                 }
912*1b3f573fSAndroid Build Coastguard Worker                 message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds);
913*1b3f573fSAndroid Build Coastguard Worker                 message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos);
914*1b3f573fSAndroid Build Coastguard Worker             }
915*1b3f573fSAndroid Build Coastguard Worker             catch (FormatException)
916*1b3f573fSAndroid Build Coastguard Worker             {
917*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException($"Invalid Duration value: {token.StringValue}");
918*1b3f573fSAndroid Build Coastguard Worker             }
919*1b3f573fSAndroid Build Coastguard Worker         }
920*1b3f573fSAndroid Build Coastguard Worker 
MergeFieldMask(IMessage message, JsonToken token)921*1b3f573fSAndroid Build Coastguard Worker         private static void MergeFieldMask(IMessage message, JsonToken token)
922*1b3f573fSAndroid Build Coastguard Worker         {
923*1b3f573fSAndroid Build Coastguard Worker             if (token.Type != JsonToken.TokenType.StringValue)
924*1b3f573fSAndroid Build Coastguard Worker             {
925*1b3f573fSAndroid Build Coastguard Worker                 throw new InvalidProtocolBufferException("Expected string value for FieldMask");
926*1b3f573fSAndroid Build Coastguard Worker             }
927*1b3f573fSAndroid Build Coastguard Worker             // TODO: Do we *want* to remove empty entries? Probably okay to treat "" as "no paths", but "foo,,bar"?
928*1b3f573fSAndroid Build Coastguard Worker             string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators, StringSplitOptions.RemoveEmptyEntries);
929*1b3f573fSAndroid Build Coastguard Worker             IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(message);
930*1b3f573fSAndroid Build Coastguard Worker             foreach (var path in jsonPaths)
931*1b3f573fSAndroid Build Coastguard Worker             {
932*1b3f573fSAndroid Build Coastguard Worker                 messagePaths.Add(ToSnakeCase(path));
933*1b3f573fSAndroid Build Coastguard Worker             }
934*1b3f573fSAndroid Build Coastguard Worker         }
935*1b3f573fSAndroid Build Coastguard Worker 
936*1b3f573fSAndroid Build Coastguard Worker         // Ported from src/google/protobuf/util/internal/utility.cc
ToSnakeCase(string text)937*1b3f573fSAndroid Build Coastguard Worker         private static string ToSnakeCase(string text)
938*1b3f573fSAndroid Build Coastguard Worker         {
939*1b3f573fSAndroid Build Coastguard Worker             var builder = new StringBuilder(text.Length * 2);
940*1b3f573fSAndroid Build Coastguard Worker             // Note: this is probably unnecessary now, but currently retained to be as close as possible to the
941*1b3f573fSAndroid Build Coastguard Worker             // C++, whilst still throwing an exception on underscores.
942*1b3f573fSAndroid Build Coastguard Worker             bool wasNotUnderscore = false;  // Initialize to false for case 1 (below)
943*1b3f573fSAndroid Build Coastguard Worker             bool wasNotCap = false;
944*1b3f573fSAndroid Build Coastguard Worker 
945*1b3f573fSAndroid Build Coastguard Worker             for (int i = 0; i < text.Length; i++)
946*1b3f573fSAndroid Build Coastguard Worker             {
947*1b3f573fSAndroid Build Coastguard Worker                 char c = text[i];
948*1b3f573fSAndroid Build Coastguard Worker                 if (c >= 'A' && c <= 'Z') // ascii_isupper
949*1b3f573fSAndroid Build Coastguard Worker                 {
950*1b3f573fSAndroid Build Coastguard Worker                     // Consider when the current character B is capitalized:
951*1b3f573fSAndroid Build Coastguard Worker                     // 1) At beginning of input:   "B..." => "b..."
952*1b3f573fSAndroid Build Coastguard Worker                     //    (e.g. "Biscuit" => "biscuit")
953*1b3f573fSAndroid Build Coastguard Worker                     // 2) Following a lowercase:   "...aB..." => "...a_b..."
954*1b3f573fSAndroid Build Coastguard Worker                     //    (e.g. "gBike" => "g_bike")
955*1b3f573fSAndroid Build Coastguard Worker                     // 3) At the end of input:     "...AB" => "...ab"
956*1b3f573fSAndroid Build Coastguard Worker                     //    (e.g. "GoogleLAB" => "google_lab")
957*1b3f573fSAndroid Build Coastguard Worker                     // 4) Followed by a lowercase: "...ABc..." => "...a_bc..."
958*1b3f573fSAndroid Build Coastguard Worker                     //    (e.g. "GBike" => "g_bike")
959*1b3f573fSAndroid Build Coastguard Worker                     if (wasNotUnderscore &&               //            case 1 out
960*1b3f573fSAndroid Build Coastguard Worker                         (wasNotCap ||                     // case 2 in, case 3 out
961*1b3f573fSAndroid Build Coastguard Worker                          (i + 1 < text.Length &&         //            case 3 out
962*1b3f573fSAndroid Build Coastguard Worker                           (text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii_islower(text[i + 1])
963*1b3f573fSAndroid Build Coastguard Worker                     {  // case 4 in
964*1b3f573fSAndroid Build Coastguard Worker                        // We add an underscore for case 2 and case 4.
965*1b3f573fSAndroid Build Coastguard Worker                         builder.Append('_');
966*1b3f573fSAndroid Build Coastguard Worker                     }
967*1b3f573fSAndroid Build Coastguard Worker                     // ascii_tolower, but we already know that c *is* an upper case ASCII character...
968*1b3f573fSAndroid Build Coastguard Worker                     builder.Append((char) (c + 'a' - 'A'));
969*1b3f573fSAndroid Build Coastguard Worker                     wasNotUnderscore = true;
970*1b3f573fSAndroid Build Coastguard Worker                     wasNotCap = false;
971*1b3f573fSAndroid Build Coastguard Worker                 }
972*1b3f573fSAndroid Build Coastguard Worker                 else
973*1b3f573fSAndroid Build Coastguard Worker                 {
974*1b3f573fSAndroid Build Coastguard Worker                     builder.Append(c);
975*1b3f573fSAndroid Build Coastguard Worker                     if (c == '_')
976*1b3f573fSAndroid Build Coastguard Worker                     {
977*1b3f573fSAndroid Build Coastguard Worker                         throw new InvalidProtocolBufferException($"Invalid field mask: {text}");
978*1b3f573fSAndroid Build Coastguard Worker                     }
979*1b3f573fSAndroid Build Coastguard Worker                     wasNotUnderscore = true;
980*1b3f573fSAndroid Build Coastguard Worker                     wasNotCap = true;
981*1b3f573fSAndroid Build Coastguard Worker                 }
982*1b3f573fSAndroid Build Coastguard Worker             }
983*1b3f573fSAndroid Build Coastguard Worker             return builder.ToString();
984*1b3f573fSAndroid Build Coastguard Worker         }
985*1b3f573fSAndroid Build Coastguard Worker         #endregion
986*1b3f573fSAndroid Build Coastguard Worker 
987*1b3f573fSAndroid Build Coastguard Worker         /// <summary>
988*1b3f573fSAndroid Build Coastguard Worker         /// Settings controlling JSON parsing.
989*1b3f573fSAndroid Build Coastguard Worker         /// </summary>
990*1b3f573fSAndroid Build Coastguard Worker         public sealed class Settings
991*1b3f573fSAndroid Build Coastguard Worker         {
992*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
993*1b3f573fSAndroid Build Coastguard Worker             /// Default settings, as used by <see cref="JsonParser.Default"/>. This has the same default
994*1b3f573fSAndroid Build Coastguard Worker             /// recursion limit as <see cref="CodedInputStream"/>, and an empty type registry.
995*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
996*1b3f573fSAndroid Build Coastguard Worker             public static Settings Default { get; }
997*1b3f573fSAndroid Build Coastguard Worker 
998*1b3f573fSAndroid Build Coastguard Worker             // Workaround for the Mono compiler complaining about XML comments not being on
999*1b3f573fSAndroid Build Coastguard Worker             // valid language elements.
Settings()1000*1b3f573fSAndroid Build Coastguard Worker             static Settings()
1001*1b3f573fSAndroid Build Coastguard Worker             {
1002*1b3f573fSAndroid Build Coastguard Worker                 Default = new Settings(CodedInputStream.DefaultRecursionLimit);
1003*1b3f573fSAndroid Build Coastguard Worker             }
1004*1b3f573fSAndroid Build Coastguard Worker 
1005*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1006*1b3f573fSAndroid Build Coastguard Worker             /// The maximum depth of messages to parse. Note that this limit only applies to parsing
1007*1b3f573fSAndroid Build Coastguard Worker             /// messages, not collections - so a message within a collection within a message only counts as
1008*1b3f573fSAndroid Build Coastguard Worker             /// depth 2, not 3.
1009*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1010*1b3f573fSAndroid Build Coastguard Worker             public int RecursionLimit { get; }
1011*1b3f573fSAndroid Build Coastguard Worker 
1012*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1013*1b3f573fSAndroid Build Coastguard Worker             /// The type registry used to parse <see cref="Any"/> messages.
1014*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1015*1b3f573fSAndroid Build Coastguard Worker             public TypeRegistry TypeRegistry { get; }
1016*1b3f573fSAndroid Build Coastguard Worker 
1017*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1018*1b3f573fSAndroid Build Coastguard Worker             /// Whether the parser should ignore unknown fields (<c>true</c>) or throw an exception when
1019*1b3f573fSAndroid Build Coastguard Worker             /// they are encountered (<c>false</c>).
1020*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1021*1b3f573fSAndroid Build Coastguard Worker             public bool IgnoreUnknownFields { get; }
1022*1b3f573fSAndroid Build Coastguard Worker 
Settings(int recursionLimit, TypeRegistry typeRegistry, bool ignoreUnknownFields)1023*1b3f573fSAndroid Build Coastguard Worker             private Settings(int recursionLimit, TypeRegistry typeRegistry, bool ignoreUnknownFields)
1024*1b3f573fSAndroid Build Coastguard Worker             {
1025*1b3f573fSAndroid Build Coastguard Worker                 RecursionLimit = recursionLimit;
1026*1b3f573fSAndroid Build Coastguard Worker                 TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry));
1027*1b3f573fSAndroid Build Coastguard Worker                 IgnoreUnknownFields = ignoreUnknownFields;
1028*1b3f573fSAndroid Build Coastguard Worker             }
1029*1b3f573fSAndroid Build Coastguard Worker 
1030*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1031*1b3f573fSAndroid Build Coastguard Worker             /// Creates a new <see cref="Settings"/> object with the specified recursion limit.
1032*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1033*1b3f573fSAndroid Build Coastguard Worker             /// <param name="recursionLimit">The maximum depth of messages to parse</param>
Settings(int recursionLimit)1034*1b3f573fSAndroid Build Coastguard Worker             public Settings(int recursionLimit) : this(recursionLimit, TypeRegistry.Empty)
1035*1b3f573fSAndroid Build Coastguard Worker             {
1036*1b3f573fSAndroid Build Coastguard Worker             }
1037*1b3f573fSAndroid Build Coastguard Worker 
1038*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1039*1b3f573fSAndroid Build Coastguard Worker             /// Creates a new <see cref="Settings"/> object with the specified recursion limit and type registry.
1040*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1041*1b3f573fSAndroid Build Coastguard Worker             /// <param name="recursionLimit">The maximum depth of messages to parse</param>
1042*1b3f573fSAndroid Build Coastguard Worker             /// <param name="typeRegistry">The type registry used to parse <see cref="Any"/> messages</param>
Settings(int recursionLimit, TypeRegistry typeRegistry)1043*1b3f573fSAndroid Build Coastguard Worker             public Settings(int recursionLimit, TypeRegistry typeRegistry) : this(recursionLimit, typeRegistry, false)
1044*1b3f573fSAndroid Build Coastguard Worker             {
1045*1b3f573fSAndroid Build Coastguard Worker             }
1046*1b3f573fSAndroid Build Coastguard Worker 
1047*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1048*1b3f573fSAndroid Build Coastguard Worker             /// Creates a new <see cref="Settings"/> object set to either ignore unknown fields, or throw an exception
1049*1b3f573fSAndroid Build Coastguard Worker             /// when unknown fields are encountered.
1050*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1051*1b3f573fSAndroid Build Coastguard Worker             /// <param name="ignoreUnknownFields"><c>true</c> if unknown fields should be ignored when parsing; <c>false</c> to throw an exception.</param>
1052*1b3f573fSAndroid Build Coastguard Worker             public Settings WithIgnoreUnknownFields(bool ignoreUnknownFields) =>
1053*1b3f573fSAndroid Build Coastguard Worker                 new Settings(RecursionLimit, TypeRegistry, ignoreUnknownFields);
1054*1b3f573fSAndroid Build Coastguard Worker 
1055*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1056*1b3f573fSAndroid Build Coastguard Worker             /// Creates a new <see cref="Settings"/> object based on this one, but with the specified recursion limit.
1057*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1058*1b3f573fSAndroid Build Coastguard Worker             /// <param name="recursionLimit">The new recursion limit.</param>
WithRecursionLimit(int recursionLimit)1059*1b3f573fSAndroid Build Coastguard Worker             public Settings WithRecursionLimit(int recursionLimit) =>
1060*1b3f573fSAndroid Build Coastguard Worker                 new Settings(recursionLimit, TypeRegistry, IgnoreUnknownFields);
1061*1b3f573fSAndroid Build Coastguard Worker 
1062*1b3f573fSAndroid Build Coastguard Worker             /// <summary>
1063*1b3f573fSAndroid Build Coastguard Worker             /// Creates a new <see cref="Settings"/> object based on this one, but with the specified type registry.
1064*1b3f573fSAndroid Build Coastguard Worker             /// </summary>
1065*1b3f573fSAndroid Build Coastguard Worker             /// <param name="typeRegistry">The new type registry. Must not be null.</param>
1066*1b3f573fSAndroid Build Coastguard Worker             public Settings WithTypeRegistry(TypeRegistry typeRegistry) =>
1067*1b3f573fSAndroid Build Coastguard Worker                 new Settings(
1068*1b3f573fSAndroid Build Coastguard Worker                     RecursionLimit,
1069*1b3f573fSAndroid Build Coastguard Worker                     ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry)),
1070*1b3f573fSAndroid Build Coastguard Worker                     IgnoreUnknownFields);
1071*1b3f573fSAndroid Build Coastguard Worker         }
1072*1b3f573fSAndroid Build Coastguard Worker     }
1073*1b3f573fSAndroid Build Coastguard Worker }
1074