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