-
Notifications
You must be signed in to change notification settings - Fork 437
/
Descriptor.swift
1223 lines (1079 loc) · 50 KB
/
Descriptor.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Sources/SwiftProtobufPluginLibrary/Descriptor.swift - Descriptor wrappers
//
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See LICENSE.txt for license information:
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
//
// -----------------------------------------------------------------------------
///
/// This is like Descriptor.{h,cc} in the google/protobuf C++ code, it provides
/// wrappers around the protos to make a more usable object graph for generation
/// and also provides some SwiftProtobuf specific additions that would be useful
/// to anyone generating something that uses SwiftProtobufs (like support the
/// `service` messages). It is *not* the intent for these to eventually be used
/// as part of some reflection or generate message api.
///
/// Unlike the C++ Descriptors, the intent is for these to *only* be used within
/// the context of a protoc plugin, meaning, the
/// `Google_Protobuf_FileDescriptorSet` used to create these will be *always*
/// be well formed by protoc and the guarentees it provides.
///
// -----------------------------------------------------------------------------
import Foundation
import SwiftProtobuf
/// The front interface for building/getting descriptors. The objects
/// vended from the here are different from the raw
/// `Google_Protobuf_*Proto` types in that they have all the cross object
/// references resolved or wired up, making for an easier to use object
/// model.
///
/// This is like the `DescriptorPool` class in the C++ protobuf library.
public final class DescriptorSet {
/// The list of `FileDescriptor`s in this set.
public let files: [FileDescriptor]
private let registry = Registry()
// Construct out of a `Google_Protobuf_FileDescriptorSet` likely
// created by protoc.
public convenience init(proto: Google_Protobuf_FileDescriptorSet) {
self.init(protos: proto.file)
}
/// The bundled in `google.protobuf.FeatureSetDefault` that defines what
/// the plugin library can support.
private static let bundledFeatureSetDefaults =
// Decoding the bundle defaults better never fail
try! Google_Protobuf_FeatureSetDefaults(serializedBytes: bundledFeatureSetDefaultBytes)
/// The range of Editions that the library can support.
///
/// This will limit what edition versions a plugin can claim to support.
public static var bundledEditionsSupport: ClosedRange<Google_Protobuf_Edition> {
return bundledFeatureSetDefaults.minimumEdition...bundledFeatureSetDefaults.maximumEdition
}
/// Construct out of a ordered list of
/// `Google_Protobuf_FileDescriptorProto`s likely created by protoc.
public convenience init(protos: [Google_Protobuf_FileDescriptorProto]) {
self.init(protos: protos,
featureSetDefaults: DescriptorSet.bundledFeatureSetDefaults)
}
/// Construct out of a ordered list of
/// `Google_Protobuf_FileDescriptorProto`s likely created by protoc. Since
/// .proto files can import other .proto files, the imports have to be
/// listed before the things that use them so the graph can be
/// reconstructed.
///
/// - Parameters:
/// - protos: An ordered list of `Google_Protobuf_FileDescriptorProto`.
/// They must be order such that a file is provided before another file
/// that depends on it.
/// - featureSetDefaults: A `Google_Protobuf_FeatureSetDefaults` that provides
/// the Feature defaults to use when parsing the give File protos.
/// - featureExtensions: A list of Protobuf Extension extensions to
/// `google.protobuf.FeatureSet` that define custom features. If used, the
/// `defaults` should have been parsed with the extensions being
/// supported.
public init(
protos: [Google_Protobuf_FileDescriptorProto],
featureSetDefaults: Google_Protobuf_FeatureSetDefaults,
featureExtensions: [any AnyMessageExtension] = []
) {
precondition(Self.bundledEditionsSupport.contains(featureSetDefaults.minimumEdition),
"Attempt to use a FeatureSetDefault minimumEdition that isn't supported by the library.")
precondition(Self.bundledEditionsSupport.contains(featureSetDefaults.maximumEdition),
"Attempt to use a FeatureSetDefault maximumEdition that isn't supported by the library.")
// If a protoc is too old ≤v26, it might have `features` instead of `overridable_features` and
// `fixed_features`, try to catch that.
precondition(
nil == featureSetDefaults.defaults.first(where: { !$0.hasOverridableFeatures && !$0.hasFixedFeatures }),
"These FeatureSetDefault don't appear valid, make sure you are using a new enough protoc to generate them. ")
let registry = self.registry
self.files = protos.map {
return FileDescriptor(proto: $0,
featureSetDefaults: featureSetDefaults,
featureExtensions: featureExtensions,
registry: registry)
}
}
/// Lookup a specific file. The names for files are what was captured in
/// the `Google_Protobuf_FileDescriptorProto` when it was created, protoc
/// uses the path name for how the file was found.
///
/// This is a legacy api since it requires the file to be found or it aborts.
/// Mainly kept for grpc-swift compatibility.
@available(*, deprecated, renamed: "fileDescriptor(named:)")
public func lookupFileDescriptor(protoName name: String) -> FileDescriptor {
return registry.fileDescriptor(named: name)!
}
/// Find a specific file. The names for files are what was captured in
/// the `Google_Protobuf_FileDescriptorProto` when it was created, protoc
/// uses the path name for how the file was found.
public func fileDescriptor(named name: String) -> FileDescriptor? {
return registry.fileDescriptor(named: name)
}
/// Find the `Descriptor` for a named proto message.
public func descriptor(named fullName: String) -> Descriptor? {
return registry.descriptor(named: ".\(fullName)")
}
/// Find the `EnumDescriptor` for a named proto enum.
public func enumDescriptor(named fullName: String) -> EnumDescriptor? {
return registry.enumDescriptor(named: ".\(fullName)")
}
/// Find the `ServiceDescriptor` for a named proto service.
public func serviceDescriptor(named fullName: String) -> ServiceDescriptor? {
return registry.serviceDescriptor(named: ".\(fullName)")
}
}
/// Models a .proto file. `FileDescriptor`s are not directly created,
/// instead they are constructed/fetched via the `DescriptorSet` or
/// they are directly accessed via a `file` property on all the other
/// types of descriptors.
public final class FileDescriptor {
/// The filename used with protoc.
public let name: String
/// The proto package.
public let package: String
/// The edition of the file.
///
/// This is not public because generators should not make decisions based
/// on the Edition, to properly handing editions, they should be checking
/// Features and in most case the other `Descriptor` api that expose the
/// information.
public let edition: Google_Protobuf_Edition
/// The resolved features for this File.
public let features: Google_Protobuf_FeatureSet
/// The imports for this file.
public let dependencies: [FileDescriptor]
/// The subset of the imports that were declared `public`.
public let publicDependencies: [FileDescriptor]
/// The subset of the imports that were declared `weak`.
public let weakDependencies: [FileDescriptor]
/// The enum defintions at the file scope level.
public let enums: [EnumDescriptor]
/// The message defintions at the file scope level.
public let messages: [Descriptor]
/// The extension field defintions at the file scope level.
public let extensions: [FieldDescriptor]
/// The service defintions at the file scope level.
public let services: [ServiceDescriptor]
/// The `Google_Protobuf_FileOptions` set on this file.
public let options: Google_Protobuf_FileOptions
private let sourceCodeInfo: Google_Protobuf_SourceCodeInfo
/// The proto version of the descriptor that defines this File.
///
/// Thanks to Editions, this isn't likely to be exactly what
/// folks want anymore, so wave any other plugins off it.
@available(*, deprecated,
message: "Use the properties directly or open a GitHub issue for something missing")
public var proto: Google_Protobuf_FileDescriptorProto { return _proto }
private let _proto: Google_Protobuf_FileDescriptorProto
fileprivate init(
proto: Google_Protobuf_FileDescriptorProto,
featureSetDefaults: Google_Protobuf_FeatureSetDefaults,
featureExtensions: [any AnyMessageExtension],
registry: Registry
) {
self.name = proto.name
self.package = proto.package
// This logic comes from upstream `DescriptorBuilder::BuildFileImpl()`.
if proto.hasEdition {
self.edition = proto.edition
} else {
switch proto.syntax {
case "", "proto2":
self.edition = .proto2
case "proto3":
self.edition = .proto3
default:
self.edition = .unknown
fatalError(
"protoc provided an expected value (\"\(proto.syntax)\") for syntax/edition: \(proto.name)")
}
}
// TODO: Revsit capturing the error here and see about exposing it out
// to be reported via plugins.
let featureResolver: FeatureResolver
do {
featureResolver = try FeatureResolver(edition: self.edition,
featureSetDefaults: featureSetDefaults,
featureExtensions: featureExtensions)
} catch let e {
fatalError("Failed to make a FeatureResolver for \(self.name): \(e)")
}
let resolvedFeatures = featureResolver.resolve(proto.options)
self.features = resolvedFeatures
self.options = proto.options
let protoPackage = proto.package
self.enums = proto.enumType.enumerated().map {
return EnumDescriptor(proto: $0.element,
index: $0.offset,
parentFeatures: resolvedFeatures,
featureResolver: featureResolver,
registry: registry,
scope: protoPackage)
}
self.messages = proto.messageType.enumerated().map {
return Descriptor(proto: $0.element,
index: $0.offset,
parentFeatures: resolvedFeatures,
featureResolver: featureResolver,
registry: registry,
scope: protoPackage)
}
self.extensions = proto.extension.enumerated().map {
return FieldDescriptor(extension: $0.element,
index: $0.offset,
parentFeatures: resolvedFeatures,
featureResolver: featureResolver,
registry: registry)
}
self.services = proto.service.enumerated().map {
return ServiceDescriptor(proto: $0.element,
index: $0.offset,
fileFeatures: resolvedFeatures,
featureResolver: featureResolver,
registry: registry,
scope: protoPackage)
}
// The compiler ensures there aren't cycles between a file and dependencies, so
// this doesn't run the risk of creating any retain cycles that would force these
// to have to be weak.
let dependencies = proto.dependency.map { return registry.fileDescriptor(named: $0)! }
self.dependencies = dependencies
self.publicDependencies = proto.publicDependency.map { dependencies[Int($0)] }
self.weakDependencies = proto.weakDependency.map { dependencies[Int($0)] }
self.sourceCodeInfo = proto.sourceCodeInfo
self._proto = proto
// Done initializing, register ourselves.
registry.register(file: self)
// descriptor.proto documents the files will be in deps order. That means we
// any external reference will have been in the previous files in the set.
self.enums.forEach { $0.bind(file: self, registry: registry, containingType: nil) }
self.messages.forEach { $0.bind(file: self, registry: registry, containingType: nil) }
self.extensions.forEach { $0.bind(file: self, registry: registry, containingType: nil) }
self.services.forEach { $0.bind(file: self, registry: registry) }
}
/// Fetch the source information for a give path. For more details on the paths
/// and what this information is, see `Google_Protobuf_SourceCodeInfo`.
///
/// For simpler access to the comments for give message, fields, enums; see
/// `Descriptor+Extensions.swift` and the `ProvidesLocationPath` and
/// `ProvidesSourceCodeLocation` protocols.
public func sourceCodeInfoLocation(path: IndexPath) -> Google_Protobuf_SourceCodeInfo.Location? {
guard let location = locationMap[path] else {
return nil
}
return location
}
// Lazy so this can be computed on demand, as the imported files won't need
// comments during generation.
private lazy var locationMap: [IndexPath:Google_Protobuf_SourceCodeInfo.Location] = {
var result: [IndexPath:Google_Protobuf_SourceCodeInfo.Location] = [:]
for loc in sourceCodeInfo.location {
let intList = loc.path.map { return Int($0) }
result[IndexPath(indexes: intList)] = loc
}
return result
}()
}
/// Describes a type of protocol message, or a particular group within a
/// message. `Descriptor`s are not directly created, instead they are
/// constructed/fetched via the `DescriptorSet` or they are directly accessed
/// via a `messageType` property on `FieldDescriptor`s, etc.
public final class Descriptor {
/// The type of this Message.
public enum WellKnownType: String {
/// An instance of google.protobuf.DoubleValue.
case doubleValue = "google.protobuf.DoubleValue"
/// An instance of google.protobuf.FloatValue.
case floatValue = "google.protobuf.FloatValue"
/// An instance of google.protobuf.Int64Value.
case int64Value = "google.protobuf.Int64Value"
/// An instance of google.protobuf.UInt64Value.
case uint64Value = "google.protobuf.UInt64Value"
/// An instance of google.protobuf.Int32Value.
case int32Value = "google.protobuf.Int32Value"
/// An instance of google.protobuf.UInt32Value.
case uint32Value = "google.protobuf.UInt32Value"
/// An instance of google.protobuf.StringValue.
case stringValue = "google.protobuf.StringValue"
/// An instance of google.protobuf.BytesValue.
case bytesValue = "google.protobuf.BytesValue"
/// An instance of google.protobuf.BoolValue.
case boolValue = "google.protobuf.BoolValue"
/// An instance of google.protobuf.Any.
case any = "google.protobuf.Any"
/// An instance of google.protobuf.FieldMask.
case fieldMask = "google.protobuf.FieldMask"
/// An instance of google.protobuf.Duration.
case duration = "google.protobuf.Duration"
/// An instance of google.protobuf.Timestamp.
case timestamp = "google.protobuf.Timestamp"
/// An instance of google.protobuf.Value.
case value = "google.protobuf.Value"
/// An instance of google.protobuf.ListValue.
case listValue = "google.protobuf.ListValue"
/// An instance of google.protobuf.Struct.
case `struct` = "google.protobuf.Struct"
}
/// Describes an extension range of a message. `ExtensionRange`s are not
/// directly created, instead they are constructed/fetched via the
/// `Descriptor`.
public final class ExtensionRange {
/// The start field number of this range (inclusive).
public let start: Int32
// The end field number of this range (exclusive).
public let end: Int32
// Tndex of this extension range within the message's extension range array.
public let index: Int
/// The resolved features for this ExtensionRange.
public let features: Google_Protobuf_FeatureSet
/// The `Google_Protobuf_ExtensionRangeOptions` set on this ExtensionRange.
public let options: Google_Protobuf_ExtensionRangeOptions
/// The name of the containing type, not including its scope.
public var name: String { return containingType.name }
/// The fully-qualified name of the containing type, scope delimited by
/// periods.
public var fullName: String { return containingType.fullName }
/// The .proto file in which this ExtensionRange was defined.
public var file: FileDescriptor { return containingType.file }
/// The descriptor that owns with ExtensionRange.
public var containingType: Descriptor { return _containingType! }
// Storage for `containingType`, will be set by bind()
private unowned var _containingType: Descriptor?
fileprivate init(proto: Google_Protobuf_DescriptorProto.ExtensionRange,
index: Int,
features: Google_Protobuf_FeatureSet) {
self.start = proto.start
self.end = proto.end
self.index = index
self.features = features
self.options = proto.options
}
fileprivate func bind(containingType: Descriptor, registry: Registry) {
self._containingType = containingType
}
}
/// The name of the message type, not including its scope.
public let name: String
/// The fully-qualified name of the message type, scope delimited by
/// periods. For example, message type "Foo" which is declared in package
/// "bar" has full name "bar.Foo". If a type "Baz" is nested within
/// Foo, Baz's `fullName` is "bar.Foo.Baz". To get only the part that
/// comes after the last '.', use name().
public let fullName: String
/// Index of this descriptor within the file or containing type's message
/// type array.
public let index: Int
/// The .proto file in which this message type was defined.
public var file: FileDescriptor { return _file! }
/// If this Descriptor describes a nested type, this returns the type
/// in which it is nested.
public private(set) unowned var containingType: Descriptor?
/// The resolved features for this Descriptor.
public let features: Google_Protobuf_FeatureSet
/// The `Google_Protobuf_MessageOptions` set on this Message.
public let options: Google_Protobuf_MessageOptions
// If this descriptor represents a well known type, which type it is.
public let wellKnownType: WellKnownType?
/// The enum defintions under this message.
public let enums: [EnumDescriptor]
/// The message defintions under this message. In the C++ Descriptor this
/// is `nested_type`.
public let messages: [Descriptor]
/// The fields of this message.
public let fields: [FieldDescriptor]
/// The oneofs in this message. This can include synthetic oneofs.
public let oneofs: [OneofDescriptor]
/// Non synthetic oneofs.
///
/// These always come first (enforced by the C++ Descriptor code). So this is always a
/// leading subset of `oneofs` (or the same if there are no synthetic entries).
public private(set) lazy var realOneofs: [OneofDescriptor] = {
// Lazy because `isSynthetic` can't be called until after `bind()`.
return self.oneofs.filter { !$0.isSynthetic }
}()
/// The extension field defintions under this message.
public let extensions: [FieldDescriptor]
/// The extension ranges declared for this message. They are returned in
/// the order they are defined in the .proto file.
public let extensionRanges: [ExtensionRange]
/// The reserved field number ranges for this message. These are returned
/// in the order they are defined in the .proto file.
public let reservedRanges: [Range<Int32>]
/// The reserved field names for this message. These are returned in the
/// order they are defined in the .proto file.
public let reservedNames: [String]
/// Returns the `FieldDescriptor`s for the "key" and "value" fields. If
/// this isn't a map entry field, returns nil.
///
/// This is like the C++ Descriptor `map_key()` and `map_value()` methods.
public var mapKeyAndValue: (key: FieldDescriptor, value: FieldDescriptor)? {
guard options.mapEntry else { return nil }
precondition(fields.count == 2)
return (key: fields[0], value: fields[1])
}
// Storage for `file`, will be set by bind()
private unowned var _file: FileDescriptor?
fileprivate init(proto: Google_Protobuf_DescriptorProto,
index: Int,
parentFeatures: Google_Protobuf_FeatureSet,
featureResolver: FeatureResolver,
registry: Registry,
scope: String) {
self.name = proto.name
let fullName = scope.isEmpty ? proto.name : "\(scope).\(proto.name)"
self.fullName = fullName
self.index = index
let resolvedFeatures = featureResolver.resolve(proto.options, resolvedParent: parentFeatures)
self.features = resolvedFeatures
self.options = proto.options
self.wellKnownType = WellKnownType(rawValue: fullName)
self.reservedRanges = proto.reservedRange.map { return $0.start ..< $0.end }
self.reservedNames = proto.reservedName
// TODO: This can skip the synthetic oneofs as no features can be set on
// them to inherrit things.
let oneofFeatures = proto.oneofDecl.map {
return featureResolver.resolve($0.options, resolvedParent: resolvedFeatures)
}
self.extensionRanges = proto.extensionRange.enumerated().map {
return ExtensionRange(proto: $0.element,
index: $0.offset,
features: featureResolver.resolve($0.element.options,
resolvedParent: resolvedFeatures))
}
self.enums = proto.enumType.enumerated().map {
return EnumDescriptor(proto: $0.element,
index: $0.offset,
parentFeatures: resolvedFeatures,
featureResolver: featureResolver,
registry: registry,
scope: fullName)
}
self.messages = proto.nestedType.enumerated().map {
return Descriptor(proto: $0.element,
index: $0.offset,
parentFeatures: resolvedFeatures,
featureResolver: featureResolver,
registry: registry,
scope: fullName)
}
self.fields = proto.field.enumerated().map {
// For field Features inherrit from the parent oneof or message. A
// synthetic oneof (for proto3 optional) can't get features, so those
// don't come into play.
let inRealOneof = $0.element.hasOneofIndex && !$0.element.proto3Optional
return FieldDescriptor(messageField: $0.element,
index: $0.offset,
parentFeatures: inRealOneof ? oneofFeatures[Int($0.element.oneofIndex)] : resolvedFeatures,
featureResolver: featureResolver,
registry: registry)
}
self.oneofs = proto.oneofDecl.enumerated().map {
return OneofDescriptor(proto: $0.element,
index: $0.offset,
features: oneofFeatures[$0.offset],
registry: registry)
}
self.extensions = proto.extension.enumerated().map {
return FieldDescriptor(extension: $0.element,
index: $0.offset,
parentFeatures: resolvedFeatures,
featureResolver: featureResolver,
registry: registry)
}
// Done initializing, register ourselves.
registry.register(message: self)
}
fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) {
_file = file
self.containingType = containingType
extensionRanges.forEach { $0.bind(containingType: self, registry: registry) }
enums.forEach { $0.bind(file: file, registry: registry, containingType: self) }
messages.forEach { $0.bind(file: file, registry: registry, containingType: self) }
fields.forEach { $0.bind(file: file, registry: registry, containingType: self) }
oneofs.forEach { $0.bind(registry: registry, containingType: self) }
extensions.forEach { $0.bind(file: file, registry: registry, containingType: self) }
// Synthetic oneofs come after normal oneofs. The C++ Descriptor enforces this, only
// here as a secondary validation because other code can rely on it.
var seenSynthetic = false
for o in oneofs {
if seenSynthetic {
// Once we've seen one synthetic, all the rest must also be synthetic.
precondition(o.isSynthetic)
} else {
seenSynthetic = o.isSynthetic
}
}
}
}
/// Describes a type of protocol enum. `EnumDescriptor`s are not directly
/// created, instead they are constructed/fetched via the `DescriptorSet` or
/// they are directly accessed via a `EnumType` property on `FieldDescriptor`s,
/// etc.
public final class EnumDescriptor {
/// The name of this enum type in the containing scope.
public let name: String
/// The fully-qualified name of the enum type, scope delimited by periods.
public let fullName: String
/// Index of this enum within the file or containing message's enums.
public let index: Int
/// The .proto file in which this message type was defined.
public var file: FileDescriptor { return _file! }
/// If this Descriptor describes a nested type, this returns the type
/// in which it is nested.
public private(set) unowned var containingType: Descriptor?
/// The resolved features for this Enum.
public let features: Google_Protobuf_FeatureSet
/// The values defined for this enum. Guaranteed (by protoc) to be atleast
/// one item. These are returned in the order they were defined in the .proto
/// file.
public let values: [EnumValueDescriptor]
/// The `Google_Protobuf_MessageOptions` set on this enum.
public let options: Google_Protobuf_EnumOptions
/// The reserved value ranges for this enum. These are returned in the order
/// they are defined in the .proto file.
public let reservedRanges: [ClosedRange<Int32>]
/// The reserved value names for this enum. These are returned in the order
/// they are defined in the .proto file.
public let reservedNames: [String]
/// Returns true whether this is a "closed" enum, meaning that it:
/// - Has a fixed set of named values.
/// - Encountering values not in this set causes them to be treated as unknown
/// fields.
/// - The first value (i.e., the default) may be nonzero.
public var isClosed: Bool {
// Implementation comes from C++ EnumDescriptor::is_closed().
return features.enumType == .closed
}
// Storage for `file`, will be set by bind()
private unowned var _file: FileDescriptor?
fileprivate init(proto: Google_Protobuf_EnumDescriptorProto,
index: Int,
parentFeatures: Google_Protobuf_FeatureSet,
featureResolver: FeatureResolver,
registry: Registry,
scope: String) {
self.name = proto.name
self.fullName = scope.isEmpty ? proto.name : "\(scope).\(proto.name)"
self.index = index
let resolvedFeatures = featureResolver.resolve(proto.options, resolvedParent: parentFeatures)
self.features = resolvedFeatures
self.options = proto.options
self.reservedRanges = proto.reservedRange.map { return $0.start ... $0.end }
self.reservedNames = proto.reservedName
self.values = proto.value.enumerated().map {
return EnumValueDescriptor(proto: $0.element,
index: $0.offset,
features: featureResolver.resolve($0.element.options,
resolvedParent: resolvedFeatures),
scope: scope)
}
// Done initializing, register ourselves.
registry.register(enum: self)
values.forEach { $0.bind(enumType: self) }
}
fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) {
_file = file
self.containingType = containingType
}
}
/// Describes an individual enum constant of a particular type. To get the
/// `EnumValueDescriptor` for a given enum value, first get the `EnumDescriptor`
/// for its type.
public final class EnumValueDescriptor {
/// Name of this enum constant.
public let name: String
/// The full_name of an enum value is a sibling symbol of the enum type.
/// e.g. the full name of FieldDescriptorProto::TYPE_INT32 is actually
/// "google.protobuf.FieldDescriptorProto.TYPE_INT32", NOT
/// "google.protobuf.FieldDescriptorProto.Type.TYPE_INT32". This is to conform
/// with C++ scoping rules for enums.
public let fullName: String
/// Index within the enums's `EnumDescriptor`.
public let index: Int
/// Numeric value of this enum constant.
public let number: Int32
/// The resolved features for this EnumValue.
public let features: Google_Protobuf_FeatureSet
/// The .proto file in which this message type was defined.
public var file: FileDescriptor { return enumType.file }
/// The type of this value.
public var enumType: EnumDescriptor { return _enumType! }
/// The `Google_Protobuf_EnumValueOptions` set on this value.
public let options: Google_Protobuf_EnumValueOptions
// Storage for `enumType`, will be set by bind()
private unowned var _enumType: EnumDescriptor?
fileprivate init(proto: Google_Protobuf_EnumValueDescriptorProto,
index: Int,
features: Google_Protobuf_FeatureSet,
scope: String) {
self.name = proto.name
self.fullName = scope.isEmpty ? proto.name : "\(scope).\(proto.name)"
self.index = index
self.features = features
self.number = proto.number
self.options = proto.options
}
fileprivate func bind(enumType: EnumDescriptor) {
self._enumType = enumType
}
}
/// Describes a oneof defined in a message type.
public final class OneofDescriptor {
/// Name of this oneof.
public let name: String
/// Fully-qualified name of the oneof.
public var fullName: String { return "\(containingType.fullName).\(name)" }
/// Index of this oneof within the message's oneofs.
public let index: Int
/// The resolved features for this Oneof.
public let features: Google_Protobuf_FeatureSet
/// Returns whether this oneof was inserted by the compiler to wrap a proto3
/// optional field. If this returns true, code generators should *not* emit it.
var isSynthetic: Bool {
return fields.count == 1 && fields.first!.proto3Optional
}
/// The .proto file in which this oneof type was defined.
public var file: FileDescriptor { return containingType.file }
/// The Descriptor of the message that defines this oneof.
public var containingType: Descriptor { return _containingType! }
/// The `Google_Protobuf_OneofOptions` set on this oneof.
public let options: Google_Protobuf_OneofOptions
/// The members of this oneof, in the order in which they were declared in the
/// .proto file.
public private(set) lazy var fields: [FieldDescriptor] = {
let myIndex = Int32(self.index)
return containingType.fields.filter { $0.oneofIndex == myIndex }
}()
// Storage for `containingType`, will be set by bind()
private unowned var _containingType: Descriptor?
fileprivate init(proto: Google_Protobuf_OneofDescriptorProto,
index: Int,
features: Google_Protobuf_FeatureSet,
registry: Registry) {
self.name = proto.name
self.index = index
self.features = features
self.options = proto.options
}
fileprivate func bind(registry: Registry, containingType: Descriptor) {
_containingType = containingType
}
}
/// Describes a single field of a message. To get the descriptor for a given
/// field, first get the `Descriptor` for the message in which it is defined,
/// then find the field. To get a `FieldDescriptor` for an extension, get the
/// `Descriptor` or `FileDescriptor` for its containing scope, find the
/// extension.
public final class FieldDescriptor {
/// Name of this field within the message.
public let name: String
/// Fully-qualified name of the field.
public var fullName: String {
// Since the fullName isn't needed on fields that often, compute it on demand.
guard isExtension else {
// Normal field on a message.
return "\(containingType.fullName).\(name)"
}
if let extensionScope = extensionScope {
return "\(extensionScope.fullName).\(name)"
}
let package = file.package
return package.isEmpty ? name : "\(package).\(name)"
}
/// JSON name of this field.
public let jsonName: String
public let features: Google_Protobuf_FeatureSet
/// File in which this field was defined.
public var file: FileDescriptor { return _file! }
/// If this is an extension field.
public let isExtension: Bool
/// The field number.
public let number: Int32
/// Valid field numbers are positive integers up to kMaxNumber.
static let kMaxNumber: Int = (1 << 29) - 1
/// First field number reserved for the protocol buffer library
/// implementation. Users may not declare fields that use reserved numbers.
static let kFirstReservedNumber: Int = 19000
/// Last field number reserved for the protocol buffer library implementation.
/// Users may not declare fields that use reserved numbers.
static let kLastReservedNumber: Int = 19999
/// Declared type of this field.
public let type: Google_Protobuf_FieldDescriptorProto.TypeEnum
/// optional/required/repeated
public let label: Google_Protobuf_FieldDescriptorProto.Label
/// Shorthand for `label` == `.required`.
///
/// NOTE: This could also be a map as the are also repeated fields.
public var isRequired: Bool {
// Implementation comes from FieldDescriptor::is_required()
return features.fieldPresence == .legacyRequired
}
/// Shorthand for `label` == `.optional`
public var isOptional: Bool { return label == .optional }
/// Shorthand for `label` == `.repeated`
public var isRepeated: Bool { return label == .repeated }
/// Is this field packable.
public var isPackable: Bool {
// This logic comes from the C++ FieldDescriptor::is_packable() impl.
return label == .repeated && FieldDescriptor.isPackable(type: type)
}
/// If this field is packable and packed.
public var isPacked: Bool {
// This logic comes from the C++ FieldDescriptor::is_packed() impl.
guard isPackable else { return false }
return features.repeatedFieldEncoding == .packed
}
/// True if this field is a map.
public var isMap: Bool {
// This logic comes from the C++ FieldDescriptor::is_map() impl.
return type == .message && messageType!.options.mapEntry
}
/// Returns true if this field was syntactically written with "optional" in the
/// .proto file. Excludes singular proto3 fields that do not have a label.
var hasOptionalKeyword: Bool {
// This logic comes from the C++ FieldDescriptor::has_optional_keyword()
// impl.
return proto3Optional ||
(file.edition == .proto2 && label == .optional && oneofIndex == nil)
}
/// Returns true if this field tracks presence, ie. does the field
/// distinguish between "unset" and "present with default value."
/// This includes required, optional, and oneof fields. It excludes maps,
/// repeated fields, and singular proto3 fields without "optional".
public var hasPresence: Bool {
// This logic comes from the C++ FieldDescriptor::has_presence() impl.
guard label != .repeated else { return false }
switch type {
case .group, .message:
// Groups/messages always get field presence.
return true
default:
break
}
return isExtension || oneofIndex != nil || features.fieldPresence != .implicit
}
/// Returns true if this is a string field and should do UTF-8 validation.
///
/// This api is for completeness, but it likely should never be used. The
/// concept comes from the C++ FieldDescriptory::requires_utf8_validation(),
/// but doesn't make a lot of sense for Swift Protobuf because `string` fields
/// are modeled as Swift `String` objects, and thus they always have to be
/// valid UTF-8. If something were to try putting something else in the field,
/// the library won't be able to parse it. While that sounds bad, other
/// languages have similar issues with their _string_ types and thus have the
/// same issues.
public var requiresUTF8Validation: Bool {
return type == .string && features.utf8Validation == .verify
}
/// Index of this field within the message's fields, or the file or
/// extension scope's extensions.
public let index: Int
/// The explicitly declared default value for this field.
///
/// This is the *raw* string value from the .proto file that was listed as
/// the default, it is up to the consumer to convert it correctly for the
/// type of this field. The C++ FieldDescriptor does offer some apis to
/// help with that, but at this time, that is not provided here.
public let defaultValue: String?
/// The `Descriptor` of the message which this is a field of. For extensions,
/// this is the extended type.
public var containingType: Descriptor { return _containingType! }
/// The oneof this field is a member of.
public var containingOneof: OneofDescriptor? {
guard let oneofIndex = oneofIndex else { return nil }
return containingType.oneofs[Int(oneofIndex)]
}
/// The non synthetic oneof this field is a member of.
public var realContainingOneof: OneofDescriptor? {
guard let oneof = containingOneof, !oneof.isSynthetic else { return nil }
return oneof
}
/// The index in a oneof this field is in.
public let oneofIndex: Int32?
// This builds basically a union for the storage for `extensionScope`
// and the value to look it up with.
private enum ExtensionScopeStorage {
case extendee(String) // The value to be used for looked up during `bind()`.
case message(UnownedBox<Descriptor>)
}
private var _extensionScopeStorage: ExtensionScopeStorage?
/// Extensions can be declared within the scope of another message. If this
/// is an extension field, then this will be the scope it was declared in
/// nil if was declared at a global scope.
public var extensionScope: Descriptor? {
guard case .message(let boxed) = _extensionScopeStorage else { return nil }
return boxed.value
}
// This builds basically a union for the storage for `messageType`
// and `enumType` since only one can needed at a time.
private enum FieldTypeStorage {
case typeName(String) // The value to be looked up during `bind()`.
case message(UnownedBox<Descriptor>)
case `enum`(UnownedBox<EnumDescriptor>)
}
private var _fieldTypeStorage: FieldTypeStorage?
/// When this is a message/group field, that message's `Desciptor`.
public var messageType: Descriptor? {
guard case .message(let boxed) = _fieldTypeStorage else { return nil }
return boxed.value
}
/// When this is a enum field, that enum's `EnumDesciptor`.
public var enumType: EnumDescriptor? {
guard case .enum(let boxed) = _fieldTypeStorage else { return nil }
return boxed.value
}
/// The FieldOptions for this field.
public var options: Google_Protobuf_FieldOptions
let proto3Optional: Bool
// Storage for `containingType`, will be set by bind()
private unowned var _containingType: Descriptor?
// Storage for `file`, will be set by bind()
private unowned var _file: FileDescriptor?
fileprivate convenience init(messageField proto: Google_Protobuf_FieldDescriptorProto,
index: Int,
parentFeatures: Google_Protobuf_FeatureSet,
featureResolver: FeatureResolver,
registry: Registry) {
precondition(proto.extendee.isEmpty) // Only for extensions
// On regular fields, it only makes sense to get `.proto3Optional`
// when also in a (synthetic) oneof. So...no oneof index, it better
// not be `.proto3Optional`
precondition(proto.hasOneofIndex || !proto.proto3Optional)
self.init(proto: proto,
index: index,
parentFeatures: parentFeatures,
featureResolver: featureResolver,
registry: registry,
isExtension: false)
}
fileprivate convenience init(extension proto: Google_Protobuf_FieldDescriptorProto,
index: Int,
parentFeatures: Google_Protobuf_FeatureSet,
featureResolver: FeatureResolver,
registry: Registry) {
precondition(!proto.extendee.isEmpty) // Required for extensions
// FieldDescriptorProto is used for fields or extensions, generally
// .proto3Optional only makes sense on fields if it is in a oneof. But,
// it is allowed on extensions. For information on that, see
// https://github.com/protocolbuffers/protobuf/issues/8234#issuecomment-774224376
// The C++ Descriptor code encorces the field/oneof part, but nothing
// is checked on the oneof side.
precondition(!proto.hasOneofIndex)
self.init(proto: proto,
index: index,
parentFeatures: parentFeatures,
featureResolver: featureResolver,
registry: registry,
isExtension: true)
}
private init(proto: Google_Protobuf_FieldDescriptorProto,
index: Int,
parentFeatures: Google_Protobuf_FeatureSet,
featureResolver: FeatureResolver,
registry: Registry,
isExtension: Bool) {
self.name = proto.name
self.index = index
self.features = featureResolver.resolve(proto, resolvedParent: parentFeatures)
self.defaultValue = proto.hasDefaultValue ? proto.defaultValue : nil
precondition(proto.hasJsonName) // protoc should always set the name
self.jsonName = proto.jsonName
self.isExtension = isExtension
self.number = proto.number
// This remapping is based follow part of what upstream
// `DescriptorBuilder::PostProcessFieldFeatures()` does. It is needed to