1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using Google.Protobuf.Compatibility; 34 using System; 35 36 namespace Google.Protobuf.Reflection 37 { 38 /// <summary> 39 /// Descriptor for a field or extension within a message in a .proto file. 40 /// </summary> 41 public sealed class FieldDescriptor : DescriptorBase, IComparable<FieldDescriptor> 42 { 43 private EnumDescriptor enumType; 44 private MessageDescriptor messageType; 45 private FieldType fieldType; 46 private readonly string propertyName; // Annoyingly, needed in Crosslink. 47 private IFieldAccessor accessor; 48 49 /// <summary> 50 /// Get the field's containing message type. 51 /// </summary> 52 public MessageDescriptor ContainingType { get; } 53 54 /// <summary> 55 /// Returns the oneof containing this field, or <c>null</c> if it is not part of a oneof. 56 /// </summary> 57 public OneofDescriptor ContainingOneof { get; } 58 59 /// <summary> 60 /// The effective JSON name for this field. This is usually the lower-camel-cased form of the field name, 61 /// but can be overridden using the <c>json_name</c> option in the .proto file. 62 /// </summary> 63 public string JsonName { get; } 64 65 internal FieldDescriptorProto Proto { get; } 66 FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, string propertyName)67 internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file, 68 MessageDescriptor parent, int index, string propertyName) 69 : base(file, file.ComputeFullName(parent, proto.Name), index) 70 { 71 Proto = proto; 72 if (proto.Type != 0) 73 { 74 fieldType = GetFieldTypeFromProtoType(proto.Type); 75 } 76 77 if (FieldNumber <= 0) 78 { 79 throw new DescriptorValidationException(this, "Field numbers must be positive integers."); 80 } 81 ContainingType = parent; 82 // OneofIndex "defaults" to -1 due to a hack in FieldDescriptor.OnConstruction. 83 if (proto.OneofIndex != -1) 84 { 85 if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count) 86 { 87 throw new DescriptorValidationException(this, 88 $"FieldDescriptorProto.oneof_index is out of range for type {parent.Name}"); 89 } 90 ContainingOneof = parent.Oneofs[proto.OneofIndex]; 91 } 92 93 file.DescriptorPool.AddSymbol(this); 94 // We can't create the accessor until we've cross-linked, unfortunately, as we 95 // may not know whether the type of the field is a map or not. Remember the property name 96 // for later. 97 // We could trust the generated code and check whether the type of the property is 98 // a MapField, but that feels a tad nasty. 99 this.propertyName = propertyName; 100 JsonName = Proto.JsonName == "" ? JsonFormatter.ToCamelCase(Proto.Name) : Proto.JsonName; 101 } 102 103 104 /// <summary> 105 /// The brief name of the descriptor's target. 106 /// </summary> 107 public override string Name => Proto.Name; 108 109 /// <summary> 110 /// Returns the accessor for this field. 111 /// </summary> 112 /// <remarks> 113 /// <para> 114 /// While a <see cref="FieldDescriptor"/> describes the field, it does not provide 115 /// any way of obtaining or changing the value of the field within a specific message; 116 /// that is the responsibility of the accessor. 117 /// </para> 118 /// <para> 119 /// The value returned by this property will be non-null for all regular fields. However, 120 /// if a message containing a map field is introspected, the list of nested messages will include 121 /// an auto-generated nested key/value pair message for the field. This is not represented in any 122 /// generated type, and the value of the map field itself is represented by a dictionary in the 123 /// reflection API. There are never instances of those "hidden" messages, so no accessor is provided 124 /// and this property will return null. 125 /// </para> 126 /// </remarks> 127 public IFieldAccessor Accessor => accessor; 128 129 /// <summary> 130 /// Maps a field type as included in the .proto file to a FieldType. 131 /// </summary> GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)132 private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type) 133 { 134 switch (type) 135 { 136 case FieldDescriptorProto.Types.Type.Double: 137 return FieldType.Double; 138 case FieldDescriptorProto.Types.Type.Float: 139 return FieldType.Float; 140 case FieldDescriptorProto.Types.Type.Int64: 141 return FieldType.Int64; 142 case FieldDescriptorProto.Types.Type.Uint64: 143 return FieldType.UInt64; 144 case FieldDescriptorProto.Types.Type.Int32: 145 return FieldType.Int32; 146 case FieldDescriptorProto.Types.Type.Fixed64: 147 return FieldType.Fixed64; 148 case FieldDescriptorProto.Types.Type.Fixed32: 149 return FieldType.Fixed32; 150 case FieldDescriptorProto.Types.Type.Bool: 151 return FieldType.Bool; 152 case FieldDescriptorProto.Types.Type.String: 153 return FieldType.String; 154 case FieldDescriptorProto.Types.Type.Group: 155 return FieldType.Group; 156 case FieldDescriptorProto.Types.Type.Message: 157 return FieldType.Message; 158 case FieldDescriptorProto.Types.Type.Bytes: 159 return FieldType.Bytes; 160 case FieldDescriptorProto.Types.Type.Uint32: 161 return FieldType.UInt32; 162 case FieldDescriptorProto.Types.Type.Enum: 163 return FieldType.Enum; 164 case FieldDescriptorProto.Types.Type.Sfixed32: 165 return FieldType.SFixed32; 166 case FieldDescriptorProto.Types.Type.Sfixed64: 167 return FieldType.SFixed64; 168 case FieldDescriptorProto.Types.Type.Sint32: 169 return FieldType.SInt32; 170 case FieldDescriptorProto.Types.Type.Sint64: 171 return FieldType.SInt64; 172 default: 173 throw new ArgumentException("Invalid type specified"); 174 } 175 } 176 177 /// <summary> 178 /// Returns <c>true</c> if this field is a repeated field; <c>false</c> otherwise. 179 /// </summary> 180 public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated; 181 182 /// <summary> 183 /// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise. 184 /// </summary> 185 public bool IsMap => fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry; 186 187 /// <summary> 188 /// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise. 189 /// </summary> 190 public bool IsPacked => 191 // Note the || rather than && here - we're effectively defaulting to packed, because that *is* 192 // the default in proto3, which is all we support. We may give the wrong result for the protos 193 // within descriptor.proto, but that's okay, as they're never exposed and we don't use IsPacked 194 // within the runtime. 195 Proto.Options == null || Proto.Options.Packed; 196 197 /// <summary> 198 /// Returns the type of the field. 199 /// </summary> 200 public FieldType FieldType => fieldType; 201 202 /// <summary> 203 /// Returns the field number declared in the proto file. 204 /// </summary> 205 public int FieldNumber => Proto.Number; 206 207 /// <summary> 208 /// Compares this descriptor with another one, ordering in "canonical" order 209 /// which simply means ascending order by field number. <paramref name="other"/> 210 /// must be a field of the same type, i.e. the <see cref="ContainingType"/> of 211 /// both fields must be the same. 212 /// </summary> CompareTo(FieldDescriptor other)213 public int CompareTo(FieldDescriptor other) 214 { 215 if (other.ContainingType != ContainingType) 216 { 217 throw new ArgumentException("FieldDescriptors can only be compared to other FieldDescriptors " + 218 "for fields of the same message type."); 219 } 220 return FieldNumber - other.FieldNumber; 221 } 222 223 /// <summary> 224 /// For enum fields, returns the field's type. 225 /// </summary> 226 public EnumDescriptor EnumType 227 { 228 get 229 { 230 if (fieldType != FieldType.Enum) 231 { 232 throw new InvalidOperationException("EnumType is only valid for enum fields."); 233 } 234 return enumType; 235 } 236 } 237 238 /// <summary> 239 /// For embedded message and group fields, returns the field's type. 240 /// </summary> 241 public MessageDescriptor MessageType 242 { 243 get 244 { 245 if (fieldType != FieldType.Message) 246 { 247 throw new InvalidOperationException("MessageType is only valid for message fields."); 248 } 249 return messageType; 250 } 251 } 252 253 /// <summary> 254 /// Look up and cross-link all field types etc. 255 /// </summary> CrossLink()256 internal void CrossLink() 257 { 258 if (Proto.TypeName != "") 259 { 260 IDescriptor typeDescriptor = 261 File.DescriptorPool.LookupSymbol(Proto.TypeName, this); 262 263 if (Proto.Type != 0) 264 { 265 // Choose field type based on symbol. 266 if (typeDescriptor is MessageDescriptor) 267 { 268 fieldType = FieldType.Message; 269 } 270 else if (typeDescriptor is EnumDescriptor) 271 { 272 fieldType = FieldType.Enum; 273 } 274 else 275 { 276 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type."); 277 } 278 } 279 280 if (fieldType == FieldType.Message) 281 { 282 if (!(typeDescriptor is MessageDescriptor)) 283 { 284 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type."); 285 } 286 messageType = (MessageDescriptor) typeDescriptor; 287 288 if (Proto.DefaultValue != "") 289 { 290 throw new DescriptorValidationException(this, "Messages can't have default values."); 291 } 292 } 293 else if (fieldType == FieldType.Enum) 294 { 295 if (!(typeDescriptor is EnumDescriptor)) 296 { 297 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type."); 298 } 299 enumType = (EnumDescriptor) typeDescriptor; 300 } 301 else 302 { 303 throw new DescriptorValidationException(this, "Field with primitive type has type_name."); 304 } 305 } 306 else 307 { 308 if (fieldType == FieldType.Message || fieldType == FieldType.Enum) 309 { 310 throw new DescriptorValidationException(this, "Field with message or enum type missing type_name."); 311 } 312 } 313 314 // Note: no attempt to perform any default value parsing 315 316 File.DescriptorPool.AddFieldByNumber(this); 317 318 if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat) 319 { 320 throw new DescriptorValidationException(this, "MessageSet format is not supported."); 321 } 322 accessor = CreateAccessor(); 323 } 324 CreateAccessor()325 private IFieldAccessor CreateAccessor() 326 { 327 // If we're given no property name, that's because we really don't want an accessor. 328 // (At the moment, that means it's a map entry message...) 329 if (propertyName == null) 330 { 331 return null; 332 } 333 var property = ContainingType.ClrType.GetProperty(propertyName); 334 if (property == null) 335 { 336 throw new DescriptorValidationException(this, $"Property {propertyName} not found in {ContainingType.ClrType}"); 337 } 338 return IsMap ? new MapFieldAccessor(property, this) 339 : IsRepeated ? new RepeatedFieldAccessor(property, this) 340 : (IFieldAccessor) new SingleFieldAccessor(property, this); 341 } 342 } 343 }