blob: 2a3d5c7a2dbabb3a9bdc7dfb1831fce3147c0d26 [file] [log] [blame]
Brian Silverman9c614bc2016-02-15 20:20:02 -05001#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
Brian Silverman9c614bc2016-02-15 20:20:02 -050033using Google.Protobuf.Compatibility;
Austin Schuh40c16522018-10-28 20:27:54 -070034using System;
Brian Silverman9c614bc2016-02-15 20:20:02 -050035
36namespace 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 {
Brian Silverman9c614bc2016-02-15 20:20:02 -050043 private EnumDescriptor enumType;
44 private MessageDescriptor messageType;
Brian Silverman9c614bc2016-02-15 20:20:02 -050045 private FieldType fieldType;
46 private readonly string propertyName; // Annoyingly, needed in Crosslink.
47 private IFieldAccessor accessor;
48
Austin Schuh40c16522018-10-28 20:27:54 -070049 /// <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
Brian Silverman9c614bc2016-02-15 20:20:02 -050067 internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file,
68 MessageDescriptor parent, int index, string propertyName)
69 : base(file, file.ComputeFullName(parent, proto.Name), index)
70 {
Austin Schuh40c16522018-10-28 20:27:54 -070071 Proto = proto;
Brian Silverman9c614bc2016-02-15 20:20:02 -050072 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 }
Austin Schuh40c16522018-10-28 20:27:54 -070081 ContainingType = parent;
Brian Silverman9c614bc2016-02-15 20:20:02 -050082 // 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 }
Austin Schuh40c16522018-10-28 20:27:54 -070090 ContainingOneof = parent.Oneofs[proto.OneofIndex];
Brian Silverman9c614bc2016-02-15 20:20:02 -050091 }
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;
Austin Schuh40c16522018-10-28 20:27:54 -0700100 JsonName = Proto.JsonName == "" ? JsonFormatter.ToJsonName(Proto.Name) : Proto.JsonName;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500101 }
Austin Schuh40c16522018-10-28 20:27:54 -0700102
Brian Silverman9c614bc2016-02-15 20:20:02 -0500103
104 /// <summary>
105 /// The brief name of the descriptor's target.
106 /// </summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700107 public override string Name => Proto.Name;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500108
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>
Austin Schuh40c16522018-10-28 20:27:54 -0700127 public IFieldAccessor Accessor => accessor;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500128
129 /// <summary>
130 /// Maps a field type as included in the .proto file to a FieldType.
131 /// </summary>
132 private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)
133 {
134 switch (type)
135 {
Austin Schuh40c16522018-10-28 20:27:54 -0700136 case FieldDescriptorProto.Types.Type.Double:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500137 return FieldType.Double;
Austin Schuh40c16522018-10-28 20:27:54 -0700138 case FieldDescriptorProto.Types.Type.Float:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500139 return FieldType.Float;
Austin Schuh40c16522018-10-28 20:27:54 -0700140 case FieldDescriptorProto.Types.Type.Int64:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500141 return FieldType.Int64;
Austin Schuh40c16522018-10-28 20:27:54 -0700142 case FieldDescriptorProto.Types.Type.Uint64:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500143 return FieldType.UInt64;
Austin Schuh40c16522018-10-28 20:27:54 -0700144 case FieldDescriptorProto.Types.Type.Int32:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500145 return FieldType.Int32;
Austin Schuh40c16522018-10-28 20:27:54 -0700146 case FieldDescriptorProto.Types.Type.Fixed64:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500147 return FieldType.Fixed64;
Austin Schuh40c16522018-10-28 20:27:54 -0700148 case FieldDescriptorProto.Types.Type.Fixed32:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500149 return FieldType.Fixed32;
Austin Schuh40c16522018-10-28 20:27:54 -0700150 case FieldDescriptorProto.Types.Type.Bool:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500151 return FieldType.Bool;
Austin Schuh40c16522018-10-28 20:27:54 -0700152 case FieldDescriptorProto.Types.Type.String:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500153 return FieldType.String;
Austin Schuh40c16522018-10-28 20:27:54 -0700154 case FieldDescriptorProto.Types.Type.Group:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500155 return FieldType.Group;
Austin Schuh40c16522018-10-28 20:27:54 -0700156 case FieldDescriptorProto.Types.Type.Message:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500157 return FieldType.Message;
Austin Schuh40c16522018-10-28 20:27:54 -0700158 case FieldDescriptorProto.Types.Type.Bytes:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500159 return FieldType.Bytes;
Austin Schuh40c16522018-10-28 20:27:54 -0700160 case FieldDescriptorProto.Types.Type.Uint32:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500161 return FieldType.UInt32;
Austin Schuh40c16522018-10-28 20:27:54 -0700162 case FieldDescriptorProto.Types.Type.Enum:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500163 return FieldType.Enum;
Austin Schuh40c16522018-10-28 20:27:54 -0700164 case FieldDescriptorProto.Types.Type.Sfixed32:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500165 return FieldType.SFixed32;
Austin Schuh40c16522018-10-28 20:27:54 -0700166 case FieldDescriptorProto.Types.Type.Sfixed64:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500167 return FieldType.SFixed64;
Austin Schuh40c16522018-10-28 20:27:54 -0700168 case FieldDescriptorProto.Types.Type.Sint32:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500169 return FieldType.SInt32;
Austin Schuh40c16522018-10-28 20:27:54 -0700170 case FieldDescriptorProto.Types.Type.Sint64:
Brian Silverman9c614bc2016-02-15 20:20:02 -0500171 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>
Austin Schuh40c16522018-10-28 20:27:54 -0700180 public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500181
182 /// <summary>
183 /// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise.
184 /// </summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700185 public bool IsMap => fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500186
187 /// <summary>
188 /// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise.
189 /// </summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700190 public bool IsPacked =>
Brian Silverman9c614bc2016-02-15 20:20:02 -0500191 // 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.
Austin Schuh40c16522018-10-28 20:27:54 -0700195 Proto.Options == null || Proto.Options.Packed;
196
Brian Silverman9c614bc2016-02-15 20:20:02 -0500197 /// <summary>
198 /// Returns the type of the field.
199 /// </summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700200 public FieldType FieldType => fieldType;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500201
202 /// <summary>
203 /// Returns the field number declared in the proto file.
204 /// </summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700205 public int FieldNumber => Proto.Number;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500206
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>
213 public int CompareTo(FieldDescriptor other)
214 {
Austin Schuh40c16522018-10-28 20:27:54 -0700215 if (other.ContainingType != ContainingType)
Brian Silverman9c614bc2016-02-15 20:20:02 -0500216 {
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>
Austin Schuh40c16522018-10-28 20:27:54 -0700254 /// The (possibly empty) set of custom options for this field.
255 /// </summary>
256 public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
257
258 /// <summary>
Brian Silverman9c614bc2016-02-15 20:20:02 -0500259 /// Look up and cross-link all field types etc.
260 /// </summary>
261 internal void CrossLink()
262 {
263 if (Proto.TypeName != "")
264 {
265 IDescriptor typeDescriptor =
266 File.DescriptorPool.LookupSymbol(Proto.TypeName, this);
267
268 if (Proto.Type != 0)
269 {
270 // Choose field type based on symbol.
271 if (typeDescriptor is MessageDescriptor)
272 {
273 fieldType = FieldType.Message;
274 }
275 else if (typeDescriptor is EnumDescriptor)
276 {
277 fieldType = FieldType.Enum;
278 }
279 else
280 {
281 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type.");
282 }
283 }
284
285 if (fieldType == FieldType.Message)
286 {
287 if (!(typeDescriptor is MessageDescriptor))
288 {
289 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
290 }
291 messageType = (MessageDescriptor) typeDescriptor;
292
293 if (Proto.DefaultValue != "")
294 {
295 throw new DescriptorValidationException(this, "Messages can't have default values.");
296 }
297 }
298 else if (fieldType == FieldType.Enum)
299 {
300 if (!(typeDescriptor is EnumDescriptor))
301 {
302 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
303 }
304 enumType = (EnumDescriptor) typeDescriptor;
305 }
306 else
307 {
308 throw new DescriptorValidationException(this, "Field with primitive type has type_name.");
309 }
310 }
311 else
312 {
313 if (fieldType == FieldType.Message || fieldType == FieldType.Enum)
314 {
315 throw new DescriptorValidationException(this, "Field with message or enum type missing type_name.");
316 }
317 }
318
319 // Note: no attempt to perform any default value parsing
320
321 File.DescriptorPool.AddFieldByNumber(this);
322
Austin Schuh40c16522018-10-28 20:27:54 -0700323 if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
Brian Silverman9c614bc2016-02-15 20:20:02 -0500324 {
325 throw new DescriptorValidationException(this, "MessageSet format is not supported.");
326 }
Austin Schuh40c16522018-10-28 20:27:54 -0700327 accessor = CreateAccessor();
Brian Silverman9c614bc2016-02-15 20:20:02 -0500328 }
329
Austin Schuh40c16522018-10-28 20:27:54 -0700330 private IFieldAccessor CreateAccessor()
Brian Silverman9c614bc2016-02-15 20:20:02 -0500331 {
332 // If we're given no property name, that's because we really don't want an accessor.
333 // (At the moment, that means it's a map entry message...)
334 if (propertyName == null)
335 {
336 return null;
337 }
Austin Schuh40c16522018-10-28 20:27:54 -0700338 var property = ContainingType.ClrType.GetProperty(propertyName);
Brian Silverman9c614bc2016-02-15 20:20:02 -0500339 if (property == null)
340 {
Austin Schuh40c16522018-10-28 20:27:54 -0700341 throw new DescriptorValidationException(this, $"Property {propertyName} not found in {ContainingType.ClrType}");
Brian Silverman9c614bc2016-02-15 20:20:02 -0500342 }
343 return IsMap ? new MapFieldAccessor(property, this)
344 : IsRepeated ? new RepeatedFieldAccessor(property, this)
345 : (IFieldAccessor) new SingleFieldAccessor(property, this);
346 }
347 }
348}