1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 
21 /// Protocol for Generated Structs to conform to
22 /// Dictionary maps field names to internal IDs and uses Reflection
23 /// to iterate through all fields.
24 /// `writeFieldValue(_:name:type:id:)` calls `TSerializable.write(to:)` internally
25 /// giving a nice recursive behavior for nested TStructs, TLists, TMaps, and TSets
26 public protocol TStruct : TSerializable {
27   static var fieldIds: [String: Int32] { get }
28   static var structName: String { get }
29 }
30 
31 public extension TStruct {
32   static var fieldIds: [String: (id: Int32, type: TType)] { return [:] }
33   static var thriftType: TType { return .struct }
34 
writenull35   func write(to proto: TProtocol) throws {
36     // Write struct name first
37     try proto.writeStructBegin(name: Self.structName)
38 
39     try self.forEach { name, value, id in
40       // Write to protocol
41       try proto.writeFieldValue(value, name: name,
42                                 type: value.thriftType, id: id)
43     }
44     try proto.writeFieldStop()
45     try proto.writeStructEnd()
46   }
47 
48   /// Provides a block for handling each (available) thrift property using reflection
49   /// Caveat: Skips over optional values
50 
51 
52   /// Provides a block for handling each (available) thrift property using reflection
53   ///
54   /// - parameter block: block for handling property
55   ///
56   /// - throws: rethrows any Error thrown in block
57   private func forEach(_ block: (_ name: String, _ value: TSerializable, _ id: Int32) throws -> Void) rethrows {
58     // Mirror the object, getting (name: String?, value: Any) for every property
59     let mirror = Mirror(reflecting: self)
60 
61     // Iterate through all children, ignore empty property names
62     for (propName, propValue) in mirror.children {
63       guard let propName = propName else { continue }
64 
65       if let tval = unwrap(any: propValue, parent: mirror) as? TSerializable, let id = Self.fieldIds[propName] {
66         try block(propName, tval, id)
67       }
68     }
69   }
70 
71 
72   /// Any can mysteriously be an Optional<Any> at the same time,
73   /// this checks and always returns Optional<Any> without double wrapping
74   /// we then try to bind value as TSerializable to ignore any extension properties
75   /// and the like and verify the property exists and grab the Thrift
76   /// property ID at the same time
77   ///
78   /// - parameter any: Any instance to attempt to unwrap
79   ///
80   /// - returns: Unwrapped Any as Optional<Any>
unwrapnull81   private func unwrap(any: Any, parent: Mirror) -> Any? {
82     let mi = Mirror(reflecting: any)
83 
84     if parent.displayStyle != .enum && mi.displayStyle != .optional { return any }
85     if mi.children.count == 0 { return nil }
86 
87     let (_, some) = mi.children.first!
88     return some
89   }
90 }
91 
92